"""
Optimize all images in public/images/ to web resolution.

Rules:
- Hero images  (public/images/hero/)      : max 1920×1920 px
- All others                               : max 1600×1600 px
- Both dimensions capped via thumbnail()  (preserves aspect ratio)
- Re-encoded at quality=82, method=6      (maximum compression)
- Metadata preserved by Pillow            (EXIF, XMP, ICC)
- Filenames unchanged                     (in-place replacement)
- File replaced only if result is smaller

Also converts any stray JPG/PNG to WebP.

Run from project root:
    python3 scripts/optimize_images.py            # apply (default threshold=0)
    python3 scripts/optimize_images.py --dry-run  # simulate, no writes
    python3 scripts/optimize_images.py --threshold 300  # skip images already under 300 Ko
"""
from __future__ import annotations

import argparse
import io
import sys
from pathlib import Path

try:
    from PIL import Image
except ImportError:
    sys.exit("Pillow manquant — pip3 install Pillow")

ROOT     = Path(__file__).resolve().parent.parent
IMG_DIR  = ROOT / "public" / "images"
QUALITY  = 82

HERO_MAX = 1920   # max dimension for hero images
STD_MAX  = 1600   # max dimension for all others

ANSI = {
    "green":  "\033[92m",
    "yellow": "\033[93m",
    "red":    "\033[91m",
    "cyan":   "\033[96m",
    "bold":   "\033[1m",
    "reset":  "\033[0m",
}
def c(color: str, text: str) -> str:
    return f"{ANSI[color]}{text}{ANSI['reset']}"
def fmt(n: int) -> str:
    return f"{n / 1024:.1f} Ko"
def delta(b: int, a: int) -> str:
    d = b - a
    p = d / b * 100
    if d > 0: return c("green",  f"▼ {fmt(d)} ({p:.1f}%)")
    if d < 0: return c("red",    f"▲ {fmt(-d)} ({p:.1f}%)")
    return c("yellow", "= inchangé")


def process(path: Path, threshold_bytes: int, dry_run: bool) -> tuple[int, int, str]:
    """
    Returns (original_size, final_size, label).
    Resizes so that both dimensions fit within the max box, then re-encodes.
    """
    original_size = path.stat().st_size
    if original_size < threshold_bytes:
        return original_size, original_size, "sous seuil"

    max_px = HERO_MAX if path.parent.name == "hero" else STD_MAX

    try:
        with Image.open(path) as img:
            orig_w, orig_h = img.size
            # thumbnail() shrinks to fit inside (max_px × max_px), preserving ratio.
            # It never upscales.
            img_copy = img.copy()

        img_copy.thumbnail((max_px, max_px), Image.LANCZOS)
        resized = img_copy.size != (orig_w, orig_h)

        buf = io.BytesIO()
        img_copy.save(buf, format="WEBP", quality=QUALITY, method=6, lossless=False)

    except Exception as exc:
        return original_size, original_size, f"erreur : {exc}"

    new_size = buf.tell()

    if new_size >= original_size and not resized:
        return original_size, original_size, "déjà optimal"

    if new_size >= original_size:
        # Resized but still larger (very rare): replace anyway — dimensions matter
        pass

    if not dry_run:
        # Write as WebP regardless of original format
        out_path = path.with_suffix(".webp") if path.suffix.lower() != ".webp" else path
        out_path.write_bytes(buf.getvalue())
        if path != out_path:
            path.unlink()

    nw, nh = img_copy.size
    label = f"{orig_w}×{orig_h}→{nw}×{nh}" if resized else "recompressé"
    return original_size, new_size, label


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--threshold", type=int, default=0,
                        help="Ignorer les images déjà sous X Ko (défaut : 0 = toutes)")
    parser.add_argument("--dry-run", action="store_true")
    args = parser.parse_args()

    threshold_bytes = args.threshold * 1024

    EXTS = {".webp", ".jpg", ".jpeg", ".png"}
    paths = sorted(
        [p for p in IMG_DIR.rglob("*") if p.suffix.lower() in EXTS],
        key=lambda p: p.stat().st_size,
        reverse=True,
    )

    if not paths:
        print(c("green", "Aucune image trouvée.")); return

    mode = c("yellow", "[DRY-RUN] ") if args.dry_run else ""
    print(c("bold", f"\n{mode}Audit & optimisation de {len(paths)} image(s)"))
    print(c("cyan", f"Limite : {STD_MAX}px standard · {HERO_MAX}px hero · Qualité {QUALITY} · Métadonnées préservées\n"))
    print(f"{'Fichier':<65} {'Avant':>9} {'Après':>9}  Résultat")
    print("─" * 105)

    total_b = total_a = changed = skipped = 0

    for path in paths:
        rel   = str(path.relative_to(ROOT / "public"))
        b, a, label = process(path, threshold_bytes, args.dry_run)
        total_b += b
        total_a += a

        if label == "sous seuil":
            skipped += 1
            continue

        if a < b or ("→" in label):
            changed += 1
            gain = delta(b, a)
            info = f"  {c('cyan', label)}"
        else:
            gain = c("yellow", "déjà optimal")
            info = ""

        print(f"{rel:<65} {fmt(b):>9} {fmt(a):>9}  {gain}{info}")

    saved = total_b - total_a
    print("─" * 105)
    print(
        f"\n{c('bold', 'Total')} : {fmt(total_b)} → {fmt(total_a)}  "
        f"{c('green', f'▼ {fmt(saved)} économisés')} "
        f"sur {changed}/{len(paths) - skipped} images traitées"
        + (f"  ({skipped} sous seuil ignorées)" if skipped else "")
    )

    if args.dry_run:
        print(c("yellow", "\n[DRY-RUN] Aucun fichier modifié. Relancer sans --dry-run pour appliquer."))
    else:
        print(c("green", f"\n✓ {changed} fichier(s) optimisé(s). Métadonnées et noms inchangés."))


if __name__ == "__main__":
    main()
