<?php

declare(strict_types=1);

namespace App\Core;

use RuntimeException;

/**
 * Traitement d'images (GD) — D-19 / D-26.
 * Génère un dérivé par profil (recadrage ou ajustement proportionnel), en JPEG + WebP.
 * Les PNG avec transparence sont conservés en PNG + WebP alpha. Les originaux ne sont PAS conservés.
 */
final class ImageService
{
    public const ACCEPTED = ['image/jpeg', 'image/png'];

    private const JPEG_QUALITY = 82;
    private const WEBP_QUALITY = 90;

    /**
     * @param list<array<string,mixed>> $profiles
     * @return array{width:int,height:int,ext:string}
     */
    public static function process(string $sourcePath, int $galleryId, string $base, array $profiles): array
    {
        $info = @getimagesize($sourcePath);
        if ($info === false || !in_array($info[2], [IMAGETYPE_JPEG, IMAGETYPE_PNG], true)) {
            throw new RuntimeException('Format d\'image non supporté.');
        }

        $isPng = $info[2] === IMAGETYPE_PNG;
        $img   = $isPng ? imagecreatefrompng($sourcePath) : imagecreatefromjpeg($sourcePath);
        if ($img === false) {
            throw new RuntimeException('Lecture de l\'image impossible.');
        }
        $img = self::fixOrientation($img, $sourcePath, $info[2]);

        // Transparence : on conserve PNG + WebP alpha ; sinon JPEG + WebP.
        $transparent = $isPng && self::hasAlpha($sourcePath);
        $ext         = $transparent ? 'png' : 'jpg';

        $srcW   = imagesx($img);
        $srcH   = imagesy($img);
        $webpOk = function_exists('imagewebp');

        foreach ($profiles as $p) {
            $dir = BASE_PATH . '/media/galleries/' . $galleryId . '/' . $p['key'];
            self::ensureDir($dir);

            $dst = self::resample($img, $srcW, $srcH, (int) $p['width'], (int) $p['height'], (bool) $p['crop'], $transparent);
            if ($transparent) {
                imagepng($dst, $dir . '/' . $base . '.png');
            } else {
                imagejpeg($dst, $dir . '/' . $base . '.jpg', self::JPEG_QUALITY);
            }
            if (!empty($p['webp']) && $webpOk) {
                imagewebp($dst, $dir . '/' . $base . '.webp', self::WEBP_QUALITY);
            }
            imagedestroy($dst);
        }

        imagedestroy($img);

        return ['width' => $srcW, 'height' => $srcH, 'ext' => $ext];
    }

    /** Supprime tous les dérivés (jpg/png/webp) d'une image. */
    public static function deleteFor(int $galleryId, string $base, array $profiles): void
    {
        foreach ($profiles as $p) {
            $dir = BASE_PATH . '/media/galleries/' . $galleryId . '/' . $p['key'];
            @unlink($dir . '/' . $base . '.jpg');
            @unlink($dir . '/' . $base . '.png');
            @unlink($dir . '/' . $base . '.webp');
        }
    }

    /** Supprime les dérivés d'une extension donnée (nettoyage de l'ancien format lors d'un remplacement). */
    public static function deleteDerivative(int $galleryId, string $base, string $ext, array $profiles): void
    {
        foreach ($profiles as $p) {
            @unlink(BASE_PATH . '/media/galleries/' . $galleryId . '/' . $p['key'] . '/' . $base . '.' . $ext);
        }
    }

    /** Détecte une transparence dans un PNG via l'en-tête IHDR (sans décoder toute l'image). */
    private static function hasAlpha(string $path): bool
    {
        $fh = @fopen($path, 'rb');
        if ($fh === false) {
            return false;
        }
        $head = (string) fread($fh, 33);
        if (strlen($head) < 26 || substr($head, 0, 8) !== "\x89PNG\r\n\x1a\n") {
            fclose($fh);
            return false;
        }
        $colorType = ord($head[25]); // IHDR : 2=RGB, 3=palette, 4=gris+alpha, 6=RGBA
        if (($colorType & 4) !== 0) {
            fclose($fh);
            return true;
        }
        if ($colorType === 3) {
            // Palette : transparence éventuelle déclarée par un chunk tRNS.
            $rest = (string) fread($fh, 1_000_000);
            fclose($fh);
            return str_contains($head . $rest, 'tRNS');
        }
        fclose($fh);
        return false;
    }

    /** @return \GdImage */
    private static function resample(\GdImage $img, int $sw, int $sh, int $tw, int $th, bool $crop, bool $transparent = false)
    {
        if ($crop && $tw > 0 && $th > 0) {
            // Recadrage « cover » centré.
            $scale  = max($tw / $sw, $th / $sh);
            $nw     = (int) ceil($sw * $scale);
            $nh     = (int) ceil($sh * $scale);
            $scaled = self::canvas($nw, $nh, $transparent);
            imagecopyresampled($scaled, $img, 0, 0, 0, 0, $nw, $nh, $sw, $sh);
            $dst = self::canvas($tw, $th, $transparent);
            imagecopy($dst, $scaled, 0, 0, (int) (($nw - $tw) / 2), (int) (($nh - $th) / 2), $tw, $th);
            imagedestroy($scaled);
            return $dst;
        }

        // Ajustement proportionnel (sans agrandissement), borné par largeur et/ou hauteur.
        $scale = 1.0;
        if ($tw > 0) {
            $scale = min($scale, $tw / $sw);
        }
        if ($th > 0) {
            $scale = min($scale, $th / $sh);
        }
        if ($scale > 1.0) {
            $scale = 1.0;
        }
        $nw  = max(1, (int) round($sw * $scale));
        $nh  = max(1, (int) round($sh * $scale));
        $dst = self::canvas($nw, $nh, $transparent);
        imagecopyresampled($dst, $img, 0, 0, 0, 0, $nw, $nh, $sw, $sh);
        return $dst;
    }

    private static function canvas(int $w, int $h, bool $transparent = false): \GdImage
    {
        $im = imagecreatetruecolor($w, $h);
        if ($transparent) {
            imagealphablending($im, false);
            imagesavealpha($im, true);
            imagefilledrectangle($im, 0, 0, $w, $h, imagecolorallocatealpha($im, 0, 0, 0, 127));
        } else {
            imagefilledrectangle($im, 0, 0, $w, $h, imagecolorallocate($im, 255, 255, 255));
        }
        return $im;
    }

    private static function fixOrientation(\GdImage $img, string $path, int $type): \GdImage
    {
        if ($type !== IMAGETYPE_JPEG || !function_exists('exif_read_data')) {
            return $img;
        }
        $exif = @exif_read_data($path);
        $orientation = is_array($exif) ? (int) ($exif['Orientation'] ?? 0) : 0;

        $rotated = match ($orientation) {
            3 => imagerotate($img, 180, 0),
            6 => imagerotate($img, -90, 0),
            8 => imagerotate($img, 90, 0),
            default => null,
        };

        if ($rotated instanceof \GdImage) {
            imagedestroy($img);
            return $rotated;
        }
        return $img;
    }

    private static function ensureDir(string $dir): void
    {
        if (!is_dir($dir)) {
            @mkdir($dir, 0775, true);
        }
    }
}
