from __future__ import annotations

from pathlib import Path
from PIL import Image, ImageDraw, ImageFilter, ImageChops


ROOT = Path(__file__).resolve().parents[1]
ASSETS = ROOT / "assets"
# Include common Windows titlebar/taskbar/DPI variants. 20/24/30/40/60/72/80
# help 125%/150% Windows scaling avoid resizing one unsuitable bitmap.
SIZES = [16, 20, 24, 30, 32, 40, 48, 60, 64, 72, 80, 96, 128, 256]


def _gradient(size: int) -> Image.Image:
    """Blue Nexa gradient used by the icon masks."""
    img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
    px = img.load()
    for y in range(size):
        for x in range(size):
            # Diagonal cyan -> business blue. Keep saturation high so the
            # 16/20/24px titlebar variants stay readable on Windows.
            t = max(0.0, min(1.0, (x * 0.78 + y * 0.52) / (size * 1.30)))
            r = int(18 * (1 - t) + 33 * t)
            g = int(196 * (1 - t) + 99 * t)
            b = int(245 * (1 - t) + 238 * t)
            px[x, y] = (r, g, b, 255)
    return img


def _draw_master(size: int = 1024) -> Image.Image:
    """Draw a crisp vector-like Nexa mark instead of scaling an old bitmap.

    The old top/taskbar icon could look blurry after repeated PNG/ICO resizing.
    This routine draws the mark at high resolution from simple masks, then each
    target size gets its own sharpened downsample. The result is clearer in both
    the title bar and Windows taskbar.
    """
    scale = size / 1024.0
    def pt(x: float, y: float) -> tuple[int, int]:
        return (round(x * scale), round(y * scale))
    def val(v: float) -> int:
        return round(v * scale)

    grad = _gradient(size)
    mask = Image.new("L", (size, size), 0)
    d = ImageDraw.Draw(mask)

    # Left folded triangle.
    d.polygon([
        pt(74, 104), pt(384, 448), pt(78, 796), pt(78, 604),
        pt(236, 448), pt(78, 276)
    ], fill=255)
    # Right folded triangle.
    d.polygon([
        pt(706, 96), pt(958, 96), pt(958, 526), pt(730, 390)
    ], fill=255)
    # Center diagonal bar, rounded end caps for a cleaner software icon.
    d.line([pt(184, 166), pt(792, 850)], fill=255, width=val(192))
    d.ellipse([val(184-96), val(166-96), val(184+96), val(166+96)], fill=255)
    d.ellipse([val(792-96), val(850-96), val(792+96), val(850+96)], fill=255)

    img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
    img.paste(grad, (0, 0), mask)

    # Subtle dark-blue inner fold to preserve the original Nexa look, but not
    # enough to blur the small icons.
    shade = Image.new("RGBA", (size, size), (0, 84, 210, 128))
    shade_mask = Image.new("L", (size, size), 0)
    sd = ImageDraw.Draw(shade_mask)
    sd.polygon([pt(78, 604), pt(236, 448), pt(384, 448), pt(78, 796)], fill=130)
    sd.polygon([pt(706, 96), pt(730, 390), pt(958, 526), pt(958, 392)], fill=100)
    img.alpha_composite(Image.composite(shade, Image.new("RGBA", (size, size), (0, 0, 0, 0)), shade_mask))

    # Tighten transparent bounds and add tiny breathing room so Windows doesn't
    # crop anti-aliased pixels. This keeps taskbar/titlebar scaling crisp.
    bbox = img.getbbox()
    if bbox:
        cropped = img.crop(bbox)
        pad = val(18)
        out = Image.new("RGBA", (size, size), (0, 0, 0, 0))
        max_w = size - pad * 2
        max_h = size - pad * 2
        cropped.thumbnail((max_w, max_h), Image.LANCZOS)
        out.alpha_composite(cropped, ((size - cropped.width)//2, (size - cropped.height)//2))
        img = out
    return img


def _tile_icon(mark_master: Image.Image, size: int = 1024) -> Image.Image:
    """Draw a Windows-friendly tile icon for titlebar/taskbar frames.

    A transparent floating mark can look soft when Windows composites it against
    different titlebar/taskbar backgrounds. The app icon now uses a blue rounded
    tile with a white Nexa mark, while in-app logo assets remain transparent.
    """
    scale = size / 1024.0
    def val(v: float) -> int:
        return round(v * scale)
    tile = Image.new("RGBA", (size, size), (0, 0, 0, 0))
    shadow = Image.new("RGBA", (size, size), (0, 0, 0, 0))
    sd = ImageDraw.Draw(shadow)
    sd.rounded_rectangle([val(104), val(120), val(920), val(936)], radius=val(190), fill=(15, 23, 42, 44))
    shadow = shadow.filter(ImageFilter.GaussianBlur(radius=val(18)))
    tile.alpha_composite(shadow)

    tile_mask = Image.new("L", (size, size), 0)
    md = ImageDraw.Draw(tile_mask)
    md.rounded_rectangle([val(96), val(92), val(928), val(924)], radius=val(184), fill=255)

    grad = Image.new("RGBA", (size, size), (0, 0, 0, 0))
    px = grad.load()
    for y in range(size):
        for x in range(size):
            q = max(0.0, min(1.0, (x * 0.72 + y * 0.46) / (size * 1.18)))
            r = int(14 * (1 - q) + 37 * q)
            g = int(165 * (1 - q) + 99 * q)
            b = int(245 * (1 - q) + 235 * q)
            px[x, y] = (r, g, b, 255)
    tile.alpha_composite(Image.composite(grad, Image.new("RGBA", (size, size), (0, 0, 0, 0)), tile_mask))

    shine = Image.new("RGBA", (size, size), (255, 255, 255, 0))
    sh = ImageDraw.Draw(shine)
    sh.rounded_rectangle([val(148), val(132), val(878), val(460)], radius=val(150), fill=(255, 255, 255, 26))
    tile.alpha_composite(Image.composite(shine, Image.new("RGBA", (size, size), (0,0,0,0)), tile_mask))

    mark_alpha = mark_master.getchannel("A")
    bbox = mark_alpha.getbbox()
    mark = mark_master.crop(bbox) if bbox else mark_master.copy()
    # White mark is more legible than a gradient mark at 16/20/24px.
    white = Image.new("RGBA", mark.size, (255, 255, 255, 242))
    white.putalpha(mark.getchannel("A"))
    max_mark = val(610)
    white.thumbnail((max_mark, max_mark), Image.LANCZOS)
    tile.alpha_composite(white, ((size - white.width)//2, (size - white.height)//2 + val(6)))
    return tile


def _downsample(master: Image.Image, size: int) -> Image.Image:
    img = master.resize((size, size), Image.Resampling.LANCZOS)
    # Size-specific sharpening avoids the common Windows blurry 16/20/24/32px
    # icon problem while keeping 96px+ variants smooth.
    if size <= 24:
        img = img.filter(ImageFilter.UnsharpMask(radius=0.45, percent=190, threshold=1))
    elif size <= 48:
        img = img.filter(ImageFilter.UnsharpMask(radius=0.55, percent=165, threshold=1))
    elif size <= 96:
        img = img.filter(ImageFilter.UnsharpMask(radius=0.70, percent=125, threshold=2))
    else:
        img = img.filter(ImageFilter.UnsharpMask(radius=0.80, percent=80, threshold=2))
    return img


def generate_icons() -> None:
    ASSETS.mkdir(parents=True, exist_ok=True)
    mark_master = _draw_master(1024)
    tile_master = _tile_icon(mark_master, 1024)
    frames: list[Image.Image] = []
    for size in SIZES:
        # Runtime / EXE icon frames use the blue tile so titlebar and taskbar
        # stay sharp and visible on any Windows background.
        img = _downsample(tile_master, size)
        out = ASSETS / f"nexaoffice_icon_{size}.png"
        img.save(out, optimize=True)
        frames.append(img)

    # Transparent mark assets remain for in-app sidebar/login branding.
    _downsample(mark_master, 256).save(ASSETS / "logo_nexa_mark_256.png", optimize=True)
    _downsample(mark_master, 96).save(ASSETS / "logo_nexa_mark.png", optimize=True)

    # Save ICO with explicit tile frames instead of letting Pillow downsample one
    # frame internally. Windows can then choose exact DPI/taskbar/titlebar sizes.
    ico_frames = [_downsample(tile_master, s) for s in SIZES]
    ico_frames[-1].save(
        ASSETS / "nexaoffice.ico",
        format="ICO",
        sizes=[(s, s) for s in SIZES],
        append_images=list(reversed(ico_frames[:-1])),
    )

    print("Generated crisp NexaOffice tile icons:", ", ".join(str(s) for s in SIZES))


if __name__ == "__main__":
    generate_icons()
