"""write 节点（C4 / ARCH §5.2 §5.4 writer 行）——把组装上下文流式写成草稿。

确定性边界（CLAUDE.md「LangGraph」纪律）：
- 节点逻辑里**无 LLM 非确定性**——所有不确定性藏在网关后；节点只做
  `AssembledContext → LlmRequest` 的纯构造 + 转发 `Gateway.stream` 的 `Delta`。
- 因此 `build_write_request` 是纯函数、`stream_chapter_draft` 注入 mock 网关即可单测，
  不需要图运行时、不需要真 Postgres。
- 瞬时失败重试在网关（§4.5），不在节点；节点遇错只干净抛出，SSE 层据此发 `error`（§7.3）。

不变量 ②：只传 `tier="writer"`，绝不传具体 model。
不变量 #9：`stable_core` 进 `system` 缓存断点前（cache=True），`volatile` 进 `input`（断点后）。
"""

from __future__ import annotations

import uuid
from collections.abc import AsyncIterator
from typing import Protocol

from ww_agents import load_craft_doctrine
from ww_llm_gateway.types import Block, Delta, LlmRequest, Scope

from .state import ChapterState

# 写章 craft 教条（灵感①）：品类无关内核，import 期读盘 → 缺失即 fail-fast（崩 import）。
# 字节稳定的稳定内容，作为最前的 cache=True system 块（跨书前缀复用，不变量 #9）。
_CRAFT_DOCTRINE = load_craft_doctrine()


class GatewayStream(Protocol):
    """write 节点对网关的最小依赖——只需 `stream`（注入真网关或 mock）。"""

    def stream(self, req: LlmRequest) -> AsyncIterator[Delta]: ...


def build_write_request(
    *,
    stable_core: str,
    volatile: str,
    user_id: uuid.UUID,
    project_id: uuid.UUID,
) -> LlmRequest:
    """据组装上下文构造写章请求（纯函数，决策 2026-06-18）。

    `system` = [craft 教条(cache), stable_core(cache)]——craft 品类无关内核在最前，
    最大化跨书前缀复用；两块均在缓存断点前（cache=True）。`volatile` → `input`（断点后）。
    """
    return LlmRequest(
        tier="writer",
        system=[
            Block(text=_CRAFT_DOCTRINE, cache=True),
            Block(text=stable_core, cache=True),
        ],
        input=volatile,
        stream=True,
        scope=Scope(user_id=user_id, project_id=project_id),
    )


async def stream_chapter_draft(
    gateway: GatewayStream,
    *,
    stable_core: str,
    volatile: str,
    user_id: uuid.UUID,
    project_id: uuid.UUID,
) -> AsyncIterator[Delta]:
    """流式生成本章草稿增量（T1.4 的底层流）——构造请求 → 转发网关 `Delta`。

    本身不累积/不落库；累积归 `write_node`（图状态），落库（自动保存）归 T1.4。
    瞬时失败已在网关重试；此处的异常向上抛给 SSE 归一层（发 `error` 事件）。
    """
    req = build_write_request(
        stable_core=stable_core,
        volatile=volatile,
        user_id=user_id,
        project_id=project_id,
    )
    async for delta in gateway.stream(req):
        yield delta


async def write_node(state: ChapterState, *, gateway: GatewayStream) -> dict[str, str]:
    """LangGraph write 节点：流式生成 → 累积草稿 → 不可变返回 `{"draft": ...}`。

    直接调用本函数（注入 mock 网关）即可单测，无需图运行时。
    返回**增量字典**（LangGraph 合并进状态），不原地改 `state`（不可变更新）。
    """
    parts: list[str] = []
    async for delta in stream_chapter_draft(
        gateway,
        stable_core=state["stable_core"],
        volatile=state["volatile"],
        user_id=state["user_id"],
        project_id=state["project_id"],
    ):
        parts.append(delta.text)
    return {"draft": "".join(parts)}
