#!/usr/bin/env python3
"""Build full Markdown pages and keyword routes for the thinking-model library."""

from __future__ import annotations

import csv
import json
import re
from pathlib import Path


ROOT = Path(__file__).resolve().parents[3]
WORK_DIR = ROOT / "work" / "2026-05-29-thinking-models-knowledge-base"
STABLE_DIR = ROOT / "standards-stack" / "llm-wiki" / "thinking-models"
MODELS_DIR = STABLE_DIR / "models"
CATALOG_PATH = STABLE_DIR / "thinking-model-catalog.csv"
ROUTER_PATH = STABLE_DIR / "thinking-model-router.csv"
SLIDES_PATH = WORK_DIR / "extracted" / "slides.json"

ROUTE_MODEL_ALIASES = {
    "PDCA": "PDCA原则&经验积累型学习",
    "MECE": "MECE分析法",
    "5Why": "5Why思考法",
    "5W2H": "5W2H问题分析法",
    "SWOT": "SWOT分析",
    "3C": "3C分析",
    "PEST": "PEST分析模型",
    "五力分析": "五力分析模型",
    "波特三战略": "波特的3个基本战略",
    "HOOK": "上瘾（HOOK）模型",
    "MVP": "最小可行性产品（MVP）",
    "情绪ABC": "情绪ABC理论",
    "GROW": "GROW教练模型",
    "麦肯锡7S": "麦肯锡7S模型",
    "团队绩效模型": "德雷克斯勒-西贝特团队绩效模型",
}


def slugify(value: str) -> str:
    value = re.sub(r"[\\/:*?\"<>|]+", "-", value)
    value = re.sub(r"\s+", "-", value.strip())
    value = value.strip("-")
    return value or "model"


def read_catalog() -> list[dict[str, str]]:
    with CATALOG_PATH.open(newline="", encoding="utf-8") as fh:
        return list(csv.DictReader(fh))


def read_router() -> list[dict[str, str]]:
    with ROUTER_PATH.open(newline="", encoding="utf-8") as fh:
        return list(csv.DictReader(fh))


def read_slides() -> dict[int, dict[str, object]]:
    data = json.loads(SLIDES_PATH.read_text(encoding="utf-8"))
    return {int(item["slide_no"]): item for item in data}


def clean_paragraphs(paragraphs: list[str]) -> list[str]:
    cleaned: list[str] = []
    previous = None
    for item in paragraphs:
        text = re.sub(r"\s+", " ", item).strip()
        if not text:
            continue
        if text == previous:
            continue
        cleaned.append(text)
        previous = text
    return cleaned


def route_keywords(row: dict[str, str]) -> str:
    parts = [
        row["model"],
        row["scenario"],
        row["when_to_use"],
        row["expected_output"],
        row["answer_shape"],
    ]
    text = " ".join(parts)
    tokens = []
    for token in re.split(r"[\s,，;；、/()（）&]+", text):
        token = token.strip("：:。.")
        if len(token) >= 2 and token not in tokens:
            tokens.append(token)
    return ";".join(tokens[:18])


def model_markdown(row: dict[str, str], slide: dict[str, object]) -> str:
    paragraphs = clean_paragraphs(slide.get("paragraphs", []))  # type: ignore[arg-type]
    lines = [
        f"# {row['model_id']} {row['model']}",
        "",
        "## 元数据",
        "",
        f"- model_id: `{row['model_id']}`",
        f"- part: `{row['part']}`",
        f"- source_slide: `{row['slide_no']}`",
        f"- scenario: `{row['scenario']}`",
        f"- when_to_use: {row['when_to_use']}",
        f"- expected_output: {row['expected_output']}",
        f"- answer_shape: {row['answer_shape']}",
        f"- source_confidence: {row['source_confidence']}",
        "",
        "## 问答词路",
        "",
        f"- route_keywords: {route_keywords(row)}",
        f"- prompt_path: 先确认问题是否属于 `{row['scenario']}`，再用 `{row['model']}` 产出 `{row['expected_output']}`。",
        f"- answer_contract: {row['answer_shape']}",
        "",
        "## PPT 原文抽取",
        "",
    ]
    if paragraphs:
        lines.extend(f"- {item}" for item in paragraphs)
    else:
        lines.append("- no text extracted")
    lines.append("")
    return "\n".join(lines)


def write_model_pages(catalog: list[dict[str, str]], slides: dict[int, dict[str, object]]) -> list[dict[str, str]]:
    MODELS_DIR.mkdir(parents=True, exist_ok=True)
    index_rows = []
    for row in catalog:
        slide_no = int(row["slide_no"])
        slide = slides[slide_no]
        filename = f"{row['model_id']}-{slugify(row['model'])}.md"
        path = MODELS_DIR / filename
        path.write_text(model_markdown(row, slide), encoding="utf-8")
        index_rows.append({**row, "markdown_path": f"models/{filename}"})
    return index_rows


def write_full_markdown(index_rows: list[dict[str, str]], slides: dict[int, dict[str, object]]) -> None:
    lines = [
        "# 150 个思维模型全量 Markdown",
        "",
        "本文件是 `150个思维模型&麦肯锡战略思维.pptx` 的稳定 Markdown 真源。它保留每个模型对应 PPT 页的文本抽取，并补充问答路由字段，供 Codex 后续按模型回答问题。",
        "",
        "## 使用方式",
        "",
        "- 查单个模型：进入 `models/<model>.md`。",
        "- 查场景路由：看 `thinking-model-question-routes.md`。",
        "- 做问答：先读 `thinking-model-keyword-routes.csv` 或 `thinking-model-router.csv` 选模型，再读对应模型 Markdown 的 PPT 原文。",
        "",
        "## 全量模型目录",
        "",
    ]
    for row in index_rows:
        lines.append(f"- [{row['model_id']} {row['model']}]({row['markdown_path']}) - {row['part']} / {row['scenario']}")
    lines.append("")
    lines.append("## 全量正文")
    lines.append("")
    for row in index_rows:
        slide = slides[int(row["slide_no"])]
        lines.append(model_markdown(row, slide))
        lines.append("---")
        lines.append("")
    (STABLE_DIR / "full-content.md").write_text("\n".join(lines), encoding="utf-8")


def write_model_index(index_rows: list[dict[str, str]]) -> None:
    fields = [
        "model_id",
        "slide_no",
        "part",
        "model",
        "scenario",
        "when_to_use",
        "expected_output",
        "answer_shape",
        "markdown_path",
        "source_confidence",
    ]
    with (STABLE_DIR / "thinking-model-index.csv").open("w", encoding="utf-8", newline="") as fh:
        writer = csv.DictWriter(fh, fieldnames=fields)
        writer.writeheader()
        writer.writerows(index_rows)


def write_keyword_routes(index_rows: list[dict[str, str]]) -> None:
    fields = [
        "model_id",
        "model",
        "part",
        "scenario",
        "route_keywords",
        "question_triggers",
        "prompt_path",
        "expected_output",
        "markdown_path",
    ]
    rows = []
    for row in index_rows:
        rows.append(
            {
                "model_id": row["model_id"],
                "model": row["model"],
                "part": row["part"],
                "scenario": row["scenario"],
                "route_keywords": route_keywords(row),
                "question_triggers": row["when_to_use"],
                "prompt_path": f"用 `{row['model']}` 先判断适用场景，再按 `{row['answer_shape']}` 输出。",
                "expected_output": row["expected_output"],
                "markdown_path": row["markdown_path"],
            }
        )
    with (STABLE_DIR / "thinking-model-keyword-routes.csv").open("w", encoding="utf-8", newline="") as fh:
        writer = csv.DictWriter(fh, fieldnames=fields)
        writer.writeheader()
        writer.writerows(rows)


def match_route_model(name: str, by_name: dict[str, dict[str, str]]) -> dict[str, str] | None:
    if name in by_name:
        return by_name[name]

    alias = ROUTE_MODEL_ALIASES.get(name)
    if alias and alias in by_name:
        return by_name[alias]

    for model, row in by_name.items():
        if name == model or name in model or model in name:
            return row
    return None


def write_question_routes(router: list[dict[str, str]], index_rows: list[dict[str, str]]) -> None:
    by_name = {row["model"]: row for row in index_rows}
    lines = [
        "# 思维模型问答词路",
        "",
        "本文件把用户问题映射到模型组合。回答时不要只报模型名，要进入对应 `models/*.md` 或 `full-content.md` 读取 PPT 原文，再用模型推导结论。",
        "",
        "## 总规则",
        "",
        "1. 先用用户问题中的动词和对象判断场景。",
        "2. 普通问题选 1-3 个模型；复杂问题最多 3-5 个。",
        "3. 模型提供结构，不替代业务证据。",
        "4. 输出必须包含：选用模型、为什么选、模型推导、结论/动作、待验证项。",
        "",
    ]
    for route in router:
        lines.append(f"## {route['scenario']}")
        lines.append("")
        lines.append(f"- target_result: {route['target_result']}")
        lines.append(f"- preferred_models: {route['preferred_models']}")
        lines.append("- model_paths:")
        for name in route["preferred_models"].split(";"):
            name = name.strip()
            matched = match_route_model(name, by_name)
            if matched:
                lines.append(f"  - `{matched['model']}` -> `{matched['markdown_path']}`")
            else:
                lines.append(f"  - `{name}` -> check `thinking-model-keyword-routes.csv`")
        lines.append("")
    (STABLE_DIR / "thinking-model-question-routes.md").write_text("\n".join(lines), encoding="utf-8")


def main() -> int:
    catalog = read_catalog()
    router = read_router()
    slides = read_slides()
    index_rows = write_model_pages(catalog, slides)
    write_full_markdown(index_rows, slides)
    write_model_index(index_rows)
    write_keyword_routes(index_rows)
    write_question_routes(router, index_rows)
    print(json.dumps({"models": len(index_rows), "stable_dir": str(STABLE_DIR)}, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
