"""大纲**写侧** Repository（逐章 upsert；ARCH §5.4 outliner writes=outline / §7.2 POST /outline）。

读侧（`get`，供 assemble 注入本章 beats）已在 `ww_core.memory.sql_repositories.SqlOutlineRepo`
+ `domain.repositories.OutlineRepo`/`OutlineView` 提供（C5 稳定，不动）。本模块加**写**能力，
命名加 `Write` 前缀避免与读侧 `OutlineRepo` 同名歧义（同 `DigestAppendRepo`/`ForeshadowLedgerRepo`
区别于读侧的先例，见 memory/decisions）。

**提交边界**：`upsert_chapter` 只 `flush()` 不 `commit()`——提交交端点事务（T3.5 端点末尾
一次 `commit()`，与网关 ledger 一并落库；与 M2 验收-side repos 一致，见 memory/gotchas）。

唯一约束 `(project_id, chapter_no)`：用**显式 read-modify-write**（按 chapter_no 查→有则改、
无则插），不用 PG `ON CONFLICT`（保持与项目其它 upsert 路径一致、可纯 fake 单测，不引 pg 依赖）。

**beats 存储形**：DB 列 `outline.beats` 是 JSONB `dict`，而 outliner 产 `list[str]`——
故包裹成 `{"beats": [...]}` 落库，读侧 `OutlineView.beats` 取到同形 dict（round-trip 一致）。
`foreshadow_windows` 是 JSONB list，直接落 `[w.model_dump() for w in chapter.foreshadow_windows]`。
"""

from __future__ import annotations

import uuid
from typing import Any, Protocol

from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from ww_db.models import Outline


class OutlineWriteView(BaseModel):
    """单章大纲写入后的只读快照（snake_case，frozen）。"""

    model_config = {"frozen": True}

    chapter_no: int
    volume: int
    beats: list[str] = Field(default_factory=list)
    foreshadow_windows: list[dict[str, Any]] = Field(default_factory=list)


class OutlineWriteRepo(Protocol):
    """大纲写侧接口（按 project_id 隔离；唯一 `(project_id, chapter_no)`；只 flush 不 commit）。"""

    async def upsert_chapter(
        self,
        project_id: uuid.UUID,
        *,
        volume: int,
        chapter_no: int,
        beats: list[str],
        foreshadow_windows: list[dict[str, Any]],
    ) -> OutlineWriteView:
        """逐章 upsert：同 `(project_id, chapter_no)` 覆盖，否则插入。只 flush。"""
        ...


def _to_view(row: Outline) -> OutlineWriteView:
    raw = row.beats or {}
    beats = list(raw.get("beats", [])) if isinstance(raw, dict) else []
    return OutlineWriteView(
        chapter_no=row.chapter_no,
        volume=row.volume,
        beats=beats,
        foreshadow_windows=list(row.foreshadow_windows or []),
    )


class SqlOutlineWriteRepo:
    """SQLAlchemy 实现：显式 read-modify-write upsert（只 flush 不 commit）。"""

    def __init__(self, session: AsyncSession) -> None:
        self._s = session

    async def upsert_chapter(
        self,
        project_id: uuid.UUID,
        *,
        volume: int,
        chapter_no: int,
        beats: list[str],
        foreshadow_windows: list[dict[str, Any]],
    ) -> OutlineWriteView:
        row = (
            await self._s.execute(
                select(Outline).where(
                    Outline.project_id == project_id,
                    Outline.chapter_no == chapter_no,
                )
            )
        ).scalar_one_or_none()
        # JSONB 写入建新对象重赋值（避免原地 mutate 不被 ORM 侦测，见 gotcha）。
        beats_payload = {"beats": list(beats)}
        windows_payload = [dict(w) for w in foreshadow_windows]
        if row is None:
            row = Outline(
                project_id=project_id,
                volume=volume,
                chapter_no=chapter_no,
                beats=beats_payload,
                foreshadow_windows=windows_payload,
            )
            self._s.add(row)
        else:
            row.volume = volume
            row.beats = beats_payload
            row.foreshadow_windows = windows_payload
        await self._s.flush()
        await self._s.refresh(row)
        return _to_view(row)
