"""Prompt 加载器（Prompt 外置方案A · 步2）。

`load_prompt(name)` 在模块 import 期一次性读盘 `prompts/<name>.md` → 规整 → 内存缓存
→ 返回完整 UTF-8 文本。保证 `spec.system_prompt` 字节稳定（缓存断点前块，不变量 #9）
且单测可复现。文件缺失 = fail-fast（`PromptNotFoundError`），不 fallback、不返回 ""。

规整规则（锁死缓存字节，设计 §3.2），依次：
1. 去 BOM：`encoding="utf-8-sig"` 读（`utf-8` 不自动剥 BOM，带 BOM 会污染首字节破缓存）；
2. 行尾归一：CRLF/CR → LF；
3. Unicode 归一化：`unicodedata.normalize("NFC", text)`（中文/全角标点的跨平台缝隙）；
4. 尾换行策略（方案 B，顺生态）：`text.rstrip("\n")`——吞掉文件尾部所有 LF、不补回。
   `.md` 允许带 ≤1 个尾换行（顺 Prettier / editorconfig），运行时字符串无尾换行
   （与旧常量逐字等价）。

`load_prompt` 不做任何动态插值（无 `.format`）；需运行期占位符的 prompt 不走此路径。
"""

from __future__ import annotations

import unicodedata
from pathlib import Path
from typing import Final

PROMPTS_DIR: Final = Path(__file__).parent / "prompts"
_CACHE: dict[str, str] = {}


class PromptNotFoundError(FileNotFoundError):
    """按 spec.name 找不到对应 `prompts/<name>.md`（import 期 fail-fast）。"""


def load_prompt(name: str) -> str:
    """按 spec.name 读 `prompts/<name>.md` → 规整 → 缓存 → 返回完整 UTF-8 文本。"""
    cached = _CACHE.get(name)
    if cached is not None:
        return cached

    path = PROMPTS_DIR / f"{name}.md"
    if not path.is_file():
        raise PromptNotFoundError(f"prompt not found for spec name {name!r}: {path}")

    raw = path.read_text(encoding="utf-8-sig")  # 去 BOM
    normalized = raw.replace("\r\n", "\n").replace("\r", "\n")  # 行尾归一
    normalized = unicodedata.normalize("NFC", normalized)  # Unicode 归一化
    text = normalized.rstrip("\n")  # 尾换行：吞掉、不补回

    _CACHE[name] = text
    return text


# 主写章 craft 教条文件名（灵感①）。它是散文教条、不是结构化产物 spec：
# 不入 SPECS、不进 prompt_hashes 金标准、不参与计数断言——但复用同一读盘/规整/缓存路径，
# 保证字节稳定（不变量 #9）。
CRAFT_DOCTRINE_NAME: Final = "write_craft"


def load_craft_doctrine() -> str:
    """读 `prompts/write_craft.md`（品类无关写章 craft 内核）→ 规整 → 缓存 → 返回。

    与 `load_prompt` 同一读盘/规整/缓存逻辑（DRY），文件缺失同样 fail-fast
    （`PromptNotFoundError`）。调用方在 import 期取常量即触发读盘。
    """
    return load_prompt(CRAFT_DOCTRINE_NAME)
