"""
Cover / hero image loading with contain-fit + letterbox, and a cheap
blurred backdrop. All cached. Honest aspect: a professionally-designed box
is shown WHOLE at its native aspect (contain), never cropped to a tile.
"""

from __future__ import annotations

import os
from functools import lru_cache

import pygame

from ..design import tokens as t

LETTERBOX = (26, 28, 32)        # #1a1c20 — the dark matte behind a contained box
_MISSING = (22, 24, 28)


@lru_cache(maxsize=512)
def _raw(path: str) -> pygame.Surface | None:
    if not path or not os.path.exists(path):
        return None
    try:
        return pygame.image.load(path).convert_alpha()
    except Exception:
        return None


@lru_cache(maxsize=512)
def cover(path: str | None, w: int, h: int, *, radius: int = t.Radius.TILE,
          mode: str = "contain", focus: tuple[float, float] = (0.5, 0.5)
          ) -> pygame.Surface:
    """
    Return a w×h surface with the image fit by `mode` ('contain'|'cover'),
    rounded to `radius`, on a letterbox matte. `focus` (fx,fy) biases the crop
    in cover mode (CSS object-position). Missing → neutral placeholder.
    """
    surf = pygame.Surface((w, h), pygame.SRCALPHA)
    base = _raw(path)
    if base is None:
        pygame.draw.rect(surf, _MISSING, (0, 0, w, h), border_radius=radius)
        return surf
    bw, bh = base.get_size()
    if mode == "cover":
        scale = max(w / bw, h / bh)
    else:
        scale = min(w / bw, h / bh)
    nw, nh = max(1, int(bw * scale)), max(1, int(bh * scale))
    scaled = pygame.transform.smoothscale(base, (nw, nh))

    # Composite directly on an SRCALPHA target. The previous flow built an
    # opaque RGB `inner`, blitted the SRCALPHA `scaled` into it, then called
    # `inner.convert_alpha()` to get an alpha channel — that round-trip drops
    # the dest alpha to 0 on the macOS cocoa SDL driver (alpha=255 on dummy /
    # KMSDRM), and the rounded-corner BLEND_RGBA_MULT below then zeros the
    # whole cover so it blits as transparent (dark window). Allocating `out`
    # as SRCALPHA up-front, with an opaque letterbox fill, keeps alpha at 255
    # across drivers and the mask only carves the rounded corners.
    out = pygame.Surface((w, h), pygame.SRCALPHA)
    out.fill((LETTERBOX[0], LETTERBOX[1], LETTERBOX[2], 255))
    out.blit(scaled, (int((w - nw) * focus[0]), int((h - nh) * focus[1])))

    # round the corners via an alpha mask
    mask = pygame.Surface((w, h), pygame.SRCALPHA)
    pygame.draw.rect(mask, (255, 255, 255, 255), (0, 0, w, h), border_radius=radius)
    out.blit(mask, (0, 0), special_flags=pygame.BLEND_RGBA_MULT)
    return out


def soft_blur(surf: pygame.Surface, radius: int) -> pygame.Surface:
    """A genuine smooth blur — real Gaussian where available (pygame-ce ≥2.4),
    else a multi-tap downscale→upscale fallback. NOT the single harsh 1/8
    downscale, which on-device reads like a crushed JPEG."""
    if hasattr(pygame.transform, "gaussian_blur"):
        try:
            return pygame.transform.gaussian_blur(surf, max(1, radius))
        except Exception:
            pass
    w, h = surf.get_size()
    s = pygame.transform.smoothscale(surf, (max(1, w // 3), max(1, h // 3)))
    s = pygame.transform.smoothscale(s, (max(1, w // 2), max(1, h // 2)))
    return pygame.transform.smoothscale(s, (w, h))


@lru_cache(maxsize=64)
def backdrop(path: str | None, w: int, h: int, *, blur: int = 10,
             darken: float = 0.55) -> pygame.Surface:
    """
    Cinematic frosted backdrop (cover-fit), softly blurred + darkened so
    foreground text stays legible. Cached, so the Gaussian is computed once.
    """
    surf = pygame.Surface((w, h))
    surf.fill(t.Color.BG_DEEP)
    base = _raw(path)
    if base is None:
        return surf
    bw, bh = base.get_size()
    scale = max(w / bw, h / bh)
    nw, nh = max(1, int(bw * scale)), max(1, int(bh * scale))
    scaled = pygame.transform.smoothscale(base, (nw, nh))
    # blur a half-res copy then upscale — smooth (no blocky downsample edges)
    half = pygame.transform.smoothscale(scaled, (max(1, nw // 2), max(1, nh // 2)))
    half = soft_blur(half, blur)
    blurred = pygame.transform.smoothscale(half, (nw, nh))
    surf.blit(blurred, ((w - nw) // 2, (h - nh) // 2))
    veil = pygame.Surface((w, h), pygame.SRCALPHA)
    veil.fill((t.Color.BG_DEEP[0], t.Color.BG_DEEP[1], t.Color.BG_DEEP[2],
               int(darken * 255)))
    surf.blit(veil, (0, 0))
    return surf


@lru_cache(maxsize=256)
def screenshot_fit(path: str | None, w: int, h: int) -> pygame.Surface:
    """
    Fit a screenshot into w×h, centered, transparent margins. Pixel-art snaps
    (native ≤400px) are upscaled with **nearest-neighbor at an integer factor**
    (crisp pixels, per the manifesto); larger/photographic images use smooth
    contain; downscales use smooth.
    """
    surf = pygame.Surface((w, h), pygame.SRCALPHA)
    base = _raw(path)
    if base is None:
        return surf
    bw, bh = base.get_size()
    if max(bw, bh) <= 400:                       # pixel-art
        k = min(w // bw, h // bh)
        if k >= 1:                               # integer nearest upscale
            scaled = pygame.transform.scale(base, (bw * k, bh * k))  # nearest
            surf.blit(scaled, ((w - bw * k) // 2, (h - bh * k) // 2))
            return surf
    # photographic, or target smaller than native → smooth contain
    scale = min(w / bw, h / bh)
    nw, nh = max(1, int(bw * scale)), max(1, int(bh * scale))
    surf.blit(pygame.transform.smoothscale(base, (nw, nh)),
              ((w - nw) // 2, (h - nh) // 2))
    return surf


# Native cover aspect per system (w:h) — covers are shown whole at these.
SYSTEM_ASPECT = {
    "SNES": 1.40, "Genesis": 0.72, "N64": 0.72, "NES": 0.70,
    "Game Boy": 0.63, "Game Boy Color": 0.63, "GBA": 1.0, "Game Gear": 0.70,
    "Master System": 1.0, "PS1": 1.0, "PSP": 0.58, "PC Engine": 1.0,
    "Neo Geo": 1.0,
}


def tile_size(system: str, height: int) -> tuple[int, int]:
    """Tile width for a given height honoring the system's native aspect."""
    asp = SYSTEM_ASPECT.get(system, 1.40)
    return (max(1, int(height * asp)), height)
