#!/usr/bin/env python3
"""One-off — tier-bulk-retrofit (Phase 2.1 + 2.2 of road-to-feedback-consolidation).

Parses agents/settings/contexts/rule-trigger-matrix.md, emits tmp/tier-classification.md,
and inserts a `tier:` frontmatter key into every rule under
.agent-src.uncondensed/rules/. Idempotent — re-runs are a no-op when a rule
already declares the same tier value.

Lifecycle: scripts/_one_off/2026-05/. Purge eligible after 2026-08-04 per
docs/contracts/one-off-script-lifecycle.md.
"""
from __future__ import annotations

import re
import sys
from pathlib import Path

REPO = Path(__file__).resolve().parents[4]
MATRIX = REPO / "agents" / "contexts" / "rule-trigger-matrix.md"
RULES_DIR = REPO / ".agent-src.uncondensed" / "rules"
CONDENSED_RULES_DIR = REPO / ".agent-src" / "rules"
SPREADSHEET = REPO / "tmp" / "tier-classification.md"

VALID_TIERS = {"1", "2a", "2b", "3", "safety-floor", "mechanical-already"}


def parse_matrix() -> dict[str, tuple[str, str]]:
    """Return {rule_filename: (tier, notes)} from the matrix table."""
    out: dict[str, tuple[str, str]] = {}
    full = MATRIX.read_text(encoding="utf-8")
    # Slice between '## Matrix' and the next '## ' heading.
    start = full.find("\n## Matrix\n")
    if start == -1:
        sys.exit("matrix: '## Matrix' section not found")
    end = full.find("\n## ", start + 1)
    text = full[start:end] if end != -1 else full[start:]
    # Table rows look like: | `agent-authority.md` | always | 1468 | … | 3 | no | Priority index, … |
    row_re = re.compile(
        r"^\|\s*`([a-z0-9-]+\.md)`\s*\|"      # rule filename
        r"[^|]*\|"                              # type
        r"[^|]*\|"                              # raw
        r"[^|]*\|"                              # ext
        r"[^|]*\|"                              # trigger
        r"[^|]*\|"                              # obs
        r"[^|]*\|"                              # enforce
        r"[^|]*\|"                              # hook-cost
        r"\s*([^|]+?)\s*\|"                     # tier
        r"[^|]*\|"                              # dormant?
        r"\s*(.*?)\s*\|\s*$",                  # notes
        re.MULTILINE,
    )
    for m in row_re.finditer(text):
        name, tier, notes = m.group(1), m.group(2).strip(), m.group(3).strip()
        if tier not in VALID_TIERS:
            sys.exit(f"unknown tier '{tier}' for {name}")
        out[name] = (tier, notes)
    return out


def write_spreadsheet(classifications: dict[str, tuple[str, str]]) -> None:
    SPREADSHEET.parent.mkdir(parents=True, exist_ok=True)
    lines = [
        "# Tier classification — Phase 2.1 of road-to-feedback-consolidation",
        "",
        "Source: `agents/settings/contexts/rule-trigger-matrix.md` (manual classifications",
        "in `scripts/build_rule_trigger_matrix.py`'s `CLASSIFICATION` table).",
        "Generated by `scripts/_one_off/2026-05/_one_off_tier-retrofit.py`.",
        "",
        "Tier rubric: see `agents/settings/contexts/hardening-pattern.md`.",
        "",
        f"Total: {len(classifications)} rules.",
        "",
        "| Rule | Tier | Rationale |",
        "|---|---|---|",
    ]
    for name in sorted(classifications):
        tier, notes = classifications[name]
        lines.append(f"| `{name}` | `{tier}` | {notes} |")
    SPREADSHEET.write_text("\n".join(lines) + "\n", encoding="utf-8")


def parse_frontmatter(text: str) -> tuple[dict[str, str], str, str]:
    """Return (kv, raw_block, body). raw_block excludes the --- fences."""
    if not text.startswith("---\n"):
        return {}, "", text
    end = text.find("\n---\n", 4)
    if end == -1:
        return {}, "", text
    raw = text[4:end]
    body = text[end + 5 :]
    kv: dict[str, str] = {}
    for line in raw.splitlines():
        if ":" in line:
            k, _, v = line.partition(":")
            kv[k.strip()] = v.strip()
    return kv, raw, body


def apply_tier(rule_path: Path, tier: str) -> str:
    """Return one of: 'unchanged', 'inserted', 'updated'.

    Tier is always written as a quoted string in YAML (`tier: "<value>"`) so the
    schema enum check (string-only) holds for numeric tiers like `1` and `3`.
    """
    text = rule_path.read_text(encoding="utf-8")
    kv, raw, body = parse_frontmatter(text)
    if not raw:
        sys.exit(f"{rule_path}: no frontmatter found")
    existing_raw = kv.get("tier")
    existing = existing_raw.strip('"').strip("'") if existing_raw else None
    quoted = f'"{tier}"'
    target_line = f"tier: {quoted}"
    if existing == tier and existing_raw == quoted:
        return "unchanged"
    new_lines: list[str] = []
    inserted = False
    for line in raw.splitlines():
        new_lines.append(line)
        if not inserted and line.startswith("type:"):
            new_lines.append(target_line)
            inserted = True
    if existing is not None:
        new_lines = [
            l if not l.lstrip().startswith("tier:") else target_line
            for l in new_lines
        ]
        seen_tier = False
        deduped: list[str] = []
        for l in new_lines:
            if l == target_line:
                if seen_tier:
                    continue
                seen_tier = True
            deduped.append(l)
        new_lines = deduped
        result = "updated" if existing != tier or existing_raw != quoted else "unchanged"
    else:
        if not inserted:
            new_lines.insert(0, target_line)
        result = "inserted"
    new_raw = "\n".join(new_lines)
    rule_path.write_text(f"---\n{new_raw}\n---\n{body}", encoding="utf-8")
    return result


def main() -> int:
    classifications = parse_matrix()
    if len(classifications) != 58:
        sys.exit(f"expected 58 rules in matrix, got {len(classifications)}")

    on_disk = {p.name for p in RULES_DIR.glob("*.md")}
    missing = on_disk - classifications.keys()
    extra = classifications.keys() - on_disk
    if missing or extra:
        sys.exit(f"matrix/disk mismatch: missing={missing} extra={extra}")

    write_spreadsheet(classifications)

    counts: dict[str, int] = {"unchanged": 0, "inserted": 0, "updated": 0}
    mirror_counts = {"unchanged": 0, "inserted": 0, "updated": 0, "skipped": 0}
    for name, (tier, _) in classifications.items():
        result = apply_tier(RULES_DIR / name, tier)
        counts[result] += 1
        condensed = CONDENSED_RULES_DIR / name
        if condensed.exists():
            mirror_counts[apply_tier(condensed, tier)] += 1
        else:
            mirror_counts["skipped"] += 1
    print(
        f"tier-retrofit: spreadsheet={SPREADSHEET.relative_to(REPO)} "
        f"src(unchanged={counts['unchanged']} inserted={counts['inserted']} "
        f"updated={counts['updated']}) "
        f"mirror(unchanged={mirror_counts['unchanged']} inserted={mirror_counts['inserted']} "
        f"updated={mirror_counts['updated']} skipped={mirror_counts['skipped']})"
    )
    return 0


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