"""小意 Tri-Review Panel — 三思状态机（v0.5 骨架，无 LLM）。"""

from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any, Literal
from uuid import uuid4

from niannian.soul.subject_store import SubjectStateStore

ThoughtStatus = Literal["pending", "abandoned", "adopted"]
ThoughtKind = Literal["identity_draft", "narrative_line", "self_claim", "open_thread"]
TriggerKind = Literal["dream", "cultural_ingest", "core_correct", "manual"]


@dataclass(frozen=True)
class RoundResult:
    thought_id: str
    round: int
    composite_score: float
    status: ThoughtStatus
    consecutive_low: int
    message: str


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def _composite_score(seat_b: float, seat_c: float) -> float:
    return round(0.55 * seat_b + 0.45 * seat_c, 2)


class ReflectionPanel:
    """跨 trigger 三思 + 连续两低分遗弃。"""

    def __init__(self, store: SubjectStateStore) -> None:
        self._store = store

    def _load(self) -> dict[str, Any]:
        return self._store.load_reflection()

    def _save(self, data: dict[str, Any]) -> None:
        path = self._store.subject_root / "reflection.yaml"
        import yaml

        path.write_text(yaml.safe_dump(data, allow_unicode=True, sort_keys=False), encoding="utf-8")

    def submit_proposal(
        self,
        text: str,
        *,
        kind: ThoughtKind = "narrative_line",
        atom_ids: list[str] | None = None,
    ) -> str:
        data = self._load()
        thought_id = f"tp_{uuid4().hex[:8]}"
        pending = data.setdefault("pending_thoughts", [])
        pending.append(
            {
                "id": thought_id,
                "source": "xiao_wu",
                "kind": kind,
                "text": text,
                "atom_ids": atom_ids or [],
                "status": "pending",
                "rounds_completed": 0,
                "consecutive_low": 0,
                "last_trigger": None,
                "round_scores": [],
                "created_at": _now_iso(),
            }
        )
        self._save(data)
        return thought_id

    def _find_thought(self, data: dict[str, Any], thought_id: str) -> dict[str, Any] | None:
        for item in data.get("pending_thoughts") or []:
            if isinstance(item, dict) and item.get("id") == thought_id:
                return item
        return None

    def run_round(
        self,
        trigger: TriggerKind,
        thought_id: str,
        *,
        seat_a_pass: bool = True,
        seat_b_score: float = 70.0,
        seat_c_score: float = 70.0,
    ) -> RoundResult:
        data = self._load()
        settings = data.get("settings") or {}
        adopt_threshold = float(settings.get("adopt_threshold", 75))
        discard_threshold = float(settings.get("discard_threshold", 50))
        required_rounds = int(settings.get("required_rounds", 3))
        consecutive_limit = int(settings.get("consecutive_low_to_abandon", 2))
        sl1_threshold = float(settings.get("sl1_adopt_threshold", 80))

        thought = self._find_thought(data, thought_id)
        if thought is None:
            raise KeyError(f"unknown thought_id: {thought_id}")
        if thought.get("status") != "pending":
            return RoundResult(
                thought_id=thought_id,
                round=int(thought.get("rounds_completed") or 0),
                composite_score=0.0,
                status=thought["status"],
                consecutive_low=int(thought.get("consecutive_low") or 0),
                message="already finalized",
            )

        if thought.get("last_trigger") == trigger:
            return RoundResult(
                thought_id=thought_id,
                round=int(thought.get("rounds_completed") or 0),
                composite_score=0.0,
                status="pending",
                consecutive_low=int(thought.get("consecutive_low") or 0),
                message="same trigger deduped",
            )

        if not seat_a_pass:
            composite = 0.0
        else:
            composite = _composite_score(seat_b_score, seat_c_score)

        low = composite < discard_threshold or not seat_a_pass
        consecutive = int(thought.get("consecutive_low") or 0)
        if low:
            consecutive += 1
        else:
            consecutive = 0

        thought["consecutive_low"] = consecutive
        thought["last_trigger"] = trigger
        round_num = int(thought.get("rounds_completed") or 0) + 1
        thought["rounds_completed"] = round_num
        scores: list = list(thought.get("round_scores") or [])
        while len(scores) < round_num:
            scores.append(None)
        scores[round_num - 1] = composite
        thought["round_scores"] = scores

        reviews = data.setdefault("reviews", [])
        for seat, score in (("skeptic", seat_b_score), ("integrator", seat_c_score)):
            reviews.append(
                {
                    "thought_id": thought_id,
                    "round": round_num,
                    "trigger": trigger,
                    "seat": seat,
                    "score": score,
                    "at": _now_iso(),
                }
            )
        reviews.append(
            {
                "thought_id": thought_id,
                "round": round_num,
                "trigger": trigger,
                "seat": "rules",
                "score": 100 if seat_a_pass else 0,
                "verdict": "pass" if seat_a_pass else "hard_reject",
                "at": _now_iso(),
            }
        )

        status: ThoughtStatus = "pending"
        message = "hold"

        if consecutive >= consecutive_limit:
            thought["status"] = "abandoned"
            status = "abandoned"
            message = "two_low_strikes"
        elif round_num >= required_rounds:
            threshold = sl1_threshold if thought.get("kind") == "identity_draft" else adopt_threshold
            all_scores = [s for s in scores if isinstance(s, (int, float))]
            if len(all_scores) >= required_rounds and all(s >= threshold for s in all_scores[-required_rounds:]):
                thought["status"] = "adopted"
                status = "adopted"
                message = "tri_review_adopted"
            else:
                thought["status"] = "abandoned"
                status = "abandoned"
                message = "three_rounds_not_qualified"

        self._save(data)
        return RoundResult(
            thought_id=thought_id,
            round=round_num,
            composite_score=composite,
            status=status,
            consecutive_low=consecutive,
            message=message,
        )
