"""Plugin registry for the strict ResultTable API."""
from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Callable, Dict, Iterable, List, Optional, Union

from SYS.result_table_api import ColumnSpec, ProviderAdapter, ResultModel, ResultTable, ensure_result_model


ColumnFactory = Callable[[List[ResultModel]], List[ColumnSpec]]
SelectionFn = Callable[[ResultModel], List[str]]


@dataclass
class Plugin:
    name: str
    adapter: ProviderAdapter
    # columns can be a static list or a factory that derives columns from sample rows
    columns: Union[List[ColumnSpec], ColumnFactory]
    selection_fn: SelectionFn
    metadata: Optional[Dict[str, Any]] = None

    def get_columns(self, rows: Optional[Iterable[ResultModel]] = None) -> List[ColumnSpec]:
        if self.columns is None:
            raise ValueError(f"plugin '{self.name}' must define columns")

        if callable(self.columns):
            rows_list = list(rows) if rows is not None else []
            cols = list(self.columns(rows_list))
        else:
            cols = list(self.columns)

        if not cols:
            raise ValueError(f"plugin '{self.name}' produced no columns")

        return cols

    def selection_args(self, row: ResultModel) -> List[str]:
        if not callable(self.selection_fn):
            raise ValueError(f"plugin '{self.name}' must define a selection function")

        sel = list(self.selection_fn(ensure_result_model(row)))
        return sel

    def build_table(self, items: Iterable[Any]) -> ResultTable:
        """Materialize adapter output into a ResultTable (strict, no legacy types)."""

        try:
            rows = [ensure_result_model(r) for r in self.adapter(items)]
        except Exception as exc:
            raise RuntimeError(f"plugin '{self.name}' adapter failed") from exc

        cols = self.get_columns(rows)
        return ResultTable(plugin=self.name, rows=rows, columns=cols, meta=self.metadata or {})

    def serialize_row(self, row: ResultModel) -> Dict[str, Any]:
        r = ensure_result_model(row)
        metadata = r.metadata or {}
        out: Dict[str, Any] = {
            "title": r.title,
            "path": r.path,
            "ext": r.ext,
            "size_bytes": r.size_bytes,
            "metadata": metadata,
            "source": r.source or self.name,
            "_selection_args": self.selection_args(r),
        }
        selection_action = metadata.get("_selection_action") or metadata.get("selection_action")
        if selection_action:
            out["_selection_action"] = [
                str(x) for x in selection_action if x is not None
            ]
        return out

    def serialize_rows(self, rows: Iterable[ResultModel]) -> List[Dict[str, Any]]:
        return [self.serialize_row(r) for r in rows]


_PLUGINS: Dict[str, Plugin] = {}


def register_plugin(
    name: str,
    adapter: ProviderAdapter,
    *,
    columns: Union[List[ColumnSpec], ColumnFactory],
    selection_fn: SelectionFn,
    metadata: Optional[Dict[str, Any]] = None,
) -> Plugin:
    name = str(name or "").strip().lower()
    if not name:
        raise ValueError("plugin name required")
    if name in _PLUGINS:
        raise ValueError(f"plugin already registered: {name}")
    if columns is None:
        raise ValueError("plugin registration requires columns")
    if selection_fn is None:
        raise ValueError("plugin registration requires selection_fn")
    plugin = Plugin(name=name, adapter=adapter, columns=columns, selection_fn=selection_fn, metadata=metadata)
    _PLUGINS[name] = plugin
    return plugin


def get_plugin(name: str) -> Plugin:
    normalized = str(name or "").lower()
    if normalized not in _PLUGINS:
        raise KeyError(f"plugin not registered: {name}")
    return _PLUGINS[normalized]


def list_plugins() -> List[str]:
    return list(_PLUGINS.keys())
