from __future__ import annotations

from typing import TYPE_CHECKING, Any, Generic, Literal, TypedDict, TypeVar, overload

from sunflare.device import Device
from sunflare.presenter import Presenter
from sunflare.view import View
from typing_extensions import NotRequired, Required

if TYPE_CHECKING:
    from sunflare.virtual import VirtualBus


T = TypeVar("T")


class RedSunConfig(TypedDict, total=False):
    """Base configuration dictionary for Redsun containers.

    Describes the common top-level keys that may appear in a container
    configuration YAML file.  Users should inherit from this class to
    add component-specific sections.
    """

    schema: Required[float]
    """Version number for the configuration schema."""
    session: Required[str]
    """Display name for the session."""
    frontend: Required[str]
    """Frontend toolkit identifier (e.g. ``"pyqt"``, ``"pyside"``)."""

    devices: NotRequired[dict[str, Any]]
    """Dictionary of device kwargs, keyed by component name."""
    presenters: NotRequired[dict[str, Any]]
    """Dictionary of presenter kwargs, keyed by component name."""
    views: NotRequired[dict[str, Any]]
    """Dictionary of view kwargs, keyed by component name."""


class _ComponentField:
    """Internal sentinel returned by `component`.

    Stores the component class, layer assignment, and keyword arguments
    until the `AppContainerMeta` metaclass resolves them into concrete
    component wrappers.
    """

    __slots__ = ("cls", "layer", "alias", "from_config", "kwargs")

    def __init__(
        self,
        cls: type,
        layer: Literal["device", "presenter", "view"],
        alias: str | None,
        from_config: str | None,
        kwargs: dict[str, Any],
    ) -> None:
        self.cls = cls
        self.layer = layer
        self.alias = alias
        self.from_config = from_config
        self.kwargs = kwargs


@overload
def component(
    cls: type,
    *,
    layer: Literal["device"],
    alias: str | None = ...,
    from_config: str | None = ...,
    **kwargs: Any,
) -> Any: ...
@overload
def component(
    cls: type,
    *,
    layer: Literal["presenter"],
    alias: None = ...,
    from_config: str | None = ...,
    **kwargs: Any,
) -> Any: ...
@overload
def component(
    cls: type,
    *,
    layer: Literal["view"],
    alias: None = ...,
    from_config: str | None = ...,
    **kwargs: Any,
) -> Any: ...
def component(
    cls: type,
    *,
    layer: Literal["device", "presenter", "view"],
    alias: str | None = None,
    from_config: str | None = None,
    **kwargs: Any,
) -> Any:
    """Declare a component as a class field.

    This function is a field specifier for use with
    `~redsun.containers.AppContainer` subclasses.  The component class
    is passed directly as the first argument, and the attribute name
    becomes the component name.

    Parameters
    ----------
    cls : type
        The component class to instantiate. For ``layer="device"`` this
        must be a `Device` subclass. For ``layer="presenter"`` and
        ``layer="view"`` any class with the appropriate constructor
        signature is accepted, including protocol-based implementations
        that do not inherit from `Presenter` or `View` directly.
    layer : "device" | "presenter" | "view"
        The layer this component belongs to.
    alias : str | None
        For device components only: an alternative name to pass to the
        device constructor instead of the attribute name. Ignored for
        presenters and views. Defaults to `None`.
    from_config : str | None
        The key to look up in the configuration file's ``devices``,
        ``presenters``, or ``views`` section (based on ``layer``).
        If `None`, kwargs must be specified inline. Defaults to `None`.
    **kwargs : `Any`
        Additional keyword arguments forwarded to the component
        constructor at build time. These override values from the
        configuration file if ``from_config`` is set.

    Returns
    -------
    `Any`
        A `_ComponentField` sentinel (typed as `Any` so that the
        attribute assignment satisfies type checkers).

    Examples
    --------
    >>> class MyApp(AppContainer):
    ...     motor = component(MyMotor, layer="device", axis=["X"])
    ...     ctrl = component(MyCtrl, layer="presenter", gain=1.0)
    ...     ui = component(MyView, layer="view")

    With a config file:

    >>> class MyApp(AppContainer, config="app_config.yaml"):
    ...     motor = component(MyMotor, layer="device", from_config="motor")
    """
    return _ComponentField(
        cls=cls, layer=layer, alias=alias, from_config=from_config, kwargs=kwargs
    )


class _ComponentBase(Generic[T]):
    """Generic base class for components."""

    __slots__ = ("cls", "name", "alias", "kwargs", "_instance")

    def __init__(
        self, cls: type[T], name: str, alias: str | None, /, **kwargs: Any
    ) -> None:
        self.cls = cls
        self.name = name
        self.alias = alias
        self.kwargs = kwargs
        self._instance: T | None = None

    @property
    def instance(self) -> T:
        if self._instance is None:
            raise RuntimeError(
                f"Component {self.name} has not been instantiated yet. Call 'build' first."
            )
        return self._instance

    def __repr__(self) -> str:
        status = "built" if self._instance is not None else "pending"
        return f"{self.__class__.__name__}({self.name!r}, {status})"


class _DeviceComponent(_ComponentBase[Device]):
    """Device component wrapper."""

    def build(self) -> Device:
        """Build the device instance."""
        name = self.alias if self.alias is not None else self.name
        self._instance = self.cls(name, **self.kwargs)
        return self.instance


class _PresenterComponent(_ComponentBase[Presenter]):
    """Presenter component wrapper."""

    def build(self, devices: dict[str, Device], virtual_bus: VirtualBus) -> Presenter:
        """Build the presenter instance."""
        self._instance = self.cls(devices, virtual_bus, **self.kwargs)
        return self.instance


class _ViewComponent(_ComponentBase[View]):
    """View component wrapper."""

    def build(self, virtual_bus: VirtualBus) -> View:
        """Build the view instance."""
        self._instance = self.cls(virtual_bus, **self.kwargs)
        return self.instance
