#!/usr/bin/env python3
"""
FreeExile Level of Detail (LOD) Sprite Generator.
Generates 3-tier LOD assets (LOD-0 100%, LOD-1 50%, LOD-2 25%) using Pillow Lanczos resampling.
Guarantees LOD-2 file size <= 40% of original for mobile VRAM and bandwidth savings.
"""

from __future__ import annotations

import argparse
from dataclasses import dataclass
import os
from pathlib import Path
import sys
from typing import Dict, List, Optional, Tuple

from PIL import Image

# Canonical LOD scaling ratios
LOD_SPECS: Tuple[Tuple[str, float], ...] = (
    ("lod0", 1.0),
    ("lod1", 0.5),
    ("lod2", 0.25),
)


@dataclass(slots=True, frozen=True)
class LODGenerationResult:
    """Stores metadata and byte sizes for generated LOD tiers."""
    source_path: Path
    original_size: int
    lod_paths: Dict[str, Path]
    lod_sizes: Dict[str, int]
    lod2_size_ratio: float


def _resample_and_save(img: Image.Image, out_path: Path, scale: float) -> int:
    """Downsamples image with Lanczos filter and writes to out_path."""
    if scale >= 0.999:
        scaled = img.copy()
    else:
        new_w = max(1, int(round(img.width * scale)))
        new_h = max(1, int(round(img.height * scale)))
        scaled = img.resize((new_w, new_h), Image.Resampling.LANCZOS)

    out_path.parent.mkdir(parents=True, exist_ok=True)
    scaled.save(out_path, format="PNG", optimize=True)
    return out_path.stat().st_size


def generate_sprite_lods(
    image_path: Path | str,
    output_dir: Optional[Path | str] = None,
) -> Dict[str, Path]:
    """Generates 3 LOD levels (LOD-0 100%, LOD-1 50%, LOD-2 25%) for a sprite image.

    Returns a dictionary mapping both lowercase ('lod0') and uppercase ('LOD-0') keys to paths.
    """
    src = Path(image_path).resolve()
    if not src.exists() or not src.is_file():
        raise FileNotFoundError(f"Source sprite image not found: {src}")

    target_dir = Path(output_dir).resolve() if output_dir else src.parent
    target_dir.mkdir(parents=True, exist_ok=True)

    result_paths: Dict[str, Path] = {}
    with Image.open(src) as base_img:
        mode = "RGBA" if base_img.mode in ("RGBA", "LA") or ("transparency" in base_img.info) else "RGB"
        img = base_img.convert(mode)

        for lod_name, scale in LOD_SPECS:
            out_file = target_dir / f"{src.stem}_{lod_name}{src.suffix}"
            _resample_and_save(img, out_file, scale)
            result_paths[lod_name] = out_file
            result_paths[lod_name.upper()] = out_file
            # Also support hyphenated alias e.g. LOD-0, LOD-1, LOD-2
            hyphen_key = f"LOD-{lod_name[-1]}"
            result_paths[hyphen_key] = out_file

    return result_paths


def evaluate_lod_generation(
    image_path: Path | str,
    output_dir: Optional[Path | str] = None,
) -> LODGenerationResult:
    """Generates LODs and computes empirical file size and compression ratios."""
    src = Path(image_path).resolve()
    orig_size = src.stat().st_size
    paths = generate_sprite_lods(src, output_dir)

    lod_sizes: Dict[str, int] = {
        name: paths[name].stat().st_size for name, _ in LOD_SPECS
    }
    lod2_ratio = lod_sizes["lod2"] / max(1, orig_size)

    return LODGenerationResult(
        source_path=src,
        original_size=orig_size,
        lod_paths=paths,
        lod_sizes=lod_sizes,
        lod2_size_ratio=lod2_ratio,
    )


def batch_generate_lods(
    directory: Path | str,
    output_dir: Optional[Path | str] = None,
    pattern: str = "*.png",
) -> List[LODGenerationResult]:
    """Generates LODs for all matching textures within a directory."""
    src_dir = Path(directory).resolve()
    results: List[LODGenerationResult] = []

    for file_path in sorted(src_dir.glob(pattern)):
        # Skip previously generated lod files to prevent recursive self-processing
        if any(f"_{lod}" in file_path.stem for lod, _ in LOD_SPECS):
            continue
        res = evaluate_lod_generation(file_path, output_dir)
        results.append(res)

    return results


def main() -> None:
    """CLI entry point for LOD generation."""
    parser = argparse.ArgumentParser(description="FreeExile Sprite LOD Generator")
    parser.add_argument("--input", "-i", type=Path, help="Source PNG file")
    parser.add_argument("--output-dir", "-o", type=Path, default=None, help="Output directory")
    parser.add_argument("--batch", "-b", type=Path, default=None, help="Batch directory of PNGs")
    parser.add_argument("--pattern", type=str, default="*.png", help="Glob pattern for batch")
    parser.add_argument("--verify-budget", action="store_true", help="Assert LOD-2 <= 40% size")
    args = parser.parse_args()

    if args.input:
        res = evaluate_lod_generation(args.input, args.output_dir)
        pct = res.lod2_size_ratio * 100.0
        print(f"Processed: {res.source_path.name}")
        print(f"  Original: {res.original_size} bytes")
        for k, v in res.lod_sizes.items():
            print(f"  {k}: {v} bytes ({v / res.original_size * 100:.1f}%)")
        if args.verify_budget and res.lod2_size_ratio > 0.40:
            print(f"[ERROR] LOD-2 exceeds 40% budget: {pct:.2f}%", file=sys.stderr)
            sys.exit(1)
    elif args.batch:
        results = batch_generate_lods(args.batch, args.output_dir, args.pattern)
        print(f"Batch processed {len(results)} textures in {args.batch}")
        for r in results:
            print(f"  {r.source_path.name} -> LOD-2: {r.lod2_size_ratio * 100:.1f}%")
    else:
        parser.print_help()


if __name__ == "__main__":
    main()
