#!/usr/bin/env python3
"""Runs on THIS host only -- builds a local staging tree mirroring
~/.local/share/icons/{<exact source theme>,hicolor,breeze-dark}/... from
the duotone masters cached by 14-generate-duotone-icons.sh, colorized by
the target's live matugen palette (dark stop: primary_container, light
stop: on_primary_container by default).

Two master kinds, handled differently -- see lib/icon_duotone_vector.py's
docstring for why:

  generated_icons/duotone_vector/<name>.svg (preferred, SVG-sourced icons)
      Real vector shapes with lightness-placeholder fills. Colorizing is
      pure text substitution; the result is installed AS the scalable/
      slot (and the exact-override slot, when that's also .svg) and
      additionally rasterized via rsvg-convert for the raster size mirrors
      -- all genuine renders, nothing embedded.

  generated_icons/duotone/<name>.png (fallback, PNG-sourced icons)
      Grayscale-with-alpha raster, colorized via ImageMagick -clut. These
      icons have no vector source to recolor, so there is no scalable/
      slot for them at all here -- only the exact-override path (when its
      extension is .png) and the hicolor/breeze-dark raster size
      directories get installed.
"""
import argparse
import json
import re
import subprocess
import tempfile
from pathlib import Path

import icon_duotone_vector as vector

RASTER_SIZES = (48, 64, 128)
INSTALL_THEMES = ["hicolor", "breeze-dark"]


def parse_size_from_relpath(relpath):
    for part in relpath.split("/"):
        if part == "scalable":
            return None
        m = re.match(r"(\d+)", part)
        if m:
            return int(m.group(1))
    return None


def build_clut(dark_hex, light_hex, tmpdir):
    clut = Path(tmpdir) / "clut.png"
    subprocess.run(
        ["convert", "-size", "256x1", f"gradient:{dark_hex}-{light_hex}", str(clut)],
        check=True, capture_output=True,
    )
    return clut


def colorize_raster(gray_master_path, clut_path, out_png, size, tmpdir):
    resized = Path(tmpdir) / "resized.png"
    subprocess.run(["convert", str(gray_master_path), "-resize", f"{size}x{size}", str(resized)],
                    check=True, capture_output=True)
    alpha = Path(tmpdir) / "alpha.png"
    subprocess.run(["convert", str(resized), "-alpha", "extract", str(alpha)],
                    check=True, capture_output=True)
    colored = Path(tmpdir) / "colored.png"
    subprocess.run(["convert", str(resized), "-alpha", "off", str(clut_path), "-clut", str(colored)],
                    check=True, capture_output=True)
    subprocess.run(
        ["convert", str(colored), str(alpha), "-alpha", "off", "-compose", "CopyOpacity",
         "-composite", str(out_png)],
        check=True, capture_output=True,
    )


def render_png(svg_path, out_png, size):
    subprocess.run(["rsvg-convert", "-w", str(size), "-h", str(size), str(svg_path), "-o", str(out_png)],
                    check=True, capture_output=True)


def install_vector(name, vector_path, entry, dark_hex, light_hex, staging, master_size):
    dark_rgb, light_rgb = vector.hex_to_rgb(dark_hex), vector.hex_to_rgb(light_hex)

    def repl(m):
        L = float(m.group(1))
        rgb = tuple(dark_rgb[i] + (light_rgb[i] - dark_rgb[i]) * L for i in range(3))
        return f'fill="{vector.rgb_to_hex(rgb)}"'

    svg_text = re.sub(r'fill="__L([\d.]+)__"', repl, vector_path.read_text())

    with tempfile.TemporaryDirectory() as td:
        colorized = Path(td) / "colorized.svg"
        colorized.write_text(svg_text)

        # Exact override(s): the real theme+path(s) this icon actually
        # resolves through. kora (and themes like it) ship the same icon
        # under multiple contexts -- apps/devices/status/panel/... -- so
        # overriding only the one path we originally resolved leaves the
        # others showing original colors; alias_relpaths (populated by
        # 14-generate-duotone-icons.sh) lists every copy found.
        theme = entry.get("source_theme")
        relpaths = entry.get("alias_relpaths") or (
            [entry["source_relpath"]] if entry.get("source_relpath") else []
        )
        for relpath in relpaths:
            if not relpath.lower().endswith(".svg"):
                continue
            out = staging / theme / relpath
            out.parent.mkdir(parents=True, exist_ok=True)
            out.write_text(svg_text)
        for relpath in relpaths:
            if relpath.lower().endswith(".svg"):
                continue
            size = parse_size_from_relpath(relpath) or master_size
            out = staging / theme / relpath
            out.parent.mkdir(parents=True, exist_ok=True)
            png = Path(td) / f"alias_{Path(relpath).stem}.png"
            render_png(colorized, png, size)
            out.write_bytes(png.read_bytes())

        is_status = entry.get("is_status", False)
        contexts = ["apps"] + (["status"] if is_status else [])
        for install_theme in INSTALL_THEMES:
            for ctx in contexts:
                d = staging / install_theme / "scalable" / ctx
                d.mkdir(parents=True, exist_ok=True)
                (d / f"{name}.svg").write_text(svg_text)
            for size in RASTER_SIZES:
                png = Path(td) / f"r{size}.png"
                render_png(colorized, png, size)
                for ctx in contexts:
                    d = staging / install_theme / f"{size}x{size}" / ctx
                    d.mkdir(parents=True, exist_ok=True)
                    (d / f"{name}.png").write_bytes(png.read_bytes())


def install_raster(name, raster_path, entry, clut, master_size, staging):
    is_status = entry.get("is_status", False)
    contexts = ["apps"] + (["status"] if is_status else [])

    theme = entry.get("source_theme")
    relpaths = entry.get("alias_relpaths") or (
        [entry["source_relpath"]] if entry.get("source_relpath") else []
    )
    for relpath in relpaths:
        if relpath.lower().endswith(".svg"):
            continue  # no vector source to put there; leave that slot alone
        size = parse_size_from_relpath(relpath) or master_size
        out = staging / theme / relpath
        out.parent.mkdir(parents=True, exist_ok=True)
        with tempfile.TemporaryDirectory() as td:
            colorize_raster(raster_path, clut, out, size, td)

    for install_theme in INSTALL_THEMES:
        for size in RASTER_SIZES:
            with tempfile.TemporaryDirectory() as td:
                sized = Path(td) / "s.png"
                colorize_raster(raster_path, clut, sized, size, td)
                for ctx in contexts:
                    d = staging / install_theme / f"{size}x{size}" / ctx
                    d.mkdir(parents=True, exist_ok=True)
                    (d / f"{name}.png").write_bytes(sized.read_bytes())


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--vector-dir", required=True)
    ap.add_argument("--raster-dir", required=True)
    ap.add_argument("--manifest", required=True)
    ap.add_argument("--colors", required=True, help="path to the target's colors.json")
    ap.add_argument("--staging-dir", required=True)
    ap.add_argument("--dark-stop", default="primary_container")
    ap.add_argument("--light-stop", default="on_primary_container")
    ap.add_argument("--master-size", type=int, default=256)
    args = ap.parse_args()

    manifest = json.loads(Path(args.manifest).read_text())
    colors = json.loads(Path(args.colors).read_text())
    dark_hex, light_hex = colors[args.dark_stop], colors[args.light_stop]
    staging = Path(args.staging_dir)

    vector_masters = sorted(Path(args.vector_dir).glob("*.svg"))
    raster_masters = sorted(Path(args.raster_dir).glob("*.png"))
    if not vector_masters and not raster_masters:
        print("No duotone masters found -- run 14-generate-duotone-icons.sh first.")
        return

    for master in vector_masters:
        name = master.stem
        install_vector(name, master, manifest.get(name, {}), dark_hex, light_hex, staging, args.master_size)

    with tempfile.TemporaryDirectory() as tmpdir:
        clut = build_clut(dark_hex, light_hex, tmpdir)
        for master in raster_masters:
            name = master.stem
            install_raster(name, master, manifest.get(name, {}), clut, args.master_size, staging)

    print(f">>> Staged {len(vector_masters)} vector + {len(raster_masters)} raster icon(s), "
          f"dark={dark_hex} light={light_hex} -> {staging}")


if __name__ == "__main__":
    main()
