"""``PluginRegistry`` — the singleton that routes macro/slider lookups and the
device data/image senders.

Standalone (author machine, emulator, tests) this is a faithful re-implementation
of the app's registry. **Inside the running app** the live ``PluginRegistry`` is
re-exported instead, so ``push_widget_data`` reaches the real serial link and
macro/slider registration lands in the same table the app reads. The emulator
sets ``PluginRegistry.instance()._wdata_sender`` to capture pushes with no
hardware attached.
"""
from __future__ import annotations

import sys as _sys
from typing import Callable, Dict, Optional


class PluginRegistry:
    """Singleton registry for loaded plugins (SDK/standalone edition)."""

    _instance: "Optional[PluginRegistry]" = None

    def __init__(self) -> None:
        self._plugins: Dict[str, object] = {}          # plugin_id → instance
        self._macro_type_map: Dict[str, object] = {}   # "plugin:<id>" → instance
        self._slider_mode_map: Dict[str, object] = {}  # mode_id → instance
        #: Callable(str) -> bool. Set by the host (app or emulator).
        self._wdata_sender: Optional[Callable[[str], bool]] = None
        #: Callable(key:str, rgb565:bytes, w:int, h:int) -> bool.
        self._wimg_sender: Optional[Callable[..., bool]] = None

    @classmethod
    def instance(cls) -> "PluginRegistry":
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    @property
    def plugins(self) -> Dict[str, object]:
        return self._plugins

    def register(self, plugin) -> None:
        pid = getattr(plugin, "plugin_id", "")
        if not pid:
            return
        self._plugins[pid] = plugin
        self._macro_type_map[f"plugin:{pid}"] = plugin
        mode = None
        try:
            mode = plugin.get_slider_mode()
        except Exception:
            mode = None
        if mode:
            self._slider_mode_map[mode[0]] = plugin

    def unregister(self, pid: str) -> None:
        self._plugins.pop(pid, None)
        self._macro_type_map.pop(f"plugin:{pid}", None)
        self._slider_mode_map = {
            k: v for k, v in self._slider_mode_map.items()
            if getattr(v, "plugin_id", None) != pid
        }

    def get_macro_plugin(self, step_type: str):
        return self._macro_type_map.get(step_type)

    def get_slider_plugin(self, mode_id: str):
        return self._slider_mode_map.get(mode_id)

    def all_macro_step_types(self):
        return list(self._macro_type_map.keys())

    def all_slider_modes(self):
        return list(self._slider_mode_map.keys())


# Inside the live app, re-export its registry so pushes hit the real hardware
# and registrations share one table. The app may be `mixlar_mini` or `__main__`.
from ._host import find_app as _find_app  # noqa: E402
_app = _find_app()
if _app is not None:  # pragma: no cover
    PluginRegistry = _app.PluginRegistry  # type: ignore[misc,assignment]


__all__ = ["PluginRegistry"]
