"""结构化对局板:一个 agent 的"记忆主干"。

两层:
- 客观层 = PlayerView(引擎合法下发,信息差已兜住);
- 主观层 = 这个 agent 对其他人的判断(自报身份/我判定好狼/置信/站边),由 LLM 每轮发言顺带产出的 reads[] 累积更新。
render() 把两层拼成喂进每次决策 prompt 的"如实状态" —— 这就是"靠丰富记忆+如实状态涌现智能"在狼人杀里的落点。
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Optional

from genesis.werewolf.perception import PlayerView

JUDGMENTS = {"good", "wolf", "unknown"}


@dataclass
class Belief:
    seat: int
    claim: str = ""              # 该玩家自报/被推断的身份
    judgment: str = "unknown"    # good | wolf | unknown
    confidence: float = 0.0      # 0..1
    note: str = ""               # 一句话依据


class BoardMemory:
    """某座位 agent 维护的主观判断 + 把客观+主观渲染成 prompt 文本。"""

    def __init__(self, seat: int) -> None:
        self.seat = seat
        self.beliefs: dict[int, Belief] = {}
        self.notes: str = ""             # 滚动"局记":每天收尾 LLM 压缩更新(有界),常驻 prompt

    def apply_reads(self, reads) -> None:
        """用一次发言产出的 reads[] 更新对各人的判断(dict 或带属性的对象皆可)。"""
        for r in reads or []:
            g = (lambda k, d=None: r.get(k, d)) if isinstance(r, dict) else (lambda k, d=None: getattr(r, k, d))
            seat = g("seat")
            if seat is None or seat == self.seat:
                continue
            b = self.beliefs.setdefault(int(seat), Belief(seat=int(seat)))
            if g("claim"):
                b.claim = str(g("claim"))[:20]
            j = g("judgment")
            if j in JUDGMENTS:
                b.judgment = j
            c = g("confidence")
            if c is not None:
                try:
                    b.confidence = max(0.0, min(1.0, float(c)))
                except (TypeError, ValueError):
                    pass
            if g("note"):
                b.note = str(g("note"))[:30]

    def apply_digest(self, out) -> None:
        """每日复盘:用 LLM 压缩结果更新滚动局记 + 把最疑/最信落进结构化判断(dict 或对象皆可)。"""
        g = (lambda k, d=None: out.get(k, d)) if isinstance(out, dict) else (lambda k, d=None: getattr(out, k, d))
        notes = g("notes")
        if notes:
            self.notes = str(notes)[:600]                 # 局记有界:每天在旧局记上重压,不无限膨胀
        ps = g("prime_suspect")
        if ps is not None and ps != self.seat:
            self.apply_reads([{"seat": ps, "judgment": "wolf", "confidence": 0.6, "note": "今日复盘最疑"}])
        mt = g("most_trusted")
        if mt is not None and mt != self.seat:
            self.apply_reads([{"seat": mt, "judgment": "good", "confidence": 0.6, "note": "今日复盘最信"}])

    def render(self, v: PlayerView, transcript_tail: int = 10) -> str:
        """对局板文本:你是谁 → 场面 → 你的私有情报 → 你对各人的判断 → 近期发言 → 历轮投票。"""
        lines = [f"【你】{v.seat}号 · {v.role_name}({'狼人阵营' if v.team == 'wolf' else '好人阵营'})"]
        lines.append("【存活】" + "、".join(f"{s}号" for s in v.alive) +
                     ("  【已出局】" + "、".join(f"{d['seat']}号(第{d['day']}天)" for d in v.dead) if v.dead else ""))
        if v.sheriff:
            lines.append(f"【警长】{v.sheriff}号(1.5 票)")
        if self.notes:                                  # 滚动局记(往日复盘的压缩记忆)置顶
            lines.append("【你的局记(往日复盘)】" + self.notes)
        if v.is_ghost and v.all_roles:                  # 出局者:上帝视角,全场身份揭晓(活人看不到)
            roster = "  ".join(f"{s}号={n}" + ("" if s in v.alive else "†") for s, n in v.all_roles.items())
            lines.append("【👻 上帝视角 · 你已出局,只能旁观(以下对活人不可见)】全场身份:" + roster)

        priv = []                                   # 私有情报:按 view 里有什么就报什么(信息差已在上游兜住)
        if v.teammates is not None:
            mates = [s for s in v.teammates if s != v.seat]
            priv.append("你的狼队友:" + ("、".join(f"{s}号" for s in mates) if mates else "(只剩你)"))
        if v.check_results is not None:
            priv.append("你的查验史:" + ("；".join(
                f"{c['target']}号={'查杀' if c['result'] == 'wolf' else '金水'}" for c in v.check_results) or "(还没验)"))
        if v.knife_target is not None:
            priv.append(f"昨夜刀型:{v.knife_target}号倒在刀下")
        if v.potions is not None:
            priv.append(f"你的药:解药{'✓' if v.potions.get('antidote') else '✗'} 毒药{'✓' if v.potions.get('poison') else '✗'}")
        if priv:
            lines.append("【你的私有情报】" + "；".join(priv))

        if self.beliefs:
            lines.append("【你对各人的判断】")
            for s in sorted(self.beliefs):
                b = self.beliefs[s]
                if s not in v.alive:
                    continue
                tag = {"good": "好人", "wolf": "狼", "unknown": "存疑"}[b.judgment]
                lines.append(f"  {s}号" + (f" 自报{b.claim}" if b.claim else "") +
                             f" | 我看:{tag}({b.confidence:.0%})" + (f" {b.note}" if b.note else ""))

        tail = v.transcript[-transcript_tail:]
        if tail:
            lines.append("【近期公开发言/事件】")
            for e in tail:
                who = f"{e['seat']}号" if e.get("seat") else "法官"
                ch = "[狼私聊]" if e.get("channel") == "wolf" else ""   # 狼/上帝视角才看得到的私聊,显式标注
                lines.append(f"  {ch}[{who}] {e['text']}")
        if v.votes_log:
            byday: dict = {}
            for r in v.votes_log:
                byday.setdefault((r["day"], r["kind"]), []).append(f"{r['voter']}→{r['target']}")
            lines.append("【历轮投票】" + "  ".join(
                f"第{d}天{'警选' if k == 'sheriff' else '放逐'}: " + " ".join(v2) for (d, k), v2 in byday.items()))
        return "\n".join(lines)
