"""audit-gate.py: run one html-artifact-audit probe and pass only on zero unexempted findings.

The probe's own report truncates each finding's locations ("+N more"), so this
runs the probe in-process and captures the full finding map before it prints.
An exemption from audit-exemptions.json removes a finding only when EVERY one of
its occurrences matches the exemption's formula at that occurrence's viewport.

Usage (skills venv): python3 -I scripts/audit-gate.py <linebreaks|visual|fit|consistency> [config]
"""
import importlib.util
import json
import pathlib
import re
import sys

SKILL = pathlib.Path.home() / ".claude" / "skills" / "html-artifact-audit" / "scripts"
ROOT = pathlib.Path(__file__).resolve().parent.parent
sys.path.insert(0, str(SKILL))
import _common as C  # noqa: E402  (skill module, imported after the path is set)


def stage_scale(w, h, stage):
    sw, sh, cap = stage
    if w < 769 or h < 561 or h >= w:  # phone, short landscape, portrait: reflow, no stage scale
        return None
    return min(w / sw, h / sh, cap)


def matches(exemption, message, wheres):
    m = re.match(exemption["pattern"], message)
    if not m:
        return False
    reported = float(m.group(m.re.groups))
    for where in wheres:
        vm = re.match(r"(\d+)x(\d+)", where)
        if not vm:
            return False
        k = stage_scale(int(vm.group(1)), int(vm.group(2)), exemption["stage"])
        if k is None or k >= 1:
            return False
        expected = exemption["parent_padding_stage_px"] * (1 - k)
        # The probe rounds; the formula must land within tolerance of that rounded value.
        if abs(reported - expected) > exemption["tolerance_px"] + 0.5:
            return False
    return True


def main():
    probe = sys.argv[1]
    config = sys.argv[2] if len(sys.argv) > 2 else str(ROOT / "audit.config.json")
    exemptions = json.loads((ROOT / "audit-exemptions.json").read_text(encoding="utf-8")).get(probe, [])

    captured = {}

    def capture(title, grouped, scope, show=4):
        captured["grouped"], captured["scope"] = grouped, scope
        return 0

    C.report = capture
    spec = importlib.util.spec_from_file_location("probe", SKILL / f"audit-{probe}.py")
    if spec is None or spec.loader is None:
        print(f"FAIL {probe}: probe script not found at {SKILL / f'audit-{probe}.py'}")
        return 1
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    mod.C.report = capture
    sys.argv = [str(SKILL / f"audit-{probe}.py"), config]
    mod.main()

    grouped = captured.get("grouped")
    if grouped is None:
        print(f"FAIL {probe}: probe produced no finding map (it may have skipped; check its config)")
        return 1
    items = grouped.items() if hasattr(grouped, "items") else grouped
    kept, exempted = [], []
    for message, wheres in items:
        wheres = list(wheres)
        hit = next((e for e in exemptions if matches(e, message, wheres)), None)
        (exempted if hit else kept).append((message, wheres, hit["id"] if hit else None))

    for message, wheres, eid in exempted:
        print(f"  EXEMPT [{eid}] {message}  ({len(wheres)} occurrence(s), all match the formula)")
    for message, wheres, _ in kept:
        print(f"  FAIL {message}\n      seen at: {', '.join(wheres)}")
    print(f"scope: {captured.get('scope')}")
    if kept:
        print(f"AUDIT {probe.upper()} FAILED: {len(kept)} distinct problem(s)")
        return 1
    print(f"AUDIT {probe.upper()} PASSED: 0 unexempted problems ({len(exempted)} exempted with recorded reason)")
    return 0


if __name__ == "__main__":
    sys.exit(main())
