"""P3 判断层 — TurnPolicy 与 regime。"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Any, Literal

from niannian.llm.router import LLMRouter
from niannian.token.budget import chat_llm_allows

from niannian.chat.intents import Intent, IntentResult, classify_intent
from niannian.cognitive.planes import PlanePack, Regime
from niannian.cognitive.plane_registry import resolve_plane_pack

_WORK_HINT = re.compile(
    r"code|代码|bug|pytest|docker|api|mcp|fragment|ingest|架构|重构|"
    r"novelstudio|cursor|skill|函数|class |def |git |pr |spec",
    re.I,
)
_EMOTION_HINT = re.compile(r"累|开心|难过|想你|抱抱|晚安|早安|心情|孤独")
_EDUCATION_HINT = re.compile(
    r"为什么|怎么理解|讲讲|解释一下|是什么|教程|学习|背诵|历史|典故|文言文|公式推导|知识点"
)

_REGIME_LOCKS: frozenset[str] = frozenset({"companion", "work", "hybrid", "education"})
_REGIME_HINTS: frozenset[str] = frozenset({"companion", "work", "hybrid", "education"})


@dataclass(frozen=True)
class TurnPolicy:
    regime: Regime
    intent: Intent
    planes: PlanePack
    force_ingest: bool = False
    confidence: float = 1.0
    values_gate: dict[str, Any] | None = None
    regime_source: Literal["llm", "rules"] | None = None

    def to_dict(self) -> dict[str, Any]:
        out: dict[str, Any] = {
            "regime": self.regime,
            "intent": self.intent,
            "planes": sorted(self.planes.active),
            "skip": sorted(self.planes.skip),
            "force_ingest": self.force_ingest,
            "confidence": self.confidence,
        }
        if self.regime_source is not None:
            out["regime_source"] = self.regime_source
        if self.values_gate is not None:
            out["values_gate"] = self.values_gate
        return out


def _detect_regime(text: str, *, session_meta: dict[str, Any] | None) -> Regime:
    meta = session_meta or {}
    locked = meta.get("regime_lock")
    if locked in _REGIME_LOCKS:
        return locked  # type: ignore[return-value]

    stripped = text.strip()
    work = bool(_WORK_HINT.search(stripped))
    emotion = bool(_EMOTION_HINT.search(stripped))
    education = bool(_EDUCATION_HINT.search(stripped))
    if work and emotion:
        return "hybrid"
    if work:
        return "work"
    if education:
        return "education"
    if meta.get("regime_hint") in _REGIME_HINTS:
        return meta["regime_hint"]  # type: ignore[return-value]
    return "companion"


def _planes_for(regime: Regime, intent: Intent, config: dict[str, Any] | None = None) -> PlanePack:
    if intent == "device":
        base = frozenset({"P1", "P2", "P3", "P6", "P12", "P13"})
        return PlanePack(regime=regime, active=base)
    if intent == "memory_query":
        base = frozenset({"P1", "P2", "P3", "P6", "P9", "P13", "P14"})
        return PlanePack(regime=regime, active=base, skip=frozenset({"P10"}))
    return resolve_plane_pack(regime, config, pack_name=regime)


def _combined_regime_text(text: str, recent_user_texts: list[str] | None) -> str:
    if recent_user_texts:
        return "\n".join(recent_user_texts)
    return text


def _resolve_regime(
    combined: str,
    *,
    session_meta: dict[str, Any] | None,
    config: dict[str, Any] | None,
    chat_budget: dict[str, Any] | None,
    llm: LLMRouter | None,
) -> tuple[Regime, Literal["llm", "rules"]]:
    meta = session_meta or {}
    if meta.get("regime_lock") in _REGIME_LOCKS:
        return _detect_regime(combined, session_meta=session_meta), "rules"

    if (
        chat_budget is not None
        and llm is not None
        and chat_llm_allows(chat_budget, "auxiliary")
        and llm.is_module_configured("left")
    ):
        from niannian.cognitive.regime_llm import detect_regime_llm

        llm_result = detect_regime_llm(combined, config=config or {}, llm=llm)
        if llm_result is not None:
            regime, _confidence = llm_result
            return regime, "llm"

    return _detect_regime(combined, session_meta=session_meta), "rules"


def classify_turn(
    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,
) -> TurnPolicy:
    """TurnPolicy（小鉴）；heavy + left 时 LLM JSON，否则规则 fallback。"""
    return classify_turn_with_values(
        text,
        history_len=history_len,
        session_meta=session_meta,
        values=None,
        config=config,
        recent_user_texts=recent_user_texts,
        chat_budget=chat_budget,
        llm=llm,
    )


def classify_turn_with_values(
    text: str,
    *,
    history_len: int = 0,
    session_meta: dict[str, Any] | None = None,
    values: 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,
) -> TurnPolicy:
    """TurnPolicy + 可选 values_gate（SL5）。"""
    from niannian.soul.assembly import build_values_gate

    intent_result: IntentResult = classify_intent(text, history_len=history_len)
    combined = _combined_regime_text(text, recent_user_texts)
    regime, regime_source = _resolve_regime(
        combined,
        session_meta=session_meta,
        config=config,
        chat_budget=chat_budget,
        llm=llm,
    )
    planes = _planes_for(regime, intent_result.intent, config)
    gate = build_values_gate(values) if values is not None else None
    return TurnPolicy(
        regime=regime,
        intent=intent_result.intent,
        planes=planes,
        force_ingest=intent_result.force_ingest,
        confidence=intent_result.confidence,
        values_gate=gate,
        regime_source=regime_source,
    )
