"""终稿 digest 提炼（验收事务的 R2 步骤；ARCH §5.5 / §6.1，不变量 #4）。

验收时用**终稿**（作者裁决/改稿后的最终文本）经网关跑一次轻量结构化提炼，得 digest
（结构化事实）。**在开原子事务之前**完成——别在持开事务里跨网络调 LLM（R2）。

`ChapterDigestFacts` 是 digest 的结构化形：本章关键事实清单，供后续章节注入近况摘要
（assemble 的 `recent_digests`）+ 一致性比对。轻量档位（tier=light），只读终稿、产事实。

记账：digest 提炼这次网关调用产 usage，经 `SqlAlchemyLedgerSink` flush 进**同一请求
session**——由验收事务在末尾一次 commit 落 `usage_ledger`（见 ledger gotcha）。
"""

from __future__ import annotations

import uuid
from typing import Any

import structlog
from pydantic import BaseModel, Field
from ww_llm_gateway import Gateway
from ww_llm_gateway.types import Block, LlmRequest, Scope

log = structlog.get_logger(__name__)

DIGEST_SYSTEM_PROMPT = """你是长篇连载小说的「章节摘要提炼」。读入本章**终稿**，抽取后续\
章节一致性比对所需的结构化事实，产出结构化摘要。

只提炼**终稿明确写出**的事实，不臆造、不推断未写明的内容：
- summary：本章一句话主线（≤60 字）；
- events：本章发生的关键事件（按时序）；
- characters：登场人物及其本章状态变化（姓名 + 状态/变化）；
- locations：出现的地点；
- foreshadow：本章埋下或回收的伏笔线索。

纪律：只读终稿、只产事实，不评价、不改稿、不报冲突（冲突在审稿期产）。"""


class CharacterStateFact(BaseModel):
    """单个人物的本章状态事实。"""

    name: str = Field(description="人物姓名")
    state: str = Field(description="本章该人物的状态/变化")


class ChapterDigestFacts(BaseModel):
    """终稿提炼的结构化事实（落 `chapter_digests.facts`）。"""

    summary: str = Field(default="", description="本章一句话主线")
    events: list[str] = Field(default_factory=list, description="关键事件（时序）")
    characters: list[CharacterStateFact] = Field(
        default_factory=list, description="登场人物及其本章状态变化"
    )
    locations: list[str] = Field(default_factory=list, description="出现的地点")
    foreshadow: list[str] = Field(default_factory=list, description="埋下/回收的伏笔线索")


def build_digest_request(
    *, final_text: str, user_id: uuid.UUID, project_id: uuid.UUID
) -> LlmRequest:
    """据终稿构造 digest 提炼请求（纯函数）。

    `system_prompt` 进缓存断点前块；终稿进 `input`（断点后）。tier=light（不变量 #2）。
    `output_schema=ChapterDigestFacts` → 网关经 instructor 保证产结构化实例（C1）。
    """
    return LlmRequest(
        tier="light",
        system=[Block(text=DIGEST_SYSTEM_PROMPT, cache=True)],
        input=f"## 本章终稿\n{final_text}",
        output_schema=ChapterDigestFacts,
        scope=Scope(user_id=user_id, project_id=project_id),
    )


async def extract_digest_facts(
    gateway: Gateway,
    *,
    final_text: str,
    user_id: uuid.UUID,
    project_id: uuid.UUID,
    chapter_no: int,
) -> dict[str, Any]:
    """从终稿提炼结构化事实，返回可直接落 `facts` 列的 dict。

    **在开原子事务之前**调用（R2：别在持开事务里跨网络调 LLM）。`gateway.run(req).parsed`
    带 schema 时必非 None（C1）；防御性兜底：若 parsed 缺失则落空事实（不崩验收）。
    日志脱敏：只记终稿长度，不记正文。
    """
    req = build_digest_request(final_text=final_text, user_id=user_id, project_id=project_id)
    resp = await gateway.run(req)
    parsed = resp.parsed
    facts = parsed.model_dump() if parsed is not None else ChapterDigestFacts().model_dump()
    log.info(
        "digest_extracted",
        project_id=str(project_id),
        chapter_no=chapter_no,
        final_text_len=len(final_text),
        event_count=len(facts.get("events", [])),
    )
    return facts
