#!/usr/bin/env python3
"""Regenerate Mini strip Assets/**/{out_res} from 3840x1080 OHD masters.

Default (full panel):
  OUT_RES=1920x360  CONTENT_H=300  centered margins

Shenzhen Link overscan pack (keeps HDMI 1920x360; separate folder):
  python tools/gen_1920x360_assets.py --out-res 1920x360-96 --top-inset 96 --content-h 264

BG: anisotropic to 1920xCONTENT_H, pasted at TOP_INSET on a 1920x360 canvas.
FR: anisotropic to half-OHD-width x CONTENT_H (blitted at frame_anchor).
Digits: OHD glyphs scaled by (0.5, CONTENT_H/1080).
Splash/logo: same content-band fit. Fireworks: copy from 1920x360 if present.
"""
from __future__ import annotations

import argparse
import json
import re
import shutil
from pathlib import Path

from PIL import Image

ROOT = Path(__file__).resolve().parents[1]
ASSETS = ROOT / "Assets"
OHD_SHARED = ASSETS / "Shared" / "3840x1080"
MINI_NATIVE = ASSETS / "Shared" / "1920x360"

OUT_W, OUT_H = 1920, 360

_GLYPH_DIGIT = re.compile(r"^(\d)_(\d+)x(\d+)\.png$", re.I)
_GLYPH_DOLLAR = re.compile(r"^\$\_(\d+)x(\d+)\.png$", re.I)
_GLYPH_PUNCT = re.compile(r"^(comma|decimal)_(\d+)x(\d+)\.png$", re.I)


def _write_png(path: Path, im: Image.Image) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    im.save(path, format="PNG", optimize=True)


def _fit_content_band(
    im: Image.Image,
    *,
    out_w: int,
    out_h: int,
    content_h: int,
    top_inset: int,
) -> Image.Image:
    """Anisotropic scale into out_w x content_h, paste at top_inset on out_w x out_h."""
    band = im.resize(
        (max(1, out_w), max(1, content_h)),
        Image.Resampling.LANCZOS,
    )
    canvas = Image.new("RGBA", (out_w, out_h), (0, 0, 0, 255))
    canvas.paste(band, (0, top_inset), band if band.mode == "RGBA" else None)
    return canvas


def rebuild_shared_layout(
    *,
    out_res: str,
    content_h: int,
    top_inset: int,
    sx: float,
    sy: float,
) -> dict:
    src = json.loads((OHD_SHARED / "layout.json").read_text(encoding="utf-8"))
    cell = src.get("digit_cell") or {}
    aligned = src.get("aligned_cells") or {}
    fa = src.get("frame_anchor") or [1296, 0]
    strip = src.get("digit_strip") or {}
    cells_out = []
    for c in strip.get("cells") or []:
        cc = dict(c)
        for k in ("x", "w"):
            if k in cc:
                cc[k] = int(round(cc[k] * sx))
        for k in ("y", "h"):
            if k in cc:
                cc[k] = int(round(cc[k] * sy))
        if "y" in cc:
            cc["y"] = int(cc["y"]) + top_inset
        cells_out.append(cc)

    digit_w = int(round(int(cell.get("w", 220)) * sx))
    digit_h = int(round(int(cell.get("h", 360)) * sy))
    frame_x = int(round(fa[0] * sx))
    frame_y = top_inset + int(round(fa[1] * sy))
    anchor_bottom = top_inset + int(round(int(aligned.get("anchor_bottom", 710)) * sy))
    doc = {
        "resolution": out_res,
        "screen": [OUT_W, OUT_H],
        "frame_anchor": [frame_x, frame_y],
        "display_scale": 1.0,
        "digit_cell": {"w": digit_w, "h": digit_h},
        "digit_strip": {
            "orientation": strip.get("orientation", "vertical"),
            "digit_order": strip.get("digit_order", "0_at_top"),
            "cells": cells_out,
        },
        "digit_slots": [],
        "symbol_slots": [],
        "use_template_screen_coords": True,
        "aligned_cells": {
            "anchor_right": int(round(int(aligned.get("anchor_right", 3732)) * sx)),
            "anchor_bottom": anchor_bottom,
        },
        "mini_content_height": content_h,
        "mini_top_inset": top_inset,
        "mini_margin_y": top_inset,
    }
    if "odometer" in src:
        doc["odometer"] = src["odometer"]
    return doc


def rebuild_shared_glyphs(shared_out: Path, *, sx: float, sy: float) -> list[str]:
    wrote: list[str] = []
    for old in list(shared_out.glob("*.png")):
        n = old.name
        if _GLYPH_DIGIT.match(n) or _GLYPH_DOLLAR.match(n) or _GLYPH_PUNCT.match(n):
            old.unlink()
            wrote.append(f"rm {n}")

    for src in sorted(OHD_SHARED.glob("*.png")):
        m_digit = _GLYPH_DIGIT.match(src.name)
        m_dollar = _GLYPH_DOLLAR.match(src.name)
        m_punct = _GLYPH_PUNCT.match(src.name)
        if m_digit:
            label = m_digit.group(1)
        elif m_dollar:
            label = "$"
        elif m_punct:
            label = m_punct.group(1)
        else:
            continue
        w, h = Image.open(src).size
        out = Image.open(src).convert("RGBA").resize(
            (max(1, int(round(w * sx))), max(1, int(round(h * sy)))),
            Image.Resampling.LANCZOS,
        )
        if label == "$":
            name = f"$_{out.size[0]}x{out.size[1]}.png"
        else:
            name = f"{label}_{out.size[0]}x{out.size[1]}.png"
        _write_png(shared_out / name, out)
        wrote.append(name)
    return wrote


def rebuild_shared_fr(shared_out: Path, *, sx: float, content_h: int) -> None:
    fr = Image.open(OHD_SHARED / "FR.png").convert("RGBA")
    out_w = max(1, int(round(fr.size[0] * sx)))
    band = fr.resize((out_w, content_h), Image.Resampling.LANCZOS)
    _write_png(shared_out / "FR.png", band)


def rebuild_game_bgs(
    out_res: str,
    *,
    content_h: int,
    top_inset: int,
) -> list[str]:
    done: list[str] = []
    for game_dir in sorted(ASSETS.iterdir()):
        if not game_dir.is_dir() or game_dir.name == "Shared":
            continue
        if not game_dir.name.isdigit():
            continue
        ohd = game_dir / "3840x1080"
        mini = game_dir / out_res
        if not ohd.is_dir():
            continue
        mini.mkdir(parents=True, exist_ok=True)
        for src in sorted(ohd.glob("*.png")) + sorted(ohd.glob("*.jpg")):
            im = Image.open(src).convert("RGBA")
            out = _fit_content_band(
                im,
                out_w=OUT_W,
                out_h=OUT_H,
                content_h=content_h,
                top_inset=top_inset,
            )
            dest_name = src.stem + ".png"
            _write_png(mini / dest_name, out)
            done.append(f"{game_dir.name}/{dest_name}")
        for jpg in mini.glob("*.jpg"):
            if jpg.with_suffix(".png").exists():
                jpg.unlink()
    return done


def rebuild_shared_display_rasters(
    shared_out: Path,
    *,
    content_h: int,
    top_inset: int,
) -> list[str]:
    """Splash / logo into content band; copy fireworks from native Mini if present."""
    wrote: list[str] = []
    splash_candidates = [
        OHD_SHARED / "splash1.png",
        OHD_SHARED / "splash.png",
        OHD_SHARED / "splash.jpg",
        MINI_NATIVE / "splash1.png",
        MINI_NATIVE / "splash.png",
    ]
    for src in splash_candidates:
        if not src.is_file():
            continue
        im = Image.open(src).convert("RGBA")
        out = _fit_content_band(
            im,
            out_w=OUT_W,
            out_h=OUT_H,
            content_h=content_h,
            top_inset=top_inset,
        )
        _write_png(shared_out / "splash1.png", out)
        wrote.append("splash1.png")
        break

    for name in ("pot_dyzine_logo.png", "POG_Render_02.png"):
        for root in (OHD_SHARED, MINI_NATIVE):
            src = root / name
            if not src.is_file():
                continue
            im = Image.open(src).convert("RGBA")
            out = _fit_content_band(
                im,
                out_w=OUT_W,
                out_h=OUT_H,
                content_h=content_h,
                top_inset=top_inset,
            )
            _write_png(shared_out / name, out)
            wrote.append(name)
            break

    fw = MINI_NATIVE / "fireworks.mp4"
    if fw.is_file():
        dest = shared_out / "fireworks.mp4"
        if not dest.exists() or dest.stat().st_size != fw.stat().st_size:
            shutil.copy2(fw, dest)
        wrote.append("fireworks.mp4")
    return wrote


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument(
        "--out-res",
        default="1920x360",
        help="Assets folder name (default 1920x360; overscan e.g. 1920x360-96)",
    )
    ap.add_argument(
        "--top-inset",
        type=int,
        default=None,
        help="Y offset of content band (default: center for 300h, else 0)",
    )
    ap.add_argument(
        "--content-h",
        type=int,
        default=None,
        help="Content band height (default 300 for 1920x360, 264 for *-96)",
    )
    args = ap.parse_args()
    out_res = args.out_res.strip()
    if args.content_h is not None:
        content_h = args.content_h
    elif out_res.endswith("-96"):
        content_h = 264
    else:
        content_h = 300
    if args.top_inset is not None:
        top_inset = args.top_inset
    elif out_res.endswith("-96"):
        top_inset = 96
    else:
        top_inset = (OUT_H - content_h) // 2

    if top_inset < 0 or content_h <= 0 or top_inset + content_h > OUT_H:
        raise SystemExit(
            f"invalid geometry: top_inset={top_inset} content_h={content_h} OUT_H={OUT_H}"
        )

    sx = OUT_W / 3840
    sy = content_h / 1080
    shared_out = ASSETS / "Shared" / out_res
    shared_out.mkdir(parents=True, exist_ok=True)

    layout = rebuild_shared_layout(
        out_res=out_res,
        content_h=content_h,
        top_inset=top_inset,
        sx=sx,
        sy=sy,
    )
    (shared_out / "layout.json").write_text(
        json.dumps(layout, indent=2) + "\n", encoding="utf-8"
    )
    glyphs = rebuild_shared_glyphs(shared_out, sx=sx, sy=sy)
    rebuild_shared_fr(shared_out, sx=sx, content_h=content_h)
    games = rebuild_game_bgs(out_res, content_h=content_h, top_inset=top_inset)
    display = rebuild_shared_display_rasters(
        shared_out, content_h=content_h, top_inset=top_inset
    )

    print("OUT_RES", out_res, "CONTENT_H", content_h, "TOP_INSET", top_inset)
    print("layout digit_cell", layout["digit_cell"])
    print("layout aligned", layout["aligned_cells"])
    print("layout frame_anchor", layout["frame_anchor"])
    print(f"glyphs: {len([g for g in glyphs if not g.startswith('rm ')])} written")
    print(f"game rasters: {len(games)}")
    print(f"display rasters: {display}")
    fr = Image.open(shared_out / "FR.png")
    print(f"FR {fr.size}")


if __name__ == "__main__":
    main()
