#!/usr/bin/env python3
"""lucide-render.py — gera PNGs squircle 512×512 a partir de Lucide SVG icons,
para slugs com fallback (categoria) em vez de logo de marca.

Pipeline:
1. Lookup slug → tipo (de recursos/<slug>/index.json)
2. Lookup tipo → bg+icon colors (category-colors.json)
3. Lookup phosphor:X → lucide name (phosphor-lucide-map.json)
4. Fetch lucide SVG (cache em _lucide-cache/)
5. Recolor SVG fill = icon color
6. Rasterize 60% canvas (~308px) centrado
7. Composite sobre bg cor da categoria
8. Aplica squircle mask
9. Salva pub/<slug>.png

Atualiza tool-logos.json: adiciona logoUrl="local:<slug>.png" pros slugs processados.
"""
from __future__ import annotations
import io
import json
import re
import subprocess
import sys
import urllib.request
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from PIL import Image, ImageColor

sys.path.insert(0, str(Path(__file__).parent))
from squircle import squircle_mask, parse_hex

ROOT = Path("/root/nxt/terminal.net.br")
PILOT_SRC = ROOT / "projetos/terminal.net.br/src"
PUB = ROOT / "projetos/terminal.net.br/public/tool-logos"
RECURSOS = ROOT / "recursos"
TOOL_LOGOS = PILOT_SRC / "tool-logos.json"
SCRIPT_DIR = Path(__file__).parent
LUCIDE_CACHE = SCRIPT_DIR / "_lucide-cache"
CATEGORY_COLORS = SCRIPT_DIR / "category-colors.json"
PHOSPHOR_MAP = SCRIPT_DIR / "phosphor-lucide-map.json"

SIZE = 512
ICON_PCT = 0.47  # icon takes 47% of canvas (15% reduzido proporcionalmente)

LUCIDE_CACHE.mkdir(parents=True, exist_ok=True)


def fetch_lucide_svg(name: str) -> str | None:
    """Get SVG content for lucide icon. Cached."""
    cache_path = LUCIDE_CACHE / f"{name}.svg"
    if cache_path.exists():
        return cache_path.read_text()
    url = f"https://unpkg.com/lucide-static@latest/icons/{name}.svg"
    try:
        req = urllib.request.Request(
            url, headers={"User-Agent": "Mozilla/5.0 nxt-pipeline"})
        with urllib.request.urlopen(req, timeout=10) as resp:
            if resp.status != 200:
                return None
            content = resp.read().decode()
            if "<svg" not in content[:500]:
                return None
            cache_path.write_text(content)
            return content
    except Exception as e:
        print(f"  fetch fail {name}: {e}", file=sys.stderr)
        return None


def recolor_svg(svg: str, hex_color: str) -> str:
    """Set stroke + fill color in lucide SVG. Lucide icons use stroke="currentColor"."""
    # Replace stroke / fill currentColor with explicit hex
    svg = re.sub(r'stroke="currentColor"', f'stroke="{hex_color}"', svg)
    svg = re.sub(r'fill="currentColor"', f'fill="{hex_color}"', svg)
    # Also if no stroke set (uncommon), inject one
    if 'stroke="' not in svg:
        svg = svg.replace("<svg ", f'<svg stroke="{hex_color}" fill="none" ', 1)
    return svg


def rasterize_svg(svg_str: str, size: int) -> Image.Image:
    """Rasterize SVG to PIL Image at size×size."""
    result = subprocess.run(
        ["rsvg-convert", "-w", str(size), "-h", str(size)],
        input=svg_str.encode(), capture_output=True,
    )
    if result.returncode != 0 or len(result.stdout) < 100:
        # Fallback cairosvg
        try:
            import cairosvg
            png_bytes = cairosvg.svg2png(
                bytestring=svg_str.encode(),
                output_width=size, output_height=size,
            )
            return Image.open(io.BytesIO(png_bytes)).convert("RGBA")
        except Exception as e:
            raise RuntimeError(f"SVG rasterize failed: {e}")
    return Image.open(io.BytesIO(result.stdout)).convert("RGBA")


def make_glyph_squircle(
    lucide_name: str, bg_hex: str, icon_hex: str, output_path: Path,
) -> bool:
    """Render lucide icon centered on category-colored squircle."""
    svg = fetch_lucide_svg(lucide_name)
    if not svg:
        return False
    svg = recolor_svg(svg, icon_hex)
    icon_size = int(SIZE * ICON_PCT)
    # Increase stroke-width for clearer visibility at small sizes
    svg = re.sub(r'stroke-width="[\d.]+"', 'stroke-width="1.5"', svg)
    icon_img = rasterize_svg(svg, icon_size)

    # Background canvas with category color
    bg_rgb = parse_hex(bg_hex)
    canvas = Image.new("RGBA", (SIZE, SIZE), bg_rgb + (255,))
    # Center icon
    offset = ((SIZE - icon_size) // 2, (SIZE - icon_size) // 2)
    canvas.alpha_composite(icon_img, offset)

    # Apply circle mask (default shape)
    mask = squircle_mask("circle")
    canvas.putalpha(mask)

    output_path.parent.mkdir(parents=True, exist_ok=True)
    canvas.save(output_path, "PNG", optimize=True)
    return True


def slugify(s: str) -> str:
    return re.sub(r"[^a-z0-9]+", "-", s.lower()).strip("-")


def get_tipo(slug: str) -> str:
    """Read tipo from recursos/<slug>/index.json. Returns '' if missing."""
    idx = RECURSOS / slug / "index.json"
    if not idx.exists():
        return ""
    try:
        with open(idx) as f:
            data = json.load(f)
        return data.get("tipo") or ""
    except Exception:
        return ""


def main():
    with open(TOOL_LOGOS) as f:
        tool_logos = json.load(f)
    with open(CATEGORY_COLORS) as f:
        cat_colors = json.load(f)
    with open(PHOSPHOR_MAP) as f:
        ph_map = json.load(f)

    # Find slugs com fallback Phosphor sem logoUrl real.
    # Critério: entry.logoUrl ausente → lucide gera glyph categorizado.
    # Slugs com logoUrl já foram processados por regenerate-all (brand logo).
    targets = []  # list of (display_name, slug, fallback_str)
    for name, entry in tool_logos.items():
        if name.startswith("_"):
            continue
        fallback = entry.get("fallback") or ""
        if not fallback.startswith("phosphor:"):
            continue
        if entry.get("logoUrl"):
            continue  # já tem brand logo via regenerate-all
        slug = slugify(name)
        # Slug deve corresponder a um dir em recursos/
        if not (RECURSOS / slug).exists():
            # tenta variantes
            for alt in [slug.replace("-", "_"), slug.replace("_", "-")]:
                if (RECURSOS / alt).exists():
                    slug = alt
                    break
        targets.append((name, slug, fallback))

    print(f"Targets: {len(targets)} slugs com fallback Phosphor")

    def process(item):
        name, slug, fallback = item
        lucide_name = ph_map.get(fallback, "box")
        tipo = get_tipo(slug)
        cat = cat_colors.get(tipo) or cat_colors["_default"]
        out = PUB / f"{slug}.png"
        try:
            ok = make_glyph_squircle(lucide_name, cat["bg"], cat["icon"], out)
            return {
                "name": name, "slug": slug, "tipo": tipo, "lucide": lucide_name,
                "fallback": fallback, "bg": cat["bg"], "ok": ok,
            }
        except Exception as e:
            return {"name": name, "slug": slug, "ok": False, "error": str(e)[:200]}

    results = []
    with ThreadPoolExecutor(max_workers=6) as ex:
        futures = {ex.submit(process, t): t for t in targets}
        for i, fut in enumerate(as_completed(futures), 1):
            r = fut.result()
            results.append(r)
            if i % 50 == 0:
                ok = sum(1 for x in results if x.get("ok"))
                print(f"  {i}/{len(targets)} done, {ok} OK")

    ok_count = sum(1 for r in results if r.get("ok"))
    fail_count = len(results) - ok_count
    print(f"\nDone: {ok_count} OK, {fail_count} FAIL")

    # Update tool-logos.json: adiciona logoUrl pros que tiveram OK
    updates = 0
    for r in results:
        if not r.get("ok"):
            continue
        entry = tool_logos.get(r["name"])
        if entry is None:
            continue
        entry["logoUrl"] = f"local:{r['slug']}.png"
        updates += 1

    # Sort + save
    sorted_logos = {k: tool_logos[k] for k in sorted(tool_logos.keys())}
    with open(TOOL_LOGOS, "w") as f:
        json.dump(sorted_logos, f, indent=2, ensure_ascii=False)
        f.write("\n")
    print(f"Updated tool-logos.json: +{updates} logoUrl entries")

    # Save report
    Path("/tmp/logo-staging-reports").mkdir(parents=True, exist_ok=True)
    with open("/tmp/logo-staging-reports/lucide-render.json", "w") as f:
        json.dump({"results": results, "updates": updates}, f, indent=2)


if __name__ == "__main__":
    main()
