#!/usr/bin/env python3
"""Derives a reusable icon style guide from the reference icons in
svg_samples/.

Run via scripts/09-build-icon-style-guide.sh (locally -- these samples and
rsvg-convert/ImageMagick live on this host, not the VM). Output is
svg_samples/style_guide.json, consumed by scripts/lib/icon_remote.py's
"trace" mode to normalize generated app icons to the same visual language:
monochrome, flat, stroke-free, with a consistent padding inset and corner
roundedness.

Measurement approach per sample: rasterize at its intrinsic size (falling
back to 256x256) via rsvg-convert, then ask ImageMagick how much transparent
margin surrounds the drawn content (`convert -trim`). Averaging that margin
across every sample gives a padding_ratio; corner roundedness is estimated
from <rect rx="..."> usage relative to element size, which maps to potrace's
alphamax corner-smoothing parameter.
"""
import json
import re
import subprocess
import sys
import tempfile
import xml.etree.ElementTree as ET
from pathlib import Path

SAMPLES_DIR = Path(__file__).resolve().parent.parent.parent / "svg_samples"
OUTPUT_PATH = SAMPLES_DIR / "style_guide.json"
FALLBACK_SIZE = 256


def intrinsic_size(svg_path):
    try:
        root = ET.parse(svg_path).getroot()
    except ET.ParseError:
        return FALLBACK_SIZE, FALLBACK_SIZE
    w = root.get("width", "")
    h = root.get("height", "")
    m_w = re.match(r"([0-9.]+)", w)
    m_h = re.match(r"([0-9.]+)", h)
    if m_w and m_h:
        return int(float(m_w.group(1))), int(float(m_h.group(1)))
    return FALLBACK_SIZE, FALLBACK_SIZE


def measure_padding(svg_path, tmpdir):
    w, h = intrinsic_size(svg_path)
    w, h = min(w, 1024) or FALLBACK_SIZE, min(h, 1024) or FALLBACK_SIZE
    png = Path(tmpdir) / "sample.png"
    try:
        subprocess.run(
            ["rsvg-convert", "-w", str(w), "-h", str(h), str(svg_path), "-o", str(png)],
            check=True, capture_output=True,
        )
        out = subprocess.run(
            ["convert", str(png), "-trim", "info:"],
            check=True, capture_output=True, text=True,
        ).stdout
    except (subprocess.CalledProcessError, FileNotFoundError):
        return None
    # e.g. "sample.png PNG 18x14+3+5 24x24+0+0 8-bit sRGB ..."
    m = re.search(r"(\d+)x(\d+)\+(\d+)\+(\d+)", out)
    if not m:
        return None
    tw, th, x, y = (int(v) for v in m.groups())
    left = x / w
    top = y / h
    right = (w - (x + tw)) / w
    bottom = (h - (y + th)) / h
    return [left, right, top, bottom]


def measure_rect_rounding(svg_path):
    """rx (or ry) on a <rect>, relative to its own shorter side -- 0 means
    square corners, 0.5 means fully rounded (a pill/circle)."""
    try:
        root = ET.parse(svg_path).getroot()
    except ET.ParseError:
        return []
    ratios = []
    for el in root.iter():
        tag = el.tag.split("}")[-1]
        if tag != "rect":
            continue
        try:
            rw = float(el.get("width", 0))
            rh = float(el.get("height", 0))
            rx = float(el.get("rx", el.get("ry", 0)) or 0)
        except ValueError:
            continue
        short = min(rw, rh)
        if short > 0 and rx > 0:
            ratios.append(min(rx / (short / 2), 1.0))
    return ratios


def has_gradient_or_raster(svg_path):
    text = svg_path.read_text(errors="ignore")
    return bool(re.search(r"linearGradient|radialGradient|<image", text))


def has_explicit_color(svg_path):
    text = svg_path.read_text(errors="ignore")
    for m in re.finditer(r'fill="([^"]+)"', text):
        v = m.group(1).lower()
        if v not in ("none", "#000", "#000000", "black", "currentcolor"):
            return True
    for m in re.finditer(r"fill:\s*([^;\"']+)", text):
        v = m.group(1).strip().lower()
        if v not in ("none", "#000", "#000000", "black", "currentcolor"):
            return True
    return False


def has_stroke(svg_path):
    text = svg_path.read_text(errors="ignore")
    for m in re.finditer(r'stroke="([^"]+)"', text):
        if m.group(1).lower() != "none":
            return True
    for m in re.finditer(r"stroke-width:\s*([0-9.]+)", text):
        if float(m.group(1)) > 0:
            return True
    return False


def main():
    samples = sorted(SAMPLES_DIR.glob("*.svg"))
    if not samples:
        print(f"No .svg files found in {SAMPLES_DIR}", file=sys.stderr)
        sys.exit(1)

    padding_samples = []
    rounding_samples = []
    n_gradient = n_color = n_stroke = 0

    with tempfile.TemporaryDirectory() as tmpdir:
        for svg in samples:
            if has_gradient_or_raster(svg):
                n_gradient += 1
            if has_explicit_color(svg):
                n_color += 1
            if has_stroke(svg):
                n_stroke += 1
            pad = measure_padding(svg, tmpdir)
            if pad:
                padding_samples.extend(pad)
            rounding_samples.extend(measure_rect_rounding(svg))

    n = len(samples)
    padding_ratio = round(sum(padding_samples) / len(padding_samples), 4) if padding_samples else 0.06
    corner_rounding_ratio = (
        round(sum(rounding_samples) / len(rounding_samples), 4) if rounding_samples else 0.0
    )
    # potrace's -a/--alphamax: 0 = sharp corners, ~1.3341 = fully rounded.
    # Map our 0..1 rounding ratio onto potrace's useful range.
    alphamax = round(min(corner_rounding_ratio * 2.2, 1.3), 3)

    guide = {
        "source": "svg_samples/ (generated by scripts/09-build-icon-style-guide.sh)",
        "sample_count": n,
        "monochrome_fraction": round(1 - n_color / n, 4),
        "stroke_free_fraction": round(1 - n_stroke / n, 4),
        "flat_fill_fraction": round(1 - n_gradient / n, 4),
        "padding_ratio": padding_ratio,
        "corner_rounding_ratio": corner_rounding_ratio,
        "potrace_alphamax": alphamax,
        "rules": [
            "single flat fill color per icon (no gradients, no multi-tone art)",
            "no stroke outlines -- solid filled shapes only",
            f"~{round(padding_ratio * 100)}% empty margin inset on each side of the glyph",
            f"potrace alphamax={alphamax} to approximate the sample set's corner roundedness",
        ],
    }

    OUTPUT_PATH.write_text(json.dumps(guide, indent=2) + "\n")
    print(f">>> Wrote {OUTPUT_PATH}")
    print(json.dumps(guide, indent=2))


if __name__ == "__main__":
    main()
