"""Chunker registry with split/reconstruct contracts.

Chunkers are used by gnos core (add/attest/materialize/derive) and may also be
provided by external plugins via Python entry points.
"""

from __future__ import annotations

from typing import Any, Dict, Iterable, List, Tuple

from .api import Chunker
from .core_chunkers import FramedSequentialV1, IdentityV1, SequentialV1


_BUILTIN_CHUNKERS: Dict[str, Chunker] = {
    "identity-v1": IdentityV1(),
    "sequential-v1": SequentialV1(),
    "framed-sequential-v1": FramedSequentialV1(),
}

_CHUNKERS: Dict[str, Chunker] = dict(_BUILTIN_CHUNKERS)
_SOURCES: Dict[str, str] = {name: "core" for name in _BUILTIN_CHUNKERS}
_PLUGINS_LOADED = False


def _iter_entry_points() -> Iterable[Any]:
    """Return entry points for external chunkers.

    Separated for testability.
    """
    from importlib import metadata

    eps = metadata.entry_points()
    # Python 3.10+ supports select(); older versions return dict-like.
    if hasattr(eps, "select"):
        return eps.select(group="gnos.chunkers")
    return eps.get("gnos.chunkers", [])


def _load_plugins() -> None:
    global _PLUGINS_LOADED
    if _PLUGINS_LOADED:
        return
    _PLUGINS_LOADED = True

    for ep in _iter_entry_points():
        try:
            obj = ep.load()
        except Exception:
            continue
        try:
            chunker = obj() if callable(obj) and not isinstance(obj, Chunker) else obj
        except Exception:
            continue
        if not isinstance(chunker, Chunker):
            continue

        if chunker.name in _CHUNKERS:
            # Core wins; ignore name collisions deterministically.
            continue
        _CHUNKERS[chunker.name] = chunker
        dist = getattr(ep, "dist", None)
        if dist is not None and getattr(dist, "name", None):
            _SOURCES[chunker.name] = f"entrypoint:{dist.name}"
        else:
            _SOURCES[chunker.name] = "entrypoint"


def get_chunker(name: str) -> Chunker:
    """Return a registered chunker or raise if unknown."""
    _load_plugins()
    if name not in _CHUNKERS:
        raise ValueError(f"unknown chunking method: {name}")
    return _CHUNKERS[name]


def list_chunkers() -> List[Tuple[str, str]]:
    """List available chunkers as (name, source) pairs."""
    _load_plugins()
    return sorted(((name, _SOURCES.get(name, "unknown")) for name in _CHUNKERS), key=lambda t: t[0])


# Backwards-compatible aliases for internal callers/tests (v1).
get_method = get_chunker
list_methods = list_chunkers
