"""多章链图工厂 `build_chain_graph`（chain-workflow 设计 §3.3）。

链 = LangGraph 有环图：`write → review → decide →(无冲突)→ accept →(还有章)→ write`，
`decide →(有冲突)→ interrupt ⏸ → accept`，写满 K 章 → END。本图**激活休眠资产**：
checkpointer（持久控制流 + pending handle，跨小时可恢复）+ interrupt（遇冲突暂停交人）。

依赖注入纪律（与现有「gateway 闭包绑定」同模式）：节点 DB 依赖经 `session_factory` +
`*_repo_factory` + `accept_op` 闭包绑入图节点。langgraph 不收 `functools.partial`，故用
**默认参绑定**（见 memory/gotchas.md）。

checkpointer 纪律（CLAUDE.md「LangGraph」）：`setup()` 只在 migrations/CI 跑（`setup_checkpointer`
在 `orchestrator/graph.py`，迁移/CI 调一次），build 函数绝不触碰 DDL；单测注 `MemorySaver`。
"""

from __future__ import annotations

import uuid
from collections.abc import Callable, Sequence
from typing import TYPE_CHECKING, Any

from langgraph.graph import END, START, StateGraph
from langgraph.types import interrupt
from ww_agents import AgentSpec

from ..graph import REVIEW_SPECS
from . import nodes
from .nodes import (
    AcceptChapterOp,
    AssembleFn,
    ChapterDraftRepo,
    GatewayBuilder,
    MemoryReposFactory,
    ReviewRecordRepo,
    SessionFactory,
)
from .state import ChainState

if TYPE_CHECKING:
    from langgraph.checkpoint.base import BaseCheckpointSaver
    from langgraph.graph.state import CompiledStateGraph

WRITE_CHAPTER = "write_chapter"
REVIEW_CHAPTER = "review_chapter"
DECIDE = "decide"
ACCEPT_CHAPTER = "accept_chapter"


def build_chain_graph(
    gateway_builder: GatewayBuilder,
    *,
    session_factory: SessionFactory,
    memory_repos_factory: MemoryReposFactory,
    chapter_repo_factory: Callable[[Any], ChapterDraftRepo],
    review_repo_factory: Callable[[Any], ReviewRecordRepo],
    assemble: AssembleFn,
    accept_op: AcceptChapterOp,
    review_specs: Sequence[AgentSpec] = REVIEW_SPECS,
    checkpointer: BaseCheckpointSaver[Any] | None = None,
) -> CompiledStateGraph[ChainState, None, ChainState, ChainState]:
    """构建并编译 `draft_volume` 多章链图。

    节点（`write_chapter`/`review_chapter`/`decide`/`accept_chapter`）经默认参绑定把
    `gateway_builder`/`session_factory`/各 repo 工厂/`assemble`/`accept_op` 闭包绑入。
    `gateway_builder` 而非具体网关实例：write/review 节点在自建 session 上现建网关，使
    usage_ledger 绑节点 session（不变量 #1，修复请求网关 session 已关闭致记账丢失的缺陷）。
    条件边：`decide` 命中冲突 → `accept_chapter` 内 `interrupt()` 暂停交人；
    `accept_chapter` 后判 `current_chapter_no <= last_chapter_no` 回 `write_chapter` 或 END。

    `checkpointer`：运行时传 Postgres saver；单测传 `MemorySaver`（同进程跨 invoke 可恢复）。
    """

    async def _write(state: ChainState) -> dict[str, Any]:
        return await nodes.write_chapter(
            state,
            gateway_builder=gateway_builder,
            session_factory=session_factory,
            memory_repos_factory=memory_repos_factory,
            chapter_repo_factory=chapter_repo_factory,
            assemble=assemble,
        )

    async def _review(state: ChainState) -> dict[str, Any]:
        return await nodes.review_chapter(
            state,
            gateway_builder=gateway_builder,
            session_factory=session_factory,
            memory_repos_factory=memory_repos_factory,
            chapter_repo_factory=chapter_repo_factory,
            review_repo_factory=review_repo_factory,
            assemble=assemble,
            review_specs=review_specs,
        )

    async def _decide(state: ChainState) -> dict[str, Any]:
        return await nodes.decide(
            state,
            session_factory=session_factory,
            review_repo_factory=review_repo_factory,
        )

    async def _accept(state: ChainState) -> dict[str, Any]:
        # interrupt 放节点**起始**（CLAUDE.md「LangGraph」）：有冲突 → 暂停交人，恢复值 =
        # 该章裁决清单；无冲突 → decisions=[]，直接验收。恢复后 accept_op 落库（不变量 #3）。
        decisions: list[Any] = []
        if nodes.has_conflicts(state):
            resumed = interrupt({"chapter_no": state["current_chapter_no"]})
            decisions = list(resumed or [])
        return await nodes.accept_chapter(state, decisions=decisions, accept_op=accept_op)

    builder: StateGraph[ChainState, None, ChainState, ChainState] = StateGraph(ChainState)
    builder.add_node(WRITE_CHAPTER, _write)
    builder.add_node(REVIEW_CHAPTER, _review)
    builder.add_node(DECIDE, _decide)
    builder.add_node(ACCEPT_CHAPTER, _accept)

    builder.add_edge(START, WRITE_CHAPTER)
    builder.add_edge(WRITE_CHAPTER, REVIEW_CHAPTER)
    builder.add_edge(REVIEW_CHAPTER, DECIDE)
    # decide 后无论有无冲突都进 accept；interrupt（若有冲突）在 accept 节点起始处暂停。
    builder.add_edge(DECIDE, ACCEPT_CHAPTER)
    # accept 后条件边：还有章 → 回 write 续跑；写满 K 章 → END。
    builder.add_conditional_edges(
        ACCEPT_CHAPTER,
        _should_continue,
        {WRITE_CHAPTER: WRITE_CHAPTER, END: END},
    )
    return builder.compile(checkpointer=checkpointer)


def _should_continue(state: ChainState) -> str:
    """accept 后路由：`current_chapter_no <= last_chapter_no` → 续写下一章；否则 END。

    `accept_chapter` 已把 `current_chapter_no += 1`，故此处直接比较即判「是否还有章」。
    纯函数，便于单测。
    """
    if state["current_chapter_no"] <= state["last_chapter_no"]:
        return WRITE_CHAPTER
    return END


def initial_chain_state(
    *,
    project_id: uuid.UUID,
    user_id: uuid.UUID,
    start_chapter_no: int,
    count: int,
    chain_key: str = "draft_volume",
) -> ChainState:
    """构造初始 `ChainState`（区间 [start, start+count-1]，current=start，written=[]）。

    纯函数：服务层 / 测试构初值用，集中区间换算（last = start + count - 1，含）。
    """
    return {
        "project_id": project_id,
        "user_id": user_id,
        "chain_key": chain_key,
        "start_chapter_no": start_chapter_no,
        "last_chapter_no": start_chapter_no + count - 1,
        "current_chapter_no": start_chapter_no,
        "written": [],
        "has_conflicts": False,
    }


__all__ = [
    "ACCEPT_CHAPTER",
    "DECIDE",
    "REVIEW_CHAPTER",
    "WRITE_CHAPTER",
    "build_chain_graph",
    "initial_chain_state",
]
