#!/usr/bin/env python3
"""Optimize Madrassa Sahaba logo PNG assets for web and documents."""

from __future__ import annotations

from collections import deque
from pathlib import Path
from statistics import median

from PIL import Image


PROJECT_DIR = Path(__file__).resolve().parents[1]
SOURCE_DIR = PROJECT_DIR / "designs" / "sahaba"
OUTPUT_DIR = SOURCE_DIR / "optimized"

TRANSPARENT_TOLERANCE = 58
DARK_CROP_TOLERANCE = 34
STANDARD_MARGIN_RATIO = 0.055
ICON_MARGIN_RATIO = 0.13


STANDARD_SPECS = [
    ("sahaba.png", "sahaba_original.png", True),
    ("sahaba_dark.png", "sahaba_dark.png", False),
    ("sahaba_mono.png", "sahaba_mono.png", True),
    ("sahaba_web.png", "sahaba_web.png", True),
]

FAVICON_SIZES = [16, 32, 48, 180, 192, 512]


def edge_background_color(image: Image.Image) -> tuple[int, int, int]:
    rgba = image.convert("RGBA")
    width, height = rgba.size
    step = max(1, min(width, height) // 160)
    pixels: list[tuple[int, int, int]] = []

    for x in range(0, width, step):
        pixels.append(rgba.getpixel((x, 0))[:3])
        pixels.append(rgba.getpixel((x, height - 1))[:3])

    for y in range(0, height, step):
        pixels.append(rgba.getpixel((0, y))[:3])
        pixels.append(rgba.getpixel((width - 1, y))[:3])

    return tuple(int(median(channel)) for channel in zip(*pixels))


def color_distance(a: tuple[int, int, int], b: tuple[int, int, int]) -> int:
    return abs(a[0] - b[0]) + abs(a[1] - b[1]) + abs(a[2] - b[2])


def remove_connected_background(image: Image.Image, tolerance: int = TRANSPARENT_TOLERANCE) -> Image.Image:
    rgba = image.convert("RGBA")
    width, height = rgba.size
    background = edge_background_color(rgba)
    pixels = rgba.load()
    visited = bytearray(width * height)
    queue: deque[tuple[int, int]] = deque()

    def index(x: int, y: int) -> int:
        return y * width + x

    def is_background(x: int, y: int) -> bool:
        r, g, b, alpha = pixels[x, y]
        return alpha > 0 and color_distance((r, g, b), background) <= tolerance

    for x in range(width):
        for y in (0, height - 1):
            i = index(x, y)
            if not visited[i] and is_background(x, y):
                visited[i] = 1
                queue.append((x, y))

    for y in range(height):
        for x in (0, width - 1):
            i = index(x, y)
            if not visited[i] and is_background(x, y):
                visited[i] = 1
                queue.append((x, y))

    while queue:
        x, y = queue.popleft()
        pixels[x, y] = (pixels[x, y][0], pixels[x, y][1], pixels[x, y][2], 0)

        for nx, ny in ((x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)):
            if nx < 0 or ny < 0 or nx >= width or ny >= height:
                continue

            i = index(nx, ny)
            if visited[i] or not is_background(nx, ny):
                continue

            visited[i] = 1
            queue.append((nx, ny))

    return rgba


def alpha_bbox(image: Image.Image) -> tuple[int, int, int, int]:
    alpha = image.getchannel("A")
    bbox = alpha.getbbox()
    if bbox is None:
        return (0, 0, image.width, image.height)

    return bbox


def content_bbox_against_background(image: Image.Image, tolerance: int = DARK_CROP_TOLERANCE) -> tuple[int, int, int, int]:
    rgba = image.convert("RGBA")
    background = edge_background_color(rgba)
    pixels = rgba.load()
    min_x, min_y = rgba.width, rgba.height
    max_x, max_y = 0, 0

    for y in range(rgba.height):
        for x in range(rgba.width):
            r, g, b, alpha = pixels[x, y]
            if alpha > 0 and color_distance((r, g, b), background) > tolerance:
                min_x = min(min_x, x)
                min_y = min(min_y, y)
                max_x = max(max_x, x)
                max_y = max(max_y, y)

    if min_x > max_x or min_y > max_y:
        return (0, 0, rgba.width, rgba.height)

    return (min_x, min_y, max_x + 1, max_y + 1)


def expand_bbox(bbox: tuple[int, int, int, int], image_size: tuple[int, int], margin_ratio: float) -> tuple[int, int, int, int]:
    left, top, right, bottom = bbox
    width, height = image_size
    margin = max(8, round(max(right - left, bottom - top) * margin_ratio))

    return (
        max(0, left - margin),
        max(0, top - margin),
        min(width, right + margin),
        min(height, bottom + margin),
    )


def resize_max_width(image: Image.Image, max_width: int = 800) -> Image.Image:
    if image.width <= max_width:
        return image

    ratio = max_width / image.width
    size = (max_width, round(image.height * ratio))
    return image.resize(size, Image.Resampling.LANCZOS)


def flatten_on_background(image: Image.Image, background: tuple[int, int, int]) -> Image.Image:
    canvas = Image.new("RGBA", image.size, (*background, 255))
    canvas.alpha_composite(image.convert("RGBA"))
    return canvas.convert("RGB")


def save_png(image: Image.Image, path: Path) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    image.save(path, "PNG", optimize=True, compress_level=9)


def optimize_standard(source_name: str, output_name: str, transparent: bool) -> Path:
    source = SOURCE_DIR / source_name
    image = Image.open(source)

    if transparent:
        processed = remove_connected_background(image)
        bbox = expand_bbox(alpha_bbox(processed), processed.size, STANDARD_MARGIN_RATIO)
        processed = processed.crop(bbox)
        processed = resize_max_width(processed, 800)
    else:
        rgba = image.convert("RGBA")
        background = edge_background_color(rgba)
        bbox = expand_bbox(content_bbox_against_background(rgba), rgba.size, STANDARD_MARGIN_RATIO)
        processed = flatten_on_background(rgba.crop(bbox), background)
        processed = resize_max_width(processed, 800)

    output = OUTPUT_DIR / output_name
    save_png(processed, output)
    return output


def optimize_icon() -> list[Path]:
    source = SOURCE_DIR / "sahaba_icon.png"
    image = remove_connected_background(Image.open(source))
    bbox = expand_bbox(alpha_bbox(image), image.size, STANDARD_MARGIN_RATIO)
    cropped = image.crop(bbox)

    canvas_size = 512
    inner_size = round(canvas_size * (1 - ICON_MARGIN_RATIO * 2))
    ratio = min(inner_size / cropped.width, inner_size / cropped.height)
    resized = cropped.resize((round(cropped.width * ratio), round(cropped.height * ratio)), Image.Resampling.LANCZOS)

    canvas = Image.new("RGBA", (canvas_size, canvas_size), (255, 255, 255, 0))
    offset = ((canvas_size - resized.width) // 2, (canvas_size - resized.height) // 2)
    canvas.alpha_composite(resized, offset)

    outputs = []
    icon_path = OUTPUT_DIR / "sahaba_icon.png"
    save_png(canvas, icon_path)
    outputs.append(icon_path)

    for size in FAVICON_SIZES:
        favicon = canvas if size == 512 else canvas.resize((size, size), Image.Resampling.LANCZOS)
        path = OUTPUT_DIR / f"favicon-{size}x{size}.png"
        save_png(favicon, path)
        outputs.append(path)

    return outputs


def has_alpha(path: Path) -> bool:
    image = Image.open(path)
    return image.mode in ("LA", "RGBA") or "transparency" in image.info


def dimensions(path: Path) -> tuple[int, int]:
    image = Image.open(path)
    return image.size


def write_readme(paths: list[Path]) -> None:
    usage = {
        "sahaba_original.png": "Logo principal pour documents et supports imprimables.",
        "sahaba_web.png": "Logo horizontal pour header/navbar du site web.",
        "sahaba_icon.png": "Icône principale, profil WhatsApp, app icon ou avatar.",
        "sahaba_mono.png": "Version monochrome pour cachet, documents simples ou impression une couleur.",
        "sahaba_dark.png": "Version spéciale pour fond sombre.",
    }

    lines = [
        "# Logos optimisés Madrassa Sahaba",
        "",
        "Les fichiers originaux sont conservés dans `designs/sahaba` et ne sont pas écrasés par ce script.",
        "",
        "## Fichiers générés",
        "",
        "| Fichier | Dimensions | Usage recommandé |",
        "| --- | ---: | --- |",
    ]

    for path in sorted(paths, key=lambda p: p.name):
        width, height = dimensions(path)
        if path.name.startswith("favicon-"):
            recommended = "Favicon pour le site web."
        else:
            recommended = usage.get(path.name, "Asset optimisé.")

        lines.append(f"| `{path.name}` | {width} x {height} px | {recommended} |")

    lines.extend([
        "",
        "## Notes techniques",
        "",
        "- Les versions `sahaba_original.png`, `sahaba_web.png`, `sahaba_icon.png`, `sahaba_mono.png` et `favicon-*.png` ont un canal alpha réel.",
        "- `sahaba_dark.png` conserve un fond sombre et n’est pas transparente.",
        "- Les PNG sont optimisés sans perte via Pillow (`optimize=True`, `compress_level=9`).",
    ])

    (OUTPUT_DIR / "README.md").write_text("\n".join(lines) + "\n", encoding="utf-8")


def main() -> None:
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)

    missing = [name for name, *_ in STANDARD_SPECS if not (SOURCE_DIR / name).is_file()]
    if not (SOURCE_DIR / "sahaba_icon.png").is_file():
        missing.append("sahaba_icon.png")

    if missing:
        raise SystemExit(f"Fichiers sources manquants dans {SOURCE_DIR}: {', '.join(sorted(set(missing)))}")

    created: list[Path] = []
    for spec in STANDARD_SPECS:
        created.append(optimize_standard(*spec))

    created.extend(optimize_icon())
    write_readme(created)
    created.append(OUTPUT_DIR / "README.md")

    print("Fichiers générés :")
    for path in sorted(created, key=lambda p: p.name):
        if path.suffix.lower() == ".png":
            width, height = dimensions(path)
            alpha = "alpha" if has_alpha(path) else "opaque"
            print(f"- {path.relative_to(PROJECT_DIR)}: {width}x{height}, {alpha}")
        else:
            print(f"- {path.relative_to(PROJECT_DIR)}")


if __name__ == "__main__":
    main()
