"""小枢 PathwayResolver — Fast / Warm / Cold 短路。"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Literal

from niannian.cognitive.judgment import TurnPolicy, classify_turn
from niannian.cognitive.pathway_registry import PathwayRegistry
from niannian.cognitive.plane_registry import resolve_plane_pack
from niannian.cognitive.planes import PlanePack, Regime
from niannian.llm.router import LLMRouter

PathwayTier = Literal["fast", "warm", "cold"]


@dataclass(frozen=True)
class ResolvedPathway:
    tier: PathwayTier
    policy: TurnPolicy
    persona_preset: str | None = None
    route_id: str | None = None


def _default_planes(regime: Regime, config: dict[str, Any] | None = None) -> PlanePack:
    return resolve_plane_pack(regime, config, pack_name=regime)


def _match_route(
    routes: list[dict[str, Any]],
    *,
    regime: Regime,
    object_id: str | None,
) -> dict[str, Any] | None:
    for route in routes:
        if not isinstance(route, dict):
            continue
        match = route.get("match")
        if not isinstance(match, dict):
            continue
        if match.get("regime") != regime:
            continue
        want_object = match.get("object_id")
        if want_object is None and object_id is not None:
            continue
        if want_object is not None and want_object != object_id:
            continue
        return route
    return None


def _planes_from_route(route: dict[str, Any], regime: Regime, config: dict[str, Any] | None) -> PlanePack:
    planes_cfg = route.get("planes")
    if not isinstance(planes_cfg, dict):
        return _default_planes(regime, config)
    skip_raw = planes_cfg.get("skip") or []
    skip = frozenset(str(x) for x in skip_raw if isinstance(x, str))
    active_raw = planes_cfg.get("active")
    if isinstance(active_raw, list) and active_raw:
        active = frozenset(str(x) for x in active_raw if isinstance(x, str))
    else:
        active = _default_planes(regime, config).active
    return PlanePack(regime=regime, active=active, skip=skip)


def _persona_from_route(route: dict[str, Any]) -> str | None:
    preset = route.get("persona_preset")
    if preset is None or (isinstance(preset, str) and not preset.strip()):
        return None
    return str(preset).strip()


def _persona_preset_from_meta(meta: dict[str, Any]) -> str | None:
    override = meta.get("persona_override")
    if isinstance(override, dict) and override.get("preset"):
        return str(override["preset"])
    sticky = meta.get("pathway_sticky")
    if isinstance(sticky, dict) and sticky.get("persona_preset"):
        return str(sticky["persona_preset"])
    return None


class PathwayResolver:
    """解析 Chat 热路径 Pack — Fast registry 命中 / Warm sticky / Cold 全 classify。"""

    def __init__(self, config: dict[str, Any] | None = None) -> None:
        self._config = config or {}
        self._registry = PathwayRegistry(self._config)
        self._routes = self._registry.list_routes() if self._registry.exists() else []

    def resolve(
        self,
        text: str,
        *,
        history_len: int = 0,
        session_meta: dict[str, Any] | None = None,
        recent_user_texts: list[str] | None = None,
        chat_budget: dict[str, Any] | None = None,
        llm: LLMRouter | None = None,
    ) -> ResolvedPathway:
        meta = dict(session_meta or {})
        object_id = meta.get("current_object_id")
        if object_id is not None:
            object_id = str(object_id)

        sticky = meta.get("pathway_sticky")
        if isinstance(sticky, dict) and sticky.get("regime") in ("companion", "work", "hybrid", "education"):
            regime: Regime = sticky["regime"]  # type: ignore[assignment]
            skip = frozenset(str(x) for x in (sticky.get("skip") or []) if isinstance(x, str))
            active_raw = sticky.get("active")
            if isinstance(active_raw, list) and active_raw:
                active = frozenset(str(x) for x in active_raw if isinstance(x, str))
            else:
                active = _default_planes(regime, self._config).active
            planes = PlanePack(regime=regime, active=active, skip=skip)
            policy = TurnPolicy(
                regime=regime,
                intent=meta.get("intent") or "chat",  # type: ignore[arg-type]
                planes=planes,
                force_ingest=bool(meta.get("force_ingest")),
                confidence=0.9,
            )
            persona = sticky.get("persona_preset")
            return ResolvedPathway(
                tier="warm",
                policy=policy,
                persona_preset=str(persona) if persona else None,
                route_id="session_sticky",
            )

        cold = classify_turn(
            text,
            history_len=history_len,
            session_meta=meta,
            config=self._config,
            recent_user_texts=recent_user_texts,
            chat_budget=chat_budget,
            llm=llm,
        )
        if meta.get("regime_lock") in ("companion", "work", "hybrid", "education"):
            persona = _persona_preset_from_meta(meta)
            return ResolvedPathway(
                tier="warm",
                policy=cold,
                persona_preset=str(persona) if persona else None,
                route_id="regime_lock",
            )

        route = _match_route(self._routes, regime=cold.regime, object_id=object_id)
        if route is not None and object_id is None:
            planes = _planes_from_route(route, cold.regime, self._config)
            policy = TurnPolicy(
                regime=cold.regime,
                intent=cold.intent,
                planes=planes,
                force_ingest=cold.force_ingest,
                confidence=cold.confidence,
                regime_source=cold.regime_source,
            )
            persona = _persona_from_route(route)
            return ResolvedPathway(
                tier="fast",
                policy=policy,
                persona_preset=persona,
                route_id=str(route.get("id")) if route.get("id") else None,
            )

        return ResolvedPathway(tier="cold", policy=cold, persona_preset=None, route_id=None)


def resolve_turn_policy(
    text: str,
    *,
    history_len: int = 0,
    session_meta: dict[str, Any] | None = None,
    config: dict[str, Any] | None = None,
    recent_user_texts: list[str] | None = None,
    chat_budget: dict[str, Any] | None = None,
    llm: LLMRouter | None = None,
) -> ResolvedPathway:
    return PathwayResolver(config).resolve(
        text,
        history_len=history_len,
        session_meta=session_meta,
        recent_user_texts=recent_user_texts,
        chat_budget=chat_budget,
        llm=llm,
    )


def update_pathway_sticky(
    session_meta: dict[str, Any],
    policy: TurnPolicy,
    *,
    config: dict[str, Any] | None = None,
    persona_preset: str | None = None,
) -> dict[str, Any]:
    """同 session 连续同配置 → pathway_sticky（hits 从 registry 读取）。"""
    meta = dict(session_meta)
    sticky_cfg = PathwayRegistry(config or {}).sticky_config()
    if not sticky_cfg.get("enabled", True):
        return meta
    hits_needed = int(sticky_cfg.get("hits_to_sticky") or 3)
    regime_hits = sticky_cfg.get("regime_hits")
    if isinstance(regime_hits, dict) and policy.regime in regime_hits:
        hits_needed = int(regime_hits[policy.regime])
    elif policy.regime == "education":
        hits_needed = 2
    signature = (
        policy.regime,
        tuple(sorted(policy.planes.active)),
        tuple(sorted(policy.planes.skip)),
        persona_preset,
    )
    last_sig = meta.get("pathway_sticky_signature")
    if last_sig != signature:
        meta["pathway_sticky_hits"] = 1
        meta["pathway_sticky_signature"] = signature
    else:
        meta["pathway_sticky_hits"] = int(meta.get("pathway_sticky_hits") or 0) + 1
    if int(meta["pathway_sticky_hits"]) >= hits_needed:
        sticky: dict[str, Any] = {
            "regime": policy.regime,
            "skip": sorted(policy.planes.skip),
            "active": sorted(policy.planes.active),
        }
        if persona_preset:
            sticky["persona_preset"] = persona_preset
        meta["pathway_sticky"] = sticky
    return meta
