# /src/xstate_statemachine/events.py
# -----------------------------------------------------------------------------
# ✉️ Event System Data Contracts
# -----------------------------------------------------------------------------
# This module defines the core event structures used throughout the state
# machine interpreter. These immutable, typed data structures ensure consistent
# and predictable communication for all event types:
#
#   1. `Event`: For external events triggered by users or systems.
#   2. `DoneEvent`: For internal events signaling the completion of services.
#   3. `AfterEvent`: For internal events triggered by timed delays.
#
# By standardizing these structures we create a clear, lightweight, and
# maintainable contract for how different parts of the system interact with the
# state machine. This adheres to SOLID principles by defining distinct,
# single-responsibility data structures.
# -----------------------------------------------------------------------------
"""
Defines the core, immutable event types for the state machine.

This module provides the data classes used for communication with the
state machine interpreters (`Interpreter` and `SyncInterpreter`). Historically
these events were implemented with ``NamedTuple`` but the ``Event`` type now
uses a ``dataclass`` so that a fresh payload dictionary is created for every
instance. This prevents subtle bugs where mutating the payload of one event
would affect all subsequently created events.
"""

# -----------------------------------------------------------------------------
# 📦 Standard Library Imports
# -----------------------------------------------------------------------------
from dataclasses import dataclass, field
from typing import Any, Dict, NamedTuple

# -----------------------------------------------------------------------------
# 📨 Event Definitions
# -----------------------------------------------------------------------------
# These classes represent the different types of events that can be processed
# by the state machine interpreter. They serve as Data Transfer Objects (DTOs)
# that carry information into and within the machine.
# -----------------------------------------------------------------------------


@dataclass(frozen=True)
class Event:
    """Represents a standard event sent to the state machine.

    This is the most common type of event, typically triggered by external
    inputs, user actions, or other system components. It serves as the primary
    mechanism for driving state transitions.

    Attributes:
        type: The name of the event, which is used to match against
            transitions defined in the machine's `on` property.
        payload: An optional dictionary containing extra data associated with
            the event. This data is accessible to actions and guards,
            allowing them to make dynamic decisions.

    Example:
        >>> # An event representing a user login attempt with a payload.
        >>> login_event = Event(type="USER_LOGIN", payload={"username": "alice"})
        >>> print(login_event)
        Event(type='USER_LOGIN', payload={'username': 'alice'})

        >>> # A simple event with no payload.
        >>> timer_tick_event = Event(type="TICK")
        >>> print(timer_tick_event)
        Event(type='TICK', payload={})
    """

    # 🏷️ The unique identifier for the event type (e.g., "SUBMIT", "CANCEL").
    type: str

    # 📦 A dictionary for any additional, dynamic data. ``default_factory`` is
    # used instead of a plain ``{}`` to ensure that each ``Event`` receives its
    # own payload dictionary rather than sharing one across instances.
    payload: Dict[str, Any] = field(default_factory=dict)

    @property
    def data(self) -> Dict[str, Any]:
        """Alias for `payload` to maintain compatibility with older versions."""
        # This property allows access to the payload using the `data` attribute,
        # which is a common convention in state machine libraries.
        # It ensures that existing code using `data` will continue to work.
        if not isinstance(self.payload, dict):
            raise TypeError(
                f"Expected payload to be a dict, got {type(self.payload).__name__}"
            )

        return self.payload


class DoneEvent(NamedTuple):
    """Represents the completion of a background service or a final state.

    This is an internal event generated by the interpreter when:
    1. An `invoke`d service finishes its work successfully.
    2. A compound or parallel state reaches its `final` state configuration.

    The `type` attribute follows a specific naming convention to avoid
    collisions with user-defined events, making it easy to target `onDone`
    transitions declaratively in the machine configuration.

    Attributes:
        type: The event name, following a strict convention:
            - `done.invoke.<service_id>` for invoked services.
            - `done.state.<state_id>` for states reaching a final state.
        data: The data returned by the completed service or from a final
            state's `data` property. This is the primary way services
            pass results back to the machine.
        src: The unique identifier of the service or state that generated
             this event, allowing for targeted transitions.

    Example:
        >>> # Event from a completed 'fetchData' service.
        >>> done_invoke_event = DoneEvent(
        ...     type="done.invoke.fetchData",
        ...     data={"user_id": 123, "name": "Alice"},
        ...     src="fetchData"
        ... )
        >>> print(done_invoke_event)
        DoneEvent(type='done.invoke.fetchData', data={'user_id': 123, 'name': 'Alice'}, src='fetchData')
    """

    # 🏷️ The structured name of the completion event.
    type: str

    # 📊 The data payload returned from the source (e.g., the return
    #    value of a service function).
    data: Any

    # 📍 The ID of the invoked service or state that completed.
    src: str


class AfterEvent(NamedTuple):
    """Represents a delayed event used for timed (`after`) transitions.

    This event is scheduled and sent internally by the `Interpreter`. A developer
    using the library typically does not create this event manually. It is
    generated when the interpreter enters a state that has an `after`
    transition defined in its configuration.

    Attributes:
        type: The event name, which is internally generated by the interpreter
              to be unique to the state and delay (e.g., `after.5000.myState`).
              This is used to match the specific `after` transition.

    Example:
        A developer defines an `after` transition in their JSON:
        ```json
        "states": {
          "pending": {
            "after": {
              "3000": { "target": "timed_out" }
            }
          },
          "timed_out": {}
        }
        ```

        After 3 seconds in the `pending` state, the interpreter would create
        and process an event like this one internally:

        >>> after_event = AfterEvent(type="after.3000.machineName.pending")
        >>> print(after_event)
        AfterEvent(type='after.3000.machineName.pending')
    """

    # 🏷️ The structured, internally-generated name of the delayed event.
    type: str
