"""Capability registries. Core built-ins and plugins register through the
same calls — one code path, no special-casing.

Capabilities:
    game_adapter    GAME/LABEL/parse(payload,state) + optional config_text/
                    CONFIG_FILENAME/CONFIG_HINT/PROCESS_NAMES/scan(path,t0,t1)/
                    MARKER_STYLE/ASSETS_DIR
    media_provider  KIND (sound|gif|image|music), LABEL, search(q),
                    import_item(item), optional preview(item)
    publisher       LABEL, publish(clip_path, meta), optional
                    list_published(), delete(id)
    theme           data-only css + metadata
"""
import re
import time

_SLUG = re.compile(r"[^a-z0-9_-]")

game_adapters: dict = {}     # slug -> adapter object (module or instance)
adapter_sources: dict = {}   # slug -> plugin id or "core"
media_providers: dict = {}   # provider id -> provider object
publishers: dict = {}        # publisher id -> publisher object
themes: dict = {}            # theme id -> {"id","label","dark","css","plugin"}


def _slug(s) -> str:
    return _SLUG.sub("", str(s or "").lower())[:32]


def reset():
    game_adapters.clear()
    adapter_sources.clear()
    media_providers.clear()
    publishers.clear()
    themes.clear()


def register_game_adapter(obj, source: str = "core"):
    slug = _slug(getattr(obj, "GAME", ""))
    if not slug or not callable(getattr(obj, "parse", None)):
        raise ValueError("game adapter needs GAME and parse(payload, state)")
    if slug in game_adapters and adapter_sources.get(slug) != source:
        print(f"[plugins] {source} overrides {adapter_sources[slug]} adapter for '{slug}'",
              flush=True)
    game_adapters[slug] = obj
    adapter_sources[slug] = source
    procs = getattr(obj, "PROCESS_NAMES", None)
    if procs:
        from clipshelf import game_detect
        game_detect.register_processes({p: slug for p in procs},
                                       label=getattr(obj, "LABEL", ""))
    return slug


def register_media_provider(obj, source: str = "core"):
    pid = _slug(getattr(obj, "ID", "") or getattr(obj, "KIND", ""))
    kind = getattr(obj, "KIND", "")
    if not pid or kind not in ("sound", "gif", "image", "music"):
        raise ValueError("media provider needs ID and KIND in sound|gif|image|music")
    if not callable(getattr(obj, "search", None)) or not callable(getattr(obj, "import_item", None)):
        raise ValueError("media provider needs search(q) and import_item(item)")
    media_providers[pid] = obj
    return pid


def register_publisher(obj, source: str = "core"):
    pid = _slug(getattr(obj, "ID", ""))
    if not pid or not callable(getattr(obj, "publish", None)):
        raise ValueError("publisher needs ID and publish(clip_path, meta)")
    publishers[pid] = obj
    return pid


def register_theme(tid: str, meta: dict, css_path, source: str = "core"):
    tid = _SLUG.sub("", str(tid or "").lower())[:40]
    if not tid or not css_path:
        raise ValueError("theme needs an id and a css file")
    themes[tid] = {"id": tid, "label": str(meta.get("label") or tid)[:60],
                   "dark": bool(meta.get("dark", True)), "css": str(css_path),
                   "plugin": source}
    return tid


def init_core():
    """Built-ins register through the same registries as plugins."""
    import sys
    from clipshelf.gsi_adapters import adapters
    for slug, mod in adapters().items():
        register_game_adapter(mod, source="core")
    from clipshelf import share_client
    register_publisher(share_client.SharePublisher(), source="core")
    from clipshelf import selfhost
    register_publisher(selfhost.SelfhostPublisher(), source="core")
    from clipshelf import providers_builtin
    providers_builtin.register_apikeys_domain()
    providers_builtin.register_all(sys.modules[__name__])
    # built-in themes ride the same registry as plugin themes
    import json as _json
    from clipshelf.config import FRONTEND
    tdir = FRONTEND / "themes"
    for css in sorted(tdir.glob("*.css")) if tdir.is_dir() else []:
        meta = {}
        mp = css.with_suffix(".json")
        if mp.exists():
            try:
                meta = _json.loads(mp.read_text(encoding="utf-8"))
            except Exception:
                meta = {}
        register_theme(css.stem, meta, css, source="core")


def provider_ready(p) -> bool:
    fn = getattr(p, "ready", None)
    try:
        return bool(fn()) if callable(fn) else True
    except Exception:
        return False


def marker_styles() -> dict:
    """Per-game event-type marker hints for the vods timeline:
    {game: {event_type: {color?, icon?, label?}}}"""
    out = {}
    for slug, mod in game_adapters.items():
        ms = getattr(mod, "MARKER_STYLE", None)
        if isinstance(ms, dict):
            out[slug] = ms
    return out


def ui_payload() -> dict:
    """Everything the frontend needs to render plugin-provided surfaces —
    fetched once by core.js at boot."""
    return {
        "games": [{"game": s, "label": getattr(m, "LABEL", s),
                   "source": adapter_sources.get(s, "core")}
                  for s, m in sorted(game_adapters.items())],
        "providers": [{"id": pid, "kind": p.KIND,
                       "label": getattr(p, "LABEL", pid),
                       "ready": provider_ready(p),
                       "hints": getattr(p, "HINTS", {})}
                      for pid, p in sorted(media_providers.items())],
        "publishers": [{"id": pid, "label": getattr(p, "LABEL", pid)}
                       for pid, p in sorted(publishers.items())],
        "themes": [{k: t[k] for k in ("id", "label", "dark")}
                   for t in themes.values()],
        "markers": marker_styles(),
        "ts": int(time.time()),
    }
