"""生成/入库端点的请求/响应 schema（C3 扩 / ARCH §6.5 / §7.2）。

snake_case；前端经 OpenAPI 生成 TS 类型消费。改字段 → 前端必须 `pnpm gen:api`。

生成走**即时返回**（预览 → 作者确认 → 入库），非 jobs 长任务（M5-d）：
- `POST /world/generate` / `POST /characters/generate`：返回**预览**（不持久化）。
- `POST /characters`（入库）：作者确认的角色卡 → 过 continuity 预检 gate + 权限白名单 → 写库。

视图字段贴 `ww_agents.WorldEntityCard`/`CharacterCard`（保持生成产物形：traits/speech_tics
是 list、arc 是 str；入库时由写侧 repo 转 DB JSONB 形，见 character_repo）。
"""

from __future__ import annotations

from pydantic import BaseModel, Field, model_validator

from ww_api.schemas.rules import RuleView

# 批量生成数量上限（防一次性巨量调用；原型保守值）。
MAX_GENERATE_COUNT = 12


# ---- 世界观生成（预览）----


class WorldGenerateRequest(BaseModel):
    """POST /projects/:id/world/generate：据需求生成世界观实体（预览）。"""

    brief: str = Field(min_length=1, description="世界观生成需求（题材/设定方向/约束）")


class WorldEntityCardView(BaseModel):
    """单个世界观实体卡（贴 ww_agents.WorldEntityCard；预览 + 入库共用形）。"""

    type: str = Field(description="实体类型（势力 / 地理 / 力量体系 / 物品 / 概念 等）")
    name: str = Field(description="实体名")
    rules: list[str] = Field(default_factory=list, description="该实体的硬规则清单")


class WorldGenPreviewResponse(BaseModel):
    """世界观生成预览（不持久化；作者确认后另走入库，本期入库端点为角色）。"""

    entities: list[WorldEntityCardView] = Field(default_factory=list)


# ---- 角色生成（预览）----


class CharacterGenerateRequest(BaseModel):
    """POST /projects/:id/characters/generate：据需求生成角色卡（预览，群像防雷同）。

    `role_mix`（⑦ 群像按定位配比）与 `count` 的关系：`role_mix` 在场时 `count` 取各值之和
    （忽略客户端显式 count），每项数量须 ≥ 1、总和须 ≤ `MAX_GENERATE_COUNT`；缺省则退回
    单一 `role` + `count` 语义。
    """

    brief: str = Field(min_length=1, description="角色生成需求")
    count: int = Field(
        default=1,
        ge=1,
        le=MAX_GENERATE_COUNT,
        description="生成数量（role_mix 在场时取各值之和）",
    )
    role: str | None = Field(default=None, description="角色定位（主角/CP/对手/导师/工具人 等）")
    role_mix: dict[str, int] | None = Field(
        default=None,
        description="按定位配比生成 {定位: 数量}；在场时 count=各值之和（每项≥1、总和≤12）",
    )

    @model_validator(mode="after")
    def _reconcile_role_mix(self) -> CharacterGenerateRequest:
        """配比在场时校验并把 count 定为各值之和（构造期归一化，非外部对象变更）。"""
        if self.role_mix is None:
            return self
        if not self.role_mix:
            raise ValueError("role_mix 不能为空对象")
        if any(n < 1 for n in self.role_mix.values()):
            raise ValueError("role_mix 每个定位的数量须 ≥ 1")
        total = sum(self.role_mix.values())
        if total > MAX_GENERATE_COUNT:
            raise ValueError(f"role_mix 数量总和须 ≤ {MAX_GENERATE_COUNT}")
        self.count = total
        return self


class CharacterRelationView(BaseModel):
    """单条人物关系（贴 ww_agents.CharacterRelation）。"""

    name: str
    kind: str
    note: str | None = None


class CharacterCardView(BaseModel):
    """单张角色卡（贴 ww_agents.CharacterCard；预览 + 入库请求共用形）。"""

    name: str = Field(description="角色名")
    role: str = Field(description="角色定位")
    traits: list[str] = Field(default_factory=list, description="性格特质清单（性格三层）")
    appearance: str = Field(default="", description="外貌形象（缺则空串）")
    motive: str = Field(default="", description="核心动机/欲望/恐惧（缺则空串）")
    backstory: str = Field(description="背景故事")
    arc: str = Field(description="人物弧光（一句话）")
    speech_tics: list[str] = Field(default_factory=list, description="口癖/语言风格")
    tags: list[str] = Field(default_factory=list, description="人设标签/萌点")
    relations: list[CharacterRelationView] = Field(default_factory=list, description="关系网")


class CharacterGenPreviewResponse(BaseModel):
    """角色生成预览（不持久化；作者确认后经 POST /characters 入库）。"""

    cards: list[CharacterCardView] = Field(default_factory=list)


# ---- 角色入库（作者确认后；过 continuity gate + 权限白名单）----


class CharacterIngestRequest(BaseModel):
    """POST /projects/:id/characters：把作者确认的角色卡入库。

    `acknowledge_conflicts`：作者已查看并接受预检冲突时置 `true`，越过 continuity gate
    （仿 accept 的冲突裁决：不静默入库，须作者确认；默认 false → 有冲突即 409，见端点）。
    """

    cards: list[CharacterCardView] = Field(min_length=1, description="待入库的角色卡（至少 1 张）")
    acknowledge_conflicts: bool = Field(
        default=False, description="作者已知悉并接受 continuity 冲突 → 放行入库"
    )


class IngestConflictView(BaseModel):
    """入库预检检出的单条 continuity 冲突（贴 ww_agents.Conflict 五类）。"""

    type: str
    where: str
    refs: list[str] = Field(default_factory=list)
    suggestion: str


class CharacterIngestResponse(BaseModel):
    """角色入库结果（201）：写入的角色 + 被白名单丢弃的越权表（审计）。"""

    created: list[str] = Field(default_factory=list, description="写入的角色名（按入参顺序）")
    rejected_tables: list[str] = Field(
        default_factory=list, description="被权限白名单丢弃的越权写表名（审计；正常为空）"
    )


# ---- 读端点：设定库 Codex（角色 / 世界观全量列表）----


class CharacterListResponse(BaseModel):
    """GET /projects/:id/characters：已入库角色全量列表（设定库 Codex 真源）。

    DB JSONB dict 列 → API list/str 反向解包（入库形变的逆向，见 character_repo）：
    `{"items":[...]}`→list、`{"text":...}`→str；tags/relations 直落 list。
    """

    characters: list[CharacterCardView] = Field(default_factory=list)


class WorldEntityListResponse(BaseModel):
    """GET /projects/:id/world_entities：已入库世界观实体全量列表（设定库 Codex 真源）。

    DB JSONB dict 列 `{"rules":[...]}`→裸 list（worldbuilder 形变的逆向）。
    """

    world_entities: list[WorldEntityCardView] = Field(default_factory=list)


# ---- 读端点：规则列表 + 技能库（T5.6 前端）----


class RuleListResponse(BaseModel):
    """GET /projects/:id/rules：规则列表（规则页）。"""

    rules: list[RuleView] = Field(default_factory=list)


class SkillView(BaseModel):
    """单个 skill 的声明式视图（技能库 UI）。"""

    name: str
    scope: str = Field(description="builtin / custom / community")
    tier: str = Field(description="能力档位（writer / analyst / light）")
    reads: list[str] = Field(default_factory=list)
    writes: list[str] = Field(default_factory=list)
    genre: str | None = None


class SkillListResponse(BaseModel):
    """GET /skills：技能库列表（按 name 升序）。"""

    skills: list[SkillView] = Field(default_factory=list)
