"""项目（立项）与章节草稿的请求/响应 schema（C3 / ARCH §7.2）。

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

from __future__ import annotations

import uuid
from datetime import datetime
from typing import Any, Literal

from pydantic import BaseModel, Field


class ProjectCreateRequest(BaseModel):
    """POST /projects：立项向导字段（owner_id 由后端补 stub，不入参）。"""

    title: str = Field(min_length=1)
    genre: str | None = None
    logline: str | None = None
    premise: str | None = None
    theme: str | None = None
    selling_points: list[str] = Field(default_factory=list)
    structure: str | None = None
    # 立项书级常量（灵感④）——free text + 前端预设，无 Literal（Literal 只留 Verdict）。
    tone: str | None = None
    ending_type: str | None = None
    narrative_pov: str | None = None


class ProjectResponse(BaseModel):
    """项目视图（创建/列表/详情共用）。"""

    id: uuid.UUID
    title: str
    genre: str | None = None
    logline: str | None = None
    premise: str | None = None
    theme: str | None = None
    selling_points: list[str] = Field(default_factory=list)
    structure: str | None = None
    tone: str | None = None
    ending_type: str | None = None
    narrative_pov: str | None = None
    updated_at: datetime | None = Field(default=None, description="项目最近更新时间")
    pending_review_count: int = Field(default=0, ge=0, description="待审稿章节数")


class ProjectListResponse(BaseModel):
    """GET /projects：项目列表。"""

    projects: list[ProjectResponse] = Field(default_factory=list)


# ---- AI 立项方案生成（灵感⑤；向导阶段 project 未建，纯预览不写库）----

# 请求字段长度上界（CR-H9：防超长向导草稿撑爆上下文/成本）。短字段=标签级，长字段=段落级。
_PLAN_SHORT_MAX = 200
_PLAN_LONG_MAX = 4000
_PLAN_LIST_MAX = 20


class ProjectPlanGenerateRequest(BaseModel):
    """POST /skills/project-plan/generate：向导草稿种子（不带 project 前缀，立项前 project 未建）。

    **种子门控**：`genre` + `logline` 为必给种子（端点校验非空，否则 422）——空向导不出方案，
    防 slop。其余字段有则贴合、无则不臆造（序列化进 project_context，仿 `_project_context`）。
    全字段带 `max_length` 上界（CR-H9），`brief` = 作者一句话方向（可空）。
    """

    genre: str | None = Field(default=None, max_length=_PLAN_SHORT_MAX, description="题材（种子）")
    logline: str | None = Field(
        default=None, max_length=_PLAN_LONG_MAX, description="一句话故事（种子）"
    )
    title: str | None = Field(
        default=None, max_length=_PLAN_SHORT_MAX, description="暂定书名（可空）"
    )
    premise: str | None = Field(default=None, max_length=_PLAN_LONG_MAX, description="立意（可空）")
    theme: str | None = Field(default=None, max_length=_PLAN_SHORT_MAX, description="主题（可空）")
    structure: str | None = Field(
        default=None, max_length=_PLAN_SHORT_MAX, description="故事结构（可空）"
    )
    tone: str | None = Field(default=None, max_length=_PLAN_SHORT_MAX, description="基调（可空）")
    ending_type: str | None = Field(
        default=None, max_length=_PLAN_SHORT_MAX, description="结局取向（可空）"
    )
    narrative_pov: str | None = Field(
        default=None, max_length=_PLAN_SHORT_MAX, description="叙事视角（可空）"
    )
    selling_points: list[str] = Field(
        default_factory=list, max_length=_PLAN_LIST_MAX, description="核心卖点（可空）"
    )
    # `str | None`（非 `str=""`）：codegen 渲染为可选字段，前端种子请求可省略（brief 本就可空）。
    brief: str | None = Field(
        default=None, max_length=_PLAN_LONG_MAX, description="作者一句话方向/需求（可空）"
    )


class ProjectPlanView(BaseModel):
    """立项方案生成结果（贴 ww_agents.ProjectPlanResult；结构化预览，不入库）。

    全字段有默认值（守解析韧性）——前端按字段回填立项向导，只填非空交集、不覆盖作者已编辑值。
    """

    title_candidates: list[str] = Field(default_factory=list, description="书名候选清单")
    setting: str = Field(default="", description="时空背景")
    narrative_structure: str = Field(default="", description="叙事结构")
    story_core: str = Field(default="", description="故事核心")
    ending_design: str = Field(default="", description="结局设计")
    tone: str = Field(default="", description="基调")


class DraftStreamRequest(BaseModel):
    """POST /projects/:id/chapters/:no/draft：本章生成的可选输入（T4-b）。

    `directive` = 作者本章指令（覆盖/补充大纲节拍）。临时输入，不持久化、无迁移；
    后端只入 volatile（守缓存前缀不变量 #9）。无 body 时退化为纯按大纲生成（向后兼容）。
    """

    directive: str | None = None


class DraftSaveRequest(BaseModel):
    """PUT /projects/:id/chapters/:no/draft：自动保存草稿正文。"""

    text: str


class DraftResponse(BaseModel):
    """草稿保存结果（脱敏：只回元信息 + 长度，不回灌正文以外的衍生）。"""

    project_id: uuid.UUID
    chapter_no: int
    volume: int
    status: str
    version: int
    length: int


class DraftView(BaseModel):
    """GET .../draft：回灌已保存草稿（含正文，供工作台重载编辑器）。

    与 PUT 的 `DraftResponse` 同元信息，**额外带 `content`**——读侧需要正文以重建编辑器，
    保存侧不回灌正文故不带。`length` 仍按 content 字符数派生。
    """

    project_id: uuid.UUID
    chapter_no: int
    volume: int
    status: str
    version: int
    content: str
    length: int


# ---- 审稿（T2.5）----


class ReviewRequest(BaseModel):
    """POST /projects/:id/chapters/:no/review：可选携带待审草稿正文。

    不传 `draft` 时端点回退到已保存的草稿（chapter_repo）。
    """

    draft: str | None = None


class ReviewConflictView(BaseModel):
    """单条一致性冲突（chapter_reviews.conflicts JSONB 子项；形对齐 SSE `conflict` 事件）。

    强类型化此前的裸 `dict[str, Any]`，给 TS 客户端真实字段类型。容忍历史/缺省（默认值）。
    """

    type: str = ""
    where: str = ""
    refs: list[str] = Field(default_factory=list)
    suggestion: str = ""
    # 一键采纳补丁对（可缺）：留痕里带则前端「采纳改法」可把 original→replacement 改入终稿。
    original: str | None = None
    replacement: str | None = None


class ReviewHistoryItem(BaseModel):
    """单条审稿留痕（GET .../reviews 历史项；snake_case）。"""

    id: uuid.UUID
    project_id: uuid.UUID
    chapter_no: int
    chapter_version: int | None = None
    conflicts: list[ReviewConflictView] = Field(default_factory=list)
    foreshadow_sug: list[dict[str, Any]] = Field(default_factory=list)
    style: dict[str, Any] | None = None
    pace: dict[str, Any] | None = None
    # 灵感③ advisory 人物塑造审留痕（issues 列表 dict）；仅建议、不阻断验收。
    characterization: dict[str, Any] | None = None
    health_score: int | None = None
    decisions: dict[str, Any] | None = None


class ReviewHistoryResponse(BaseModel):
    """GET /projects/:id/chapters/:no/reviews：审稿历史（新→旧）。"""

    reviews: list[ReviewHistoryItem] = Field(default_factory=list)


# ---- 验收（T2.4）----

# 每个冲突的裁决：采纳改法 / 忽略 / 手改（R5）。
Verdict = Literal["accept", "ignore", "manual"]


class ConflictDecision(BaseModel):
    """对最近一次审稿留痕里**某个冲突**（按其在 conflicts 列表的下标定位）的裁决。"""

    conflict_index: int = Field(ge=0, description="冲突在最近审稿 conflicts 列表中的下标")
    verdict: Verdict = Field(description="采纳改法 / 忽略 / 手改")
    note: str | None = Field(default=None, description="可选裁决备注（如手改说明）")


class AcceptRequest(BaseModel):
    """POST /projects/:id/chapters/:no/accept：裁决清单 + 可能改过的终稿。"""

    final_text: str = Field(min_length=1, description="作者裁决/改稿后的最终验收文本")
    decisions: list[ConflictDecision] = Field(
        default_factory=list, description="对最近审稿每个冲突的裁决（每冲突必有其一，R5）"
    )


class AcceptResponse(BaseModel):
    """验收「本次将更新」清单（ARCH §7.2 写回结果）。"""

    project_id: uuid.UUID
    chapter_no: int
    accepted_version: int = Field(description="晋升到的 accepted 版次（max+1）")
    digest_added: bool = Field(description="是否新增了一行 chapter_digests")
    decisions_recorded: int = Field(description="本次写回的裁决条数")
    review_id: uuid.UUID | None = Field(default=None, description="写回裁决的审稿留痕行 id")
