"""GameState:一局狼人杀的全部状态(隐藏身份在内)。

必须可**无损 JSON 序列化/反序列化** —— 既是 SQLite 存档(snapshot blob)地基,也是续局地基。
GameState 只装数据与纯逻辑助手;凡需要"角色阵营/归边"的判断都交给引擎(它持有 Role 注册表)。
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Optional

# 阶段常量(状态机相位;引擎按它推进)
LOBBY = "lobby"
DEAL = "deal"
SHERIFF_RUN = "sheriff_run"
SHERIFF_SPEECH = "sheriff_speech"
SHERIFF_VOTE = "sheriff_vote"
NIGHT = "night"
DAY_ANNOUNCE = "day_announce"
DAY_SPEECH = "day_speech"
DAY_VOTE = "day_vote"
DAY_EXILE = "day_exile"
END = "end"

# 死因(对外只暴露"谁出局",死因仅引擎/特定角色可见 —— 信息差核心)
CAUSE_KNIFE = "knife"
CAUSE_POISON = "poison"
CAUSE_EXILE = "exile"
CAUSE_SHOT = "shot"
CAUSE_EXPLODE = "explode"          # 狼人自爆出局(白天掀桌亮身份,中止当天发言/投票直接入夜)


@dataclass
class GameState:
    board_id: str
    seats: int
    roles: dict[int, str]                                  # seat -> role_id(隐藏身份)
    alive: dict[int, bool] = field(default_factory=dict)
    day: int = 0                                           # 0=未开局;1=第一白天(第一夜在其之前)
    phase: str = LOBBY
    sheriff: Optional[int] = None                          # 持警徽的座位
    transcript: list[dict] = field(default_factory=list)   # 公共发言/事件流 {day,phase,type,seat,text}
    deaths: list[dict] = field(default_factory=list)       # {seat, day, cause}
    seer_checks: dict[int, list] = field(default_factory=dict)   # seer_seat -> [{target,result}]
    witch_potions: dict[int, dict] = field(default_factory=dict) # witch_seat -> {antidote,poison}
    last_knife: Optional[int] = None                       # 本夜狼刀目标(供女巫看刀型)
    votes: dict[int, int] = field(default_factory=dict)    # 本轮:voter -> target(瞬时)
    votes_log: list[dict] = field(default_factory=list)    # 公开历轮投票 {day, kind:exile|sheriff, voter, target}
    winner: Optional[str] = None                           # "wolf" | "good" | None
    seed: int = 0

    def __post_init__(self):
        if not self.alive:
            self.alive = {s: True for s in self.roles}

    # —— 纯逻辑助手 ——
    def alive_seats(self) -> list[int]:
        return sorted(s for s, a in self.alive.items() if a)

    def is_alive(self, seat: int) -> bool:
        return bool(self.alive.get(seat))

    def role_of(self, seat: int) -> str:
        return self.roles[seat]

    def kill(self, seat: int, cause: str) -> None:
        """记一笔死亡(幂等:已死不重复记)。死因入档但不对外公开。"""
        if not self.alive.get(seat):
            return
        self.alive[seat] = False
        self.deaths.append({"seat": seat, "day": self.day, "cause": cause})

    def record(self, etype: str, text: str, seat: Optional[int] = None, channel: str = "public") -> None:
        """记一条事件。channel='public' 人人可见;'wolf' 仅狼队可见(私聊频道)。
        可见性由 perception 按频道裁剪 —— 私聊内容绝不进他人视野。"""
        self.transcript.append({"day": self.day, "phase": self.phase, "channel": channel,
                                "type": etype, "seat": seat, "text": text})

    # —— 序列化(SQLite 存档 / 续局)——
    def to_dict(self) -> dict:
        """JSON-able:int 键转成 str(JSON 对象键必须是字符串)。"""
        ik = lambda d: {str(k): v for k, v in d.items()}
        return {
            "board_id": self.board_id, "seats": self.seats,
            "roles": ik(self.roles), "alive": ik(self.alive),
            "day": self.day, "phase": self.phase, "sheriff": self.sheriff,
            "transcript": self.transcript, "deaths": self.deaths,
            "seer_checks": ik(self.seer_checks), "witch_potions": ik(self.witch_potions),
            "last_knife": self.last_knife, "votes": ik(self.votes),
            "votes_log": self.votes_log, "winner": self.winner, "seed": self.seed,
        }

    @classmethod
    def from_dict(cls, d: dict) -> "GameState":
        ik = lambda m: {int(k): v for k, v in (m or {}).items()}
        gs = cls(
            board_id=d["board_id"], seats=int(d["seats"]),
            roles=ik(d["roles"]), alive=ik(d.get("alive")),
            day=int(d.get("day", 0)), phase=d.get("phase", LOBBY), sheriff=d.get("sheriff"),
            transcript=list(d.get("transcript") or []), deaths=list(d.get("deaths") or []),
            seer_checks=ik(d.get("seer_checks")), witch_potions=ik(d.get("witch_potions")),
            last_knife=d.get("last_knife"), votes=ik(d.get("votes")),
            votes_log=list(d.get("votes_log") or []),
            winner=d.get("winner"), seed=int(d.get("seed", 0)))
        return gs
