#!/usr/bin/env python3
"""Add ``aria-hidden="true"`` to decorative <svg> tags in apps/rep templates.

Run from repo root:
    python scripts/a11y_aria_hidden_rep.py            # mutate files
    python scripts/a11y_aria_hidden_rep.py --check    # exit 1 if any decorative <svg> still missing

Definition of "decorative" used here:
- The <svg> opening tag does NOT already declare ``aria-hidden=`` or ``role="img"``.
- The <svg> is not commented out (we skip lines inside ``{% comment %}…{% endcomment %}``).

Caller is expected to manually inspect cases where an icon-only button has no
text label — those need ``aria-label`` instead, and should keep their <svg>
non-aria-hidden so the label semantic still holds. The script does not try to
infer that.
"""
from __future__ import annotations

import argparse
import re
import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent
TARGET_GLOB = "apps/rep/**/*.html"

# <svg followed by attributes, until first '>' (single-line opening tag).
# We don't try to handle <svg ... > spread across multiple lines — those exist
# but are rare in this codebase; manual touch-up if any slip through.
SVG_OPEN_RE = re.compile(r"<svg\b([^>]*)>", re.IGNORECASE)
COMMENT_OPEN = "{% comment %}"
COMMENT_CLOSE = "{% endcomment %}"


def needs_aria(attrs: str) -> bool:
    if "aria-hidden" in attrs.lower():
        return False
    if re.search(r'role\s*=\s*["\']img["\']', attrs, re.IGNORECASE):
        return False
    return True


def patch_line(line: str) -> tuple[str, int]:
    """Return (new_line, count_added)."""
    count = 0

    def repl(match):
        nonlocal count
        attrs = match.group(1)
        if not needs_aria(attrs):
            return match.group(0)
        count += 1
        # Insert before closing >. Trim trailing whitespace inside attrs to
        # keep the rendered output tidy.
        return f"<svg{attrs.rstrip()} aria-hidden=\"true\">"

    return SVG_OPEN_RE.sub(repl, line), count


def patch_file(path: Path) -> tuple[int, list[str]]:
    text = path.read_text(encoding="utf-8")
    out_lines = []
    in_comment = False
    additions = 0
    for line in text.splitlines(keepends=True):
        if not in_comment and COMMENT_OPEN in line:
            in_comment = True
            out_lines.append(line)
            if COMMENT_CLOSE in line:
                in_comment = False
            continue
        if in_comment:
            out_lines.append(line)
            if COMMENT_CLOSE in line:
                in_comment = False
            continue
        new_line, n = patch_line(line)
        additions += n
        out_lines.append(new_line)
    new_text = "".join(out_lines)
    return additions, [new_text]


def main(argv=None):
    parser = argparse.ArgumentParser()
    parser.add_argument("--check", action="store_true",
                        help="Exit non-zero if any decorative <svg> still lacks aria-hidden.")
    args = parser.parse_args(argv)

    files = sorted(REPO_ROOT.glob(TARGET_GLOB))
    total_additions = 0
    files_touched = 0
    for path in files:
        additions, (new_text,) = patch_file(path)
        if additions == 0:
            continue
        if args.check:
            total_additions += additions
            files_touched += 1
            continue
        path.write_text(new_text, encoding="utf-8")
        total_additions += additions
        files_touched += 1
        print(f"  {path.relative_to(REPO_ROOT)}  +{additions}")

    if args.check:
        if total_additions:
            print(f"a11y: {total_additions} decorative <svg> tags missing aria-hidden across {files_touched} files",
                  file=sys.stderr)
            return 1
        print("a11y: every decorative <svg> in apps/rep has aria-hidden")
        return 0

    print(f"\na11y: added aria-hidden=\"true\" to {total_additions} <svg> tags across {files_touched} files")
    return 0


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