#!/usr/bin/env python3
"""Audit the user-type axis frontmatter coverage (step-9 Phase 4).

Two checks across `.agent-src.uncondensed/skills/`:

1. **Orphan values** — every `recommended_for_user_types` value must have
   a corresponding `user-types/<value>.yml` config. Orphans are FATAL
   (exit 1) — they imply the runtime filter would tag prompts against a
   user-type with no documented identity.
2. **Unused configs** — every `user-types/*.yml` should be consumed by
   at least one skill. Unused configs are WARN-only (exit 0): seeding
   future identities ahead of consumption is allowed.

Writes a markdown report to `agents/runtime/reports/user-type-axis-audit.md` and
emits a one-line summary to stdout. Stdlib-only — no PyYAML dependency.

Usage:
    python3 scripts/audit_user_type_axis.py            # human report + exit code
    python3 scripts/audit_user_type_axis.py --quiet    # exit code only
"""

from __future__ import annotations

import re
import sys
from collections import defaultdict
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent.parent
SKILLS_ROOT = REPO_ROOT / ".agent-src.uncondensed" / "skills"
USER_TYPES_ROOT = REPO_ROOT / "user-types"
REPORT_PATH = REPO_ROOT / "agents" / "reports" / "user-type-axis-audit.md"

_FRONTMATTER_LINE = re.compile(
    r"^recommended_for_user_types:\s*\[([^\]]*)\]\s*$",
    re.MULTILINE,
)


def _declared_user_types() -> set[str]:
    """Read `user-types/*.yml` stems (one identity per YAML file)."""
    if not USER_TYPES_ROOT.is_dir():
        return set()
    return {p.stem for p in USER_TYPES_ROOT.glob("*.yml")}


def _scan_skill_values() -> dict[str, list[Path]]:
    """Map every frontmatter user-type value → list of declaring SKILL.md paths."""
    by_value: dict[str, list[Path]] = defaultdict(list)
    if not SKILLS_ROOT.is_dir():
        return by_value
    for skill_md in sorted(SKILLS_ROOT.rglob("SKILL.md")):
        text = skill_md.read_text(encoding="utf-8", errors="replace")
        # Frontmatter is the leading `---` block — strip everything after.
        if text.startswith("---\n"):
            end = text.find("\n---", 4)
            fm = text[4:end] if end >= 0 else text
        else:
            fm = text[:4096]
        match = _FRONTMATTER_LINE.search(fm)
        if not match:
            continue
        for raw in match.group(1).split(","):
            value = raw.strip().strip('"').strip("'")
            if value:
                by_value[value].append(skill_md.relative_to(REPO_ROOT))
    return by_value


def _render_report(
    declared: set[str],
    by_value: dict[str, list[Path]],
    orphans: set[str],
    unused: set[str],
) -> str:
    lines: list[str] = [
        "# User-type axis — frontmatter coverage audit",
        "",
        "Generated by `scripts/audit_user_type_axis.py` (step-9 Phase 4).",
        "",
        f"- Declared user-types (`user-types/*.yml`): **{len(declared)}**",
        f"- Distinct frontmatter values across skills: **{len(by_value)}**",
        f"- Orphans (FATAL): **{len(orphans)}**",
        f"- Unused configs (WARN): **{len(unused)}**",
        "",
        "## Coverage matrix",
        "",
        "| user-type | declared | consuming skills |",
        "| --- | --- | --- |",
    ]
    for ut in sorted(declared | set(by_value)):
        flag_declared = "yes" if ut in declared else "**no (orphan)**"
        count = len(by_value.get(ut, []))
        lines.append(f"| `{ut}` | {flag_declared} | {count} |")
    if orphans:
        lines.extend(["", "## Orphans", ""])
        for orphan in sorted(orphans):
            lines.append(f"- `{orphan}` — referenced by:")
            for path in by_value[orphan]:
                lines.append(f"  - `{path}`")
    if unused:
        lines.extend(["", "## Unused configs (WARN)", ""])
        for stem in sorted(unused):
            lines.append(f"- `user-types/{stem}.yml` has no consuming skill yet.")
    lines.append("")
    return "\n".join(lines)


def main(argv: list[str]) -> int:
    quiet = "--quiet" in argv
    declared = _declared_user_types()
    by_value = _scan_skill_values()
    used = set(by_value)
    orphans = used - declared
    unused = declared - used

    report = _render_report(declared, by_value, orphans, unused)
    REPORT_PATH.parent.mkdir(parents=True, exist_ok=True)
    REPORT_PATH.write_text(report, encoding="utf-8")

    if not quiet:
        sys.stdout.write(
            f"user-type-axis audit — declared={len(declared)} "
            f"used={len(used)} orphans={len(orphans)} unused={len(unused)}\n"
        )
        if orphans:
            sys.stdout.write(
                "  FAIL orphans: " + ", ".join(sorted(orphans)) + "\n"
            )
        if unused:
            sys.stdout.write(
                "  warn unused: " + ", ".join(sorted(unused)) + "\n"
            )
        sys.stdout.write(f"  report: {REPORT_PATH.relative_to(REPO_ROOT)}\n")

    return 1 if orphans else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
