"""STEP_MANIFEST를 Markdown 표로 dump.

Wave 0 / F01의 산출물. 문서의 step 숫자/분포가 manifest와 drift하지 않도록,
문서용 generated baseline을 스크립트로 재생성 가능하게 한다.

사용법(저장소 루트에서):
  backend/.venv/bin/python backend/scripts/dump_step_manifest.py > docs/architecture/_step_manifest.generated.md

검증(drift 있으면 non-zero exit):
  backend/.venv/bin/python backend/scripts/dump_step_manifest.py | diff - docs/architecture/_step_manifest.generated.md
"""

from __future__ import annotations

import sys
from collections import Counter
from pathlib import Path

# 프로젝트 루트를 sys.path에 추가 (scripts는 backend/scripts/, manifest는 backend/app/)
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "backend"))

from app.core.applicability import APPLICABILITY_VALIDATORS  # noqa: E402
from app.core.step_manifest import STEP_MANIFEST, get_ordered_steps  # noqa: E402


# manifest의 if_* 규칙이 모두 validator registry에 등록돼 있는지 검증 (Codex W0 리뷰 Medium 2).
# always/disabled/on_demand는 validator 없이 해석되므로 제외.
_SIMPLE_APPLICABILITIES = {"always", "disabled", "on_demand"}
_KNOWN_IF_RULES = set(APPLICABILITY_VALIDATORS.keys())
_USED = {info.get("applicability") for info in STEP_MANIFEST.values()}
_UNKNOWN = _USED - _SIMPLE_APPLICABILITIES - _KNOWN_IF_RULES
if _UNKNOWN:
    raise SystemExit(
        f"[dump_step_manifest] unknown applicability in STEP_MANIFEST: {sorted(_UNKNOWN)}. "
        f"Register in APPLICABILITY_VALIDATORS (app/core/applicability.py) or use always/disabled/on_demand."
    )


def _count(key: str) -> Counter:
    return Counter(info.get(key, "<missing>") for info in STEP_MANIFEST.values())


def _count_nested(category_key: str, nested_key: str) -> dict:
    """category 별로 nested_key 분포를 센다."""
    out: dict = {}
    for info in STEP_MANIFEST.values():
        cat = info.get(category_key, "<missing>")
        nested = info.get(nested_key, "<missing>")
        out.setdefault(cat, Counter())[nested] += 1
    return out


_ACTIVE_FLAG_MAP = {
    "if_planning_doc": "has_planning_doc",
    "if_has_outlooks": "has_outlooks",
}


def _active_step_ids(flags: dict[str, bool] | None = None) -> list[str]:
    """현재 기본 active 경로에 포함되는 step_id 목록.

    - `always` + `flags가 True인 if_*` step만 active.
    - `disabled`/`on_demand`는 무시.
    - 상단 _UNKNOWN 검증이 이미 unknown applicability를 fail-fast함.
    - 다만 if_* 규칙이 registry엔 있는데 이 flag map엔 없는 경우 fail-fast (Codex W0 리뷰 Medium 2).

    flags: has_planning_doc / has_outlooks (default False)
    """
    flags = flags or {}
    ok: list[str] = []
    for sid, info in STEP_MANIFEST.items():
        app = info.get("applicability")
        if app == "always":
            ok.append(sid)
        elif app in _ACTIVE_FLAG_MAP:
            if flags.get(_ACTIVE_FLAG_MAP[app]):
                ok.append(sid)
        elif app in _KNOWN_IF_RULES:
            raise SystemExit(
                f"[dump_step_manifest] if_* rule '{app}' registered in APPLICABILITY_VALIDATORS "
                f"but not mapped in _ACTIVE_FLAG_MAP (step '{sid}'). Extend _ACTIVE_FLAG_MAP."
            )
        # disabled / on_demand는 skip
    return ok


def _md_table(headers: list[str], rows: list[list[str]]) -> str:
    lines = ["| " + " | ".join(headers) + " |"]
    lines.append("|" + "|".join(["---"] * len(headers)) + "|")
    for row in rows:
        lines.append("| " + " | ".join(row) + " |")
    return "\n".join(lines)


def main() -> None:
    total = len(STEP_MANIFEST)
    cat_count = _count("category")
    app_count = _count("applicability")
    type_count = _count("step_type")
    life_count = _count("lifecycle")
    cat_app = _count_nested("category", "applicability")

    active_default = _active_step_ids({"has_planning_doc": True, "has_outlooks": True})

    print("# STEP_MANIFEST Generated Baseline")
    print("")
    print("> 자동 생성 파일. 직접 편집 금지.")
    print(">")
    print("> 재생성 (저장소 루트에서): `backend/.venv/bin/python backend/scripts/dump_step_manifest.py > docs/architecture/_step_manifest.generated.md`")
    print("")
    print("## 총량")
    print("")
    print(f"- **총 step 수**: {total}")
    print(f"- **기본 active 경로** (has_planning_doc + has_outlooks): {len(active_default)}")
    print("")
    print("## Category 분포")
    print("")
    print(_md_table(
        ["category", "count"],
        [[k, str(v)] for k, v in sorted(cat_count.items())],
    ))
    print("")
    print("## Applicability 분포")
    print("")
    print(_md_table(
        ["applicability", "count"],
        [[k, str(v)] for k, v in sorted(app_count.items())],
    ))
    print("")
    print("## step_type 분포")
    print("")
    print(_md_table(
        ["step_type", "count"],
        [[k, str(v)] for k, v in sorted(type_count.items())],
    ))
    print("")
    print("## lifecycle 분포")
    print("")
    print(_md_table(
        ["lifecycle", "count"],
        [[k, str(v)] for k, v in sorted(life_count.items())],
    ))
    print("")
    print("## Category × Applicability 교차표")
    print("")
    all_apps = sorted({a for counter in cat_app.values() for a in counter})
    headers = ["category"] + all_apps + ["total"]
    rows = []
    for cat in sorted(cat_app):
        counter = cat_app[cat]
        row = [cat] + [str(counter.get(a, 0)) for a in all_apps] + [str(sum(counter.values()))]
        rows.append(row)
    print(_md_table(headers, rows))
    print("")
    print("## Ordered Step 목록")
    print("")
    print(_md_table(
        ["order", "step_id", "category", "applicability", "step_type", "lifecycle", "default_model", "depends_on"],
        [
            [
                str(s["order"]),
                s["step_id"],
                s.get("category", ""),
                s.get("applicability", ""),
                s.get("step_type", ""),
                s.get("lifecycle", ""),
                s.get("default_model", ""),
                ", ".join(s.get("depends_on", [])) or "—",
            ]
            for s in get_ordered_steps()
        ],
    ))


if __name__ == "__main__":
    main()
