"""Base ElementHandle class for the output rendering framework.

Provides the generic handle type that all concrete handles inherit from.
"""

from __future__ import annotations

import threading
from typing import TYPE_CHECKING, Any

from cleveragents.cli.output.handles._models import (
    ElementClosed,
    ElementClosedError,
    ElementSnapshot,
    ElementUpdated,
)

if TYPE_CHECKING:
    from cleveragents.cli.output.session import OutputSession


class ElementHandle[E: ElementSnapshot]:
    """Base class for all element handles.

    An element handle is a write-only view of an output element.  Producers
    use handles to incrementally build element content without knowledge of
    the active format or materialisation strategy.

    Thread Safety
    -------------
    Individual handle methods are thread-safe.  Concurrent writes to the
    *same* handle are serialised via an internal lock.

    Lifecycle
    ---------
    ``handle = session.panel(...)``   -- Created by session factory
    ``handle.set_entry(...)``         -- Write operations (zero or more)
    ``handle.close()``                -- Finalise (required unless auto-closed)
    """

    def __init__(
        self,
        handle_id: str,
        element_type: str,
        declaration_index: int,
        session: OutputSession,
        element: E,
    ) -> None:
        if not handle_id:
            raise ValueError("handle_id cannot be empty")
        if not element_type:
            raise ValueError("element_type cannot be empty")
        if declaration_index < 0:
            raise ValueError("declaration_index must be non-negative")
        if session is None:
            raise ValueError("session cannot be None")
        if element is None:
            raise ValueError("element cannot be None")

        self.handle_id = handle_id
        self.element_type = element_type
        self.declaration_index = declaration_index
        self._session: OutputSession = session
        self._element: E = element
        self._state: str = "open"
        self._lock: threading.Lock = threading.Lock()

    def _require_open(self) -> None:
        """Raise if handle is closed."""
        if self._state != "open":
            raise ElementClosedError(
                f"Handle {self.handle_id!r} ({self.element_type}) is closed"
            )

    def _emit_update(self, update_type: str, delta: dict[str, Any]) -> None:
        """Emit an ElementUpdated event to the session.

        N3 optimisation: skip Pydantic object construction entirely when
        the active strategy does not consume incremental updates.
        """
        if not self._session._strategy.supports_incremental_updates:
            return
        event = ElementUpdated(
            event_type="updated",
            handle_id=self.handle_id,
            element_kind=self.element_type,
            update_type=update_type,
            delta=delta,
            element_snapshot=self._element,  # type-safe via type parameter E
        )
        self._session._dispatch_event(event)

    @property
    def is_open(self) -> bool:
        """Whether this handle is still accepting writes."""
        return self._state == "open"

    @property
    def element(self) -> E:
        """The accumulated element state (read-only snapshot).

        Returns the internal model reference for performance.  Use
        ``element_copy()`` if a detached deep copy is required (L3).
        """
        return self._element

    def element_copy(self) -> E:
        """Return a deep copy of the accumulated element state.

        Acquires the handle lock to prevent reading mutable state
        while a concurrent write is in progress.
        """
        with self._lock:
            return self._element.model_copy(deep=True)

    def close(self) -> None:
        """Close this handle, signalling that no more data will be written."""
        with self._lock:
            if self._state == "open":
                self._state = "closed"
                event = ElementClosed(
                    event_type="closed",
                    handle_id=self.handle_id,
                    element_kind=self.element_type,
                    final_state=self._element,  # type-safe via type parameter E
                )
                self._session._dispatch_event(event)

    def __enter__(self) -> ElementHandle[E]:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: Any,
    ) -> None:
        if self.is_open:
            self.close()
