#!/usr/bin/env python3
"""
Generate squircle app icons with a house + check mark glyph.

No external dependencies. Writes PNGs using zlib and CRC via stdlib.

Usage:
  python3 scripts/render_icon.py assets/app_icon/house_check 1024 512 256 192 128 96 64 48 32
"""
import os
import sys
import math
import struct
import zlib
from typing import List, Tuple


NAVY = (0x0F, 0x17, 0x2A, 255)  # #0F172A
WHITE = (255, 255, 255, 255)
GREEN = (0x10, 0xB9, 0x81, 255)  # #10B981


def srgb_to_lin(c: float) -> float:
    c = c / 255.0
    if c <= 0.04045:
        return c / 12.92
    return ((c + 0.055) / 1.055) ** 2.4


def lin_to_srgb(c: float) -> int:
    if c <= 0.0031308:
        v = 12.92 * c
    else:
        v = 1.055 * (c ** (1.0 / 2.4)) - 0.055
    return max(0, min(255, int(round(v * 255.0))))


def over(dst: Tuple[float, float, float, float], src: Tuple[float, float, float, float]):
    # dst, src in linear space RGBA (0..1)
    dr, dg, db, da = dst
    sr, sg, sb, sa = src
    out_a = sa + da * (1.0 - sa)
    if out_a == 0.0:
        return (0.0, 0.0, 0.0, 0.0)
    out_r = (sr * sa + dr * da * (1.0 - sa)) / out_a
    out_g = (sg * sa + dg * da * (1.0 - sa)) / out_a
    out_b = (sb * sa + db * da * (1.0 - sa)) / out_a
    return (out_r, out_g, out_b, out_a)


def to_lin_rgba(color: Tuple[int, int, int, int], alpha_scale: float = 1.0):
    r, g, b, a = color
    return (
        srgb_to_lin(r),
        srgb_to_lin(g),
        srgb_to_lin(b),
        (a / 255.0) * alpha_scale,
    )


def point_in_triangle(px, py, ax, ay, bx, by, cx, cy) -> bool:
    # Barycentric technique
    v0x, v0y = cx - ax, cy - ay
    v1x, v1y = bx - ax, by - ay
    v2x, v2y = px - ax, py - ay
    dot00 = v0x * v0x + v0y * v0y
    dot01 = v0x * v1x + v0y * v1y
    dot02 = v0x * v2x + v0y * v2y
    dot11 = v1x * v1x + v1y * v1y
    dot12 = v1x * v2x + v1y * v2y
    denom = dot00 * dot11 - dot01 * dot01
    if denom == 0:
        return False
    inv = 1.0 / denom
    u = (dot11 * dot02 - dot01 * dot12) * inv
    v = (dot00 * dot12 - dot01 * dot02) * inv
    return (u >= 0) and (v >= 0) and (u + v <= 1)


def point_in_rect(px, py, x0, y0, x1, y1) -> bool:
    return (x0 <= px <= x1) and (y0 <= py <= y1)


def dist_point_to_segment(px, py, ax, ay, bx, by) -> float:
    abx, aby = bx - ax, by - ay
    apx, apy = px - ax, py - ay
    ab2 = abx * abx + aby * aby
    if ab2 == 0:
        dx, dy = px - ax, py - ay
        return math.hypot(dx, dy)
    t = (apx * abx + apy * aby) / ab2
    if t < 0:
        t = 0
    elif t > 1:
        t = 1
    hx, hy = ax + t * abx, ay + t * aby
    dx, dy = px - hx, py - hy
    return math.hypot(dx, dy)


def inside_superellipse(u: float, v: float, n: float = 4.5) -> bool:
    # map [0,1] -> [-1,1]
    x = 2.0 * u - 1.0
    y = 2.0 * v - 1.0
    return (abs(x) ** n + abs(y) ** n) <= 1.0


def render_icon(size: int) -> bytes:
    # 2x2 supersampling
    samples = [(0.25, 0.25), (0.75, 0.25), (0.25, 0.75), (0.75, 0.75)]

    # House geometry (normalized 0..1)
    roof = (0.28, 0.46, 0.50, 0.30, 0.72, 0.46)  # (ax,ay,bx,by,cx,cy)
    body = (0.32, 0.46, 0.68, 0.76)  # (x0,y0,x1,y1)

    # Check path
    chk_a = (0.33, 0.60)
    chk_b = (0.46, 0.74)
    chk_c = (0.72, 0.48)
    thickness = 0.08  # relative to width

    lin_bg = to_lin_rgba(NAVY)
    lin_white = to_lin_rgba(WHITE)
    lin_green = to_lin_rgba(GREEN)

    out = bytearray(size * size * 4)
    idx = 0
    for j in range(size):
        for i in range(size):
            # Accumulate in linear
            dst = (0.0, 0.0, 0.0, 0.0)

            cov_bg = 0.0
            cov_house = 0.0
            cov_check = 0.0
            for (ox, oy) in samples:
                u = (i + ox) / size
                v = (j + oy) / size

                if inside_superellipse(u, v):
                    cov_bg += 1.0

                    hx = 0
                    # house coverage (triangle or rect)
                    if point_in_triangle(u, v, *roof):
                        hx = 1
                    elif point_in_rect(u, v, *body):
                        hx = 1
                    cov_house += hx

                    # check coverage by distance to segments
                    d1 = dist_point_to_segment(u, v, *chk_a, *chk_b)
                    d2 = dist_point_to_segment(u, v, *chk_b, *chk_c)
                    if min(d1, d2) <= (thickness * 0.5):
                        cov_check += 1.0

            ss = float(len(samples))
            if cov_bg > 0.0:
                dst = over(dst, (lin_bg[0], lin_bg[1], lin_bg[2], lin_bg[3] * (cov_bg / ss)))
            if cov_house > 0.0:
                dst = over(dst, (lin_white[0], lin_white[1], lin_white[2], lin_white[3] * (cov_house / ss)))
            if cov_check > 0.0:
                dst = over(dst, (lin_green[0], lin_green[1], lin_green[2], lin_green[3] * (cov_check / ss)))

            r = lin_to_srgb(dst[0])
            g = lin_to_srgb(dst[1])
            b = lin_to_srgb(dst[2])
            a = max(0, min(255, int(round(dst[3] * 255.0))))

            out[idx + 0] = r
            out[idx + 1] = g
            out[idx + 2] = b
            out[idx + 3] = a
            idx += 4

    return png_encode(size, size, bytes(out))


def png_chunk(typ: bytes, data: bytes) -> bytes:
    return struct.pack(
        ">I", len(data)
    ) + typ + data + struct.pack(
        ">I", (zlib.crc32(typ + data) & 0xFFFFFFFF)
    )


def png_encode(width: int, height: int, rgba: bytes) -> bytes:
    # Build raw scanlines with filter 0
    stride = width * 4
    raw = bytearray()
    for y in range(height):
        raw.append(0)  # filter type 0
        start = y * stride
        raw.extend(rgba[start : start + stride])
    comp = zlib.compress(bytes(raw), level=9)

    sig = b"\x89PNG\r\n\x1a\n"
    ihdr = struct.pack(
        ">IIBBBBB",
        width,
        height,
        8,  # bit depth
        6,  # color type RGBA
        0,  # compression
        0,  # filter
        0,  # interlace
    )
    out = bytearray()
    out.extend(sig)
    out.extend(png_chunk(b"IHDR", ihdr))
    out.extend(png_chunk(b"IDAT", comp))
    out.extend(png_chunk(b"IEND", b""))
    return bytes(out)


def main():
    if len(sys.argv) < 3:
        print(
            "Usage: python3 scripts/render_icon.py <out_dir> <sizes...>",
            file=sys.stderr,
        )
        sys.exit(2)
    out_dir = sys.argv[1]
    sizes = [int(s) for s in sys.argv[2:]]
    os.makedirs(out_dir, exist_ok=True)
    for s in sizes:
        data = render_icon(s)
        path = os.path.join(out_dir, f"{s}.png")
        with open(path, "wb") as f:
            f.write(data)
        print(f"wrote {path}")


if __name__ == "__main__":
    main()

