"""Manifest 数据模型 + 准入校验规则。

三类实体(character/object/place),每个 = 公共 id + sim 字段 + 可选 art(视觉生成规格)。
校验失败抛 ManifestError——"准入"即"过校验才能进世界"。
"""

from __future__ import annotations

from dataclasses import dataclass, field

from genesis.persona.mbti import normalize_mbti
from genesis.persona.persona import Persona
from genesis.world.place import Place, WorldObject

# 允许的取值集合(规范的一部分,扩展时在此登记)
GEOGRAPHIES = {"town", "city", "village", "seaside", "snow_mountain", "forest", "plaza", "indoor"}
VIEWS = {"side", "low top-down", "high top-down"}
ANIMATIONS = {"idle", "walk", "run", "sit", "talk", "work"}


class ManifestError(ValueError):
    """准入校验失败。"""


def _require(cond: bool, msg: str) -> None:
    if not cond:
        raise ManifestError(msg)


@dataclass
class ArtSpec:
    """素材生成规格(PixelLab 参数)。sprite_path 在生成后回填。"""

    description: str  # 英文 prompt,准入要求非空
    size: int = 64
    view: str = "side"
    directions: int = 4  # 角色行走方向数(4/8)
    animations: list[str] = field(default_factory=lambda: ["idle"])
    sprite_path: str | None = None

    def validate(self, *, is_character: bool) -> None:
        _require(bool(self.description and self.description.strip()), "art.description 不能为空")
        _require(self.size > 0, f"art.size 必须 >0,得到 {self.size}")
        _require(self.view in VIEWS, f"art.view 非法:{self.view}(允许 {VIEWS})")
        if is_character:
            _require(self.directions in (4, 8), f"art.directions 必须是 4 或 8,得到 {self.directions}")
            bad = set(self.animations) - ANIMATIONS
            _require(not bad, f"art.animations 含未知项 {bad}(允许 {ANIMATIONS})")

    @classmethod
    def from_dict(cls, data: dict | None) -> "ArtSpec | None":
        if not data:
            return None
        known = {f for f in cls.__dataclass_fields__}
        return cls(**{k: v for k, v in data.items() if k in known})


@dataclass
class CharacterManifest:
    kind = "character"
    id: str
    name: str
    age: int
    mbti: str
    occupation: str
    start_location: str  # place id
    gender: str = "unspecified"
    backstory: str = ""
    hobbies: list[str] = field(default_factory=list)
    relationships: dict[str, str] = field(default_factory=dict)
    secret: str = ""  # 隐藏秘密(只该 agent 自己知道)
    role: str = ""  # 隐藏身份(如 凶手),只注入自己的 prompt
    drive: str = ""  # 终极目的+赌注:一句话锚点,永置 prompt 顶部(任务清单只是达成它的手段)
    knows: str = ""  # 该角色"恰好知道"的可行动情报/机关线索(只进本人 prompt;给对的人对的信息→更有行动力)
    art: ArtSpec | None = None

    def validate(self) -> None:
        _require(bool(self.id and self.id.strip()), "character.id 不能为空")
        _require(bool(self.name and self.name.strip()), f"[{self.id}] name 不能为空")
        _require(0 < self.age < 130, f"[{self.id}] age 不合理:{self.age}")
        _require(bool(self.occupation.strip()), f"[{self.id}] occupation 不能为空")
        _require(bool(self.start_location.strip()), f"[{self.id}] start_location 不能为空")
        normalize_mbti(self.mbti)  # 非法 MBTI 会抛 ValueError
        if self.art:
            self.art.validate(is_character=True)

    def to_persona(self) -> Persona:
        return Persona(
            name=self.name, age=self.age, mbti=self.mbti, occupation=self.occupation,
            gender=self.gender, backstory=self.backstory, hobbies=list(self.hobbies),
            relationships=dict(self.relationships), secret=self.secret, role=self.role,
            drive=self.drive,
        )


@dataclass
class ObjectManifest:
    kind = "object"
    id: str
    name: str
    affordances: list[str] = field(default_factory=list)
    art: ArtSpec | None = None

    def validate(self) -> None:
        _require(bool(self.id and self.id.strip()), "object.id 不能为空")
        _require(bool(self.name and self.name.strip()), f"[{self.id}] name 不能为空")
        if self.art:
            self.art.validate(is_character=False)

    def to_world_object(self) -> WorldObject:
        return WorldObject(name=self.name, affordances=list(self.affordances))


@dataclass
class PlaceManifest:
    kind = "place"
    id: str
    name: str
    description: str = ""
    geography: str = "town"
    objects: list[str] = field(default_factory=list)  # object ids
    adjacent: list[str] = field(default_factory=list)  # place ids
    art: ArtSpec | None = None

    def validate(self) -> None:
        _require(bool(self.id and self.id.strip()), "place.id 不能为空")
        _require(bool(self.name and self.name.strip()), f"[{self.id}] name 不能为空")
        _require(
            self.geography in GEOGRAPHIES,
            f"[{self.id}] geography 非法:{self.geography}(允许 {GEOGRAPHIES})",
        )
        if self.art:
            self.art.validate(is_character=False)

    def to_place(self, object_lookup: dict[str, ObjectManifest]) -> Place:
        objs = [object_lookup[oid].to_world_object() for oid in self.objects]
        return Place(name=self.name, description=self.description, objects=objs, adjacent=[])
