"""从 web/frameworks.json 重建 web/payloads.json（不调 LLM）——确定性。

顺带两个守卫修正：
  · 组件颗的 parent.how_id 必须指向同帖一个 how 颗，否则降级为 orphan 主颗（role=主, parent=null）。
  · 组件颗 payload 的「出自」用所属 how 的 title（而非裸 id）。
用法：python3 scripts/rebuild_payloads.py
"""
from __future__ import annotations

import json
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
FW = ROOT / "web/frameworks.json"
PL = ROOT / "web/payloads.json"
TYPE2ATTR = {"how": "how", "what": "what", "why": "why"}
KINDS = {"子集", "多维关系", "序列"}
KIND_FIX = {"多维度关系": "多维关系", "多维": "多维关系", "集合": "子集", "序列型": "序列"}  # LLM 偶发变体归一


def _sections(blocks) -> list:
    out = []
    for b in blocks or []:
        head = b.get("小标题") or ""
        form = b.get("形式")
        out.append(f"【{head}】" + (f"（{form}）" if form else ""))
        if b.get("内容"):
            out.append(f"  {b['内容']}")
        for it in b.get("条目") or []:
            word = it.get("词") or it.get("要素") or ""
            cue = it.get("选择线索")
            line = f"  - {word}：{it.get('说明','')}" if word else f"  - {it.get('说明','')}"
            if cue:
                line += f"（选用：{cue}）"
            out.append(line)
    return out


def content(k: dict):
    """How → 拍平字符串；What → 结构化实体对象 {name,kind,维度拆分规则,body[]}；Why → 结构化对象 {name,desc}。"""
    t = k["type"]
    if t == "how":
        L = [f"目标：{k.get('purpose','')}"]
        for i, s in enumerate(k.get("steps", []), 1):
            L += [f"步骤{i}", f"  输入：{s.get('input','')}",
                  f"  方法：{s.get('directive','')}", f"  产出：{s.get('output','')}"]
        return "\n".join(L)
    if t == "what":  # 结构化实体；概要走 custom_ext
        return {"name": k.get("title") or "", "kind": k.get("kind"),
                "维度拆分规则": k.get("维度拆分规则") if k.get("kind") == "子集" else None,
                "body": [{"item_name": it.get("item_name", ""), "item_desc": it.get("item_desc", ""),
                          "作用域": it.get("作用域") or []} for it in (k.get("body") or [])]}
    return {"name": k.get("title") or "", "desc": k.get("阐述") or ""}


def main() -> None:
    d = json.loads(FW.read_text(encoding="utf-8"))
    payloads = []
    demoted = 0
    for p in d["posts"]:
        ks = p["knowledges"]
        how_ids = {k["id"] for k in ks if k["type"] == "how"}
        how_titles = {k["id"]: k.get("title") for k in ks if k["type"] == "how"}
        for k in ks:
            k["业务阶段"] = [b for b in (k.get("业务阶段") or []) if b in ("灵感", "选题", "脚本")]
            if k.get("type") == "what" and k.get("kind") not in KINDS:  # what.kind 受控值归一
                k["kind"] = KIND_FIX.get((k.get("kind") or "").strip(), k.get("kind"))
            for s in k.get("steps", []):
                if s.get("创作阶段") not in ("定向", "构思", "结构", "成文", "打磨"):
                    s["创作阶段"] = None
            if k.get("role") == "组件" and (k.get("parent") or {}).get("how_id") not in how_ids:
                k["role"] = "主"
                k["parent"] = None
                demoted += 1
        for k in ks:
            t = k["type"]
            scopes, seen = [], set()
            def add(lst):
                for sc in lst:
                    key = (sc["scope_type"], sc["value"])
                    if key not in seen:
                        seen.add(key)
                        scopes.append({"scope_type": sc["scope_type"], "value": sc["value"]})
            if t == "how":
                for s in k.get("steps", []):
                    add(s.get("作用域", []))
            else:
                add(k.get("作用域", []))
            ext = [{"key": "业务阶段", "type": "str", "value": v} for v in (k.get("业务阶段") or [])]
            if t == "what" and k.get("概要"):
                ext.append({"key": "概要", "type": "str", "value": k["概要"]})
            if t == "how":
                cs, cseen = [], set()
                for s in k.get("steps", []):
                    c = s.get("创作阶段")
                    if c and c not in cseen:
                        cseen.add(c)
                        cs.append(c)
                ext += [{"key": "创作阶段", "type": "str", "value": v} for v in cs]
                ext += [{"key": "动作", "type": "str", "value": s["动作"]} for s in k.get("steps", []) if s.get("动作")]
            if k.get("role") == "组件" and k.get("parent"):
                par = k["parent"]
                ext.append({"key": "出自", "type": "str",
                            "value": f"{how_titles.get(par.get('how_id'), par.get('how_id'))} 第{par.get('step')}步"})
            payloads.append({
                "source": {"id": p.get("source_id") or f"xhs_{p['post_id']}", "source_type": "post", "title": p.get("title", ""),
                           "author": "", "source_metadata": {"platform": p.get("platform"), "url": p.get("url")}},
                "title": k.get("title"),
                "content": (lambda c: c if isinstance(c, str) else json.dumps(c, ensure_ascii=False))(content(k)),
                "dim_creations": ["创作"],
                "dim_attributes": [TYPE2ATTR.get(t, "how")], "scopes": scopes, "custom_ext": ext})
    FW.write_text(json.dumps(d, ensure_ascii=False, indent=1), encoding="utf-8")
    PL.write_text(json.dumps(payloads, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"rebuilt {len(payloads)} payloads; 降级组件→orphan: {demoted}")


if __name__ == "__main__":
    main()
