"""DomainEvent base model.

All domain events emitted through an
:class:`~cleveragents.infrastructure.events.protocol.EventBus` must be
instances of :class:`DomainEvent` (or a subclass).

Based on:
    - docs/specification.md §Event-Driven Architecture
    - Forgejo issue #473
"""

from __future__ import annotations

from datetime import UTC, datetime
from typing import Any

from pydantic import BaseModel, ConfigDict, Field
from ulid import ULID

from cleveragents.infrastructure.events.types import EventType


class DomainEvent(BaseModel):
    """Base event model emitted by all domain operations.

    Attributes:
        event_type: The type of domain event.
        timestamp: UTC datetime when the event was created.
        correlation_id: Unique ULID that links related events in a
            single request/operation chain.
        plan_id: ULID of the plan associated with this event, if any.
        root_plan_id: ULID of the top-level plan in a hierarchy,
            if any.
        session_id: ULID of the session, if any.
        actor_name: Name of the actor that emitted the event, if any.
        project_name: Name of the project context, if any.
        user_identity: Authenticated user identity, if available.
            ``None`` in local (unauthenticated) mode.
        details: Event-type-specific payload (arbitrary key/value pairs).
    """

    model_config = ConfigDict(frozen=True)

    event_type: EventType
    timestamp: datetime = Field(
        default_factory=lambda: datetime.now(tz=UTC),
        description="UTC datetime of event creation.",
    )
    correlation_id: str = Field(
        default_factory=lambda: str(ULID()),
        description=(
            "Unique ULID linking related events. "
            "NOTE: Extension beyond specification for request traceability."
        ),
    )
    plan_id: str | None = Field(default=None, description="Associated plan ULID.")
    root_plan_id: str | None = Field(
        default=None, description="Root plan ULID in a hierarchy."
    )
    session_id: str | None = Field(default=None, description="Associated session ULID.")
    actor_name: str | None = Field(
        default=None, description="Actor that emitted this event."
    )
    project_name: str | None = Field(
        default=None, description="Project context of this event."
    )
    user_identity: str | None = Field(
        default=None,
        description=(
            "Authenticated user identity associated with this event, "
            "if available.  ``None`` in local (unauthenticated) mode."
        ),
    )
    details: dict[str, Any] = Field(
        default_factory=dict,
        description="Event-type-specific payload.",
    )


__all__ = ["DomainEvent"]
