"""记忆服务输出契约（C5）——AssembledContext / SelectionTrace。

决策（memory/decisions.md 2026-06-18）：assemble 返回中性序列化文本，
不返回 gateway 的 LlmRequest——记忆服务只经 DB 通信，不依赖网关类型。
write 节点(T1.3)再据此构造 LlmRequest(system=[Block(stable_core, cache=True)], input=volatile)。
"""

from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, Field

EntityKind = Literal["character", "world_entity"]
SelectionReason = Literal[
    "explicit_beat",  # 本章大纲 beats 点名
    "main_character",  # 主角/核心常驻
    "recent_digest",  # 近 N 章摘要出现
    "foreshadow_window",  # 命中本章伏笔窗口
    "author_pin",  # 作者手动置顶（B0 可控版 override 强制纳入）
]

# 理由的规范排序（保证 SelectionTrace 确定性）。author_pin 排末位：
# 它是作者覆盖的来源，区别于前四个确定性来源（不变量 #6 的作者兜底，ARCH §10）。
REASON_ORDER: tuple[SelectionReason, ...] = (
    "explicit_beat",
    "main_character",
    "recent_digest",
    "foreshadow_window",
    "author_pin",
)


class SelectedEntity(BaseModel):
    """一个被选中的实体 + 入选理由（喂给 UX 注入透明面板，T1.6）。"""

    model_config = {"frozen": True}

    kind: EntityKind
    name: str
    reasons: list[SelectionReason] = Field(default_factory=list)


class SelectionTrace(BaseModel):
    """确定性选择的可解释结果：选了谁、为什么。"""

    model_config = {"frozen": True}

    selected: list[SelectedEntity] = Field(default_factory=list)


class AssembledContext(BaseModel):
    """组装结果：缓存断点前的稳定内核 + 断点后的易变内容 + 选择留痕。

    stable_core / volatile 均为**已确定性排序、无时间戳/无 UUID** 的字符串
    （缓存前缀字节稳定，ARCH §4.6 / 不变量 #9）。
    """

    model_config = {"frozen": True}

    stable_core: str
    volatile: str
    selection: SelectionTrace
