#!/usr/bin/env python3
"""Compile original generated PNG icons to a deterministic, straight-alpha RGBA atlas.

This is an asset compiler, not an image-generation or retouching tool. It does not
alter the original PNGs or recolor, redraw, crop, remove backgrounds, or repair
pixels. Resampling happens in premultiplied alpha to preserve translucent edges.
ContactSheet.png is a labeled inspection aid and is never a runtime asset.
"""

from __future__ import annotations

import argparse
import hashlib
import json
from pathlib import Path

from PIL import Image, ImageDraw, __version__ as pillow_version


ICON_NAMES = (
    "Files", "Editor", "Browser", "Terminal", "Folder", "TextFile",
    "CodeFile", "MarkdownFile", "ApplicationFile",
)
ICON_SIZE = 64


def sha256(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()


def compile_icons(asset_directory: Path) -> dict:
    provenance_bytes = (asset_directory / "Provenance.json").read_bytes()
    provenance = json.loads(provenance_bytes.decode("utf-8"))
    if tuple(asset["Name"] for asset in provenance["Assets"]) != ICON_NAMES:
        raise ValueError("Provenance icon order does not match the runtime contract")

    atlas_parts: list[bytes] = []
    entries: list[dict] = []
    thumbnails: list[Image.Image] = []
    for index, name in enumerate(ICON_NAMES):
        source_path = asset_directory / "Source" / f"{name}.png"
        source_bytes = source_path.read_bytes()
        with Image.open(source_path) as original:
            if original.format != "PNG" or original.mode != "RGBA":
                raise ValueError(f"{name}: expected original PNG with explicit RGBA alpha")
            original.load()
            width, height = original.size
            if width <= 0 or height <= 0 or width * height > 8_388_608:
                raise ValueError(f"{name}: unsupported source dimensions")
            source = original.copy()

        histogram = source.getchannel("A").histogram()
        if histogram[0] == 0 or histogram[255] == 0:
            raise ValueError(f"{name}: must contain truly transparent and opaque pixels")
        if histogram[0] < width * height // 10:
            raise ValueError(f"{name}: unexpectedly little transparent background")

        # Deterministic pixel-format compilation only. No creative image editing.
        thumbnail = source.convert("RGBa").resize(
            (ICON_SIZE, ICON_SIZE), Image.Resampling.LANCZOS
        ).convert("RGBA")
        pixels = thumbnail.tobytes("raw", "RGBA")
        expected_size = ICON_SIZE * ICON_SIZE * 4
        if len(pixels) != expected_size:
            raise ValueError(f"{name}: invalid compiled byte length")
        atlas_parts.append(pixels)
        thumbnails.append(thumbnail)
        entries.append({
            "Name": name,
            "Index": index,
            "ByteOffset": index * expected_size,
            "ByteLength": expected_size,
            "Width": ICON_SIZE,
            "Height": ICON_SIZE,
            "Stride": ICON_SIZE * 4,
            "Source": f"Source/{name}.png",
            "SourceWidth": width,
            "SourceHeight": height,
            "SourceSha256": sha256(source_bytes),
            "CompiledSha256": sha256(pixels),
            "SourceTransparentPixels": histogram[0],
            "SourceOpaquePixels": histogram[255],
            "CompiledAlphaExtrema": list(thumbnail.getchannel("A").getextrema()),
        })

    atlas_bytes = b"".join(atlas_parts)
    atlas_path = asset_directory / "Atlas.rgba"
    atlas_path.write_bytes(atlas_bytes)
    manifest = {
        "SchemaVersion": 1,
        "Tool": "Tools/BuildDesktopIcons.py",
        "Generator": "image_gen.imagegen (built-in tool)",
        "CompilerPillowVersion": pillow_version,
        "SourcePolicy": "Original generated PNGs are retained unchanged.",
        "Resampling": "Premultiplied-alpha LANCZOS, then straight-alpha RGBA8",
        "Format": "RGBA8",
        "Alpha": "Straight",
        "Layout": "IconMajor; each icon is row-major; no header or padding",
        "ColorSpace": "sRGB",
        "IconSize": ICON_SIZE,
        "IconCount": len(ICON_NAMES),
        "Atlas": "Atlas.rgba",
        "AtlasByteLength": len(atlas_bytes),
        "AtlasSha256": sha256(atlas_bytes),
        "Provenance": "Provenance.json",
        "ProvenanceSha256": sha256(provenance_bytes),
        "WebReferences": [],
        "Icons": entries,
    }
    (asset_directory / "Manifest.json").write_text(
        json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
    )

    # Contact sheet only: originals remain the sole sources of runtime pixels.
    card_width, card_height = 224, 128
    contact = Image.new("RGB", (card_width * 3, card_height * 3), "#DCDDEC")
    drawing = ImageDraw.Draw(contact)
    for index, (name, thumbnail) in enumerate(zip(ICON_NAMES, thumbnails)):
        left = index % 3 * card_width
        top = index // 3 * card_height
        drawing.rectangle((left, top, left + card_width - 2, top + 25), fill="#EEEEFC")
        drawing.text((left + 10, top + 7), name, fill="#46465C")
        drawing.rectangle((left, top + 26, left + 110, top + card_height - 2), fill="#FFFFFF")
        drawing.rectangle((left + 111, top + 26, left + card_width - 2, top + card_height - 2), fill="#09121B")
        contact.paste(thumbnail, (left + 23, top + 44), thumbnail)
        contact.paste(thumbnail, (left + 135, top + 44), thumbnail)
    contact.save(asset_directory / "ContactSheet.png", format="PNG")
    return manifest


def main() -> None:
    default = Path(__file__).resolve().parents[1] / "assets" / "DesktopIcons"
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--assets", type=Path, default=default)
    arguments = parser.parse_args()
    manifest = compile_icons(arguments.assets.resolve())
    print(json.dumps({
        "Atlas": str(arguments.assets.resolve() / "Atlas.rgba"),
        "IconCount": manifest["IconCount"],
        "ByteLength": manifest["AtlasByteLength"],
        "Sha256": manifest["AtlasSha256"],
    }, indent=2))


if __name__ == "__main__":
    main()
