<?php
declare(strict_types=1);

namespace Celena\Core\Support;

use RuntimeException;

/**
 * Ресайз и оптимизация изображений через GD (опционально Imagick).
 * При загрузке создаём три варианта:
 *   - original (источник, уменьшённый до maxOriginal)
 *   - @large   (до 1600px по длинной стороне)
 *   - @medium  (до 800px)
 *   - @thumb   (до 320px, квадратный crop)
 * Все сохраняются рядом с оригиналом (suffix перед расширением).
 */
final class ImageProcessor
{
    /** @var array<string, array{max:int, crop:bool}> */
    private array $sizes;

    public function __construct(
        private readonly int $maxOriginal = 2400,
        private readonly int $jpegQuality = 82,
        private readonly int $webpQuality = 80,
        int $thumbSize = 320,
        int $mediumSize = 800,
        int $largeSize = 1600,
    ) {
        if (!extension_loaded('gd')) {
            throw new RuntimeException('ext-gd required for ImageProcessor.');
        }
        $this->sizes = [
            'thumb'  => ['max' => max(32, $thumbSize),  'crop' => true],
            'medium' => ['max' => max(32, $mediumSize), 'crop' => false],
            'large'  => ['max' => max(32, $largeSize),  'crop' => false],
        ];
    }

    /** Проверить, является ли файл обрабатываемым изображением. */
    public static function isImage(string $mime): bool
    {
        return in_array($mime, ['image/jpeg', 'image/png', 'image/webp', 'image/gif'], true);
    }

    /**
     * Обработать файл: сжать оригинал + создать варианты.
     * @return array{original_url:string, width:?int, height:?int, variants:array<string,string>}
     */
    public function process(string $absPath, string $publicUrl): array
    {
        $info = @getimagesize($absPath);
        if ($info === false) {
            throw new RuntimeException('Не изображение: ' . basename($absPath));
        }
        [$w, $h] = $info;
        $mime = (string) $info['mime'];

        $src = $this->load($absPath, $mime);
        if ($src === null) {
            return ['original_url' => $publicUrl, 'width' => $w, 'height' => $h, 'variants' => []];
        }

        // Уменьшаем оригинал, если больше $maxOriginal
        if ($w > $this->maxOriginal || $h > $this->maxOriginal) {
            $resized = $this->scaleToMax($src, $this->maxOriginal);
            $this->save($resized, $absPath, $mime);
            imagedestroy($src);
            $src = $resized;
            [$w, $h] = [imagesx($src), imagesy($src)];
        }

        $variants = [];
        foreach ($this->sizes as $name => $cfg) {
            $variant = $cfg['crop']
                ? $this->squareCrop($src, $cfg['max'])
                : $this->scaleToMax($src, $cfg['max']);
            $variantPath = $this->variantPath($absPath, $name);
            $this->save($variant, $variantPath, $mime);
            imagedestroy($variant);
            $variants[$name] = $this->variantPath($publicUrl, $name);
        }

        imagedestroy($src);
        return [
            'original_url' => $publicUrl,
            'width' => $w,
            'height' => $h,
            'variants' => $variants,
        ];
    }

    private function load(string $path, string $mime): ?\GdImage
    {
        return match ($mime) {
            'image/jpeg' => @imagecreatefromjpeg($path) ?: null,
            'image/png'  => @imagecreatefrompng($path)  ?: null,
            'image/webp' => @imagecreatefromwebp($path) ?: null,
            'image/gif'  => @imagecreatefromgif($path)  ?: null,
            default      => null,
        };
    }

    private function save(\GdImage $img, string $path, string $mime): void
    {
        match ($mime) {
            'image/jpeg' => imagejpeg($img, $path, $this->jpegQuality),
            'image/png'  => imagepng($img, $path, 6),
            'image/webp' => imagewebp($img, $path, $this->webpQuality),
            'image/gif'  => imagegif($img, $path),
            default      => null,
        };
    }

    private function scaleToMax(\GdImage $src, int $max): \GdImage
    {
        $w = imagesx($src);
        $h = imagesy($src);
        if ($w <= $max && $h <= $max) {
            // Просто копируем (truecolor)
            $dst = imagecreatetruecolor($w, $h);
            imagecopy($dst, $src, 0, 0, 0, 0, $w, $h);
            return $dst;
        }
        $ratio = $w > $h ? $max / $w : $max / $h;
        $nw = (int) round($w * $ratio);
        $nh = (int) round($h * $ratio);
        $dst = imagecreatetruecolor($nw, $nh);
        imagealphablending($dst, false);
        imagesavealpha($dst, true);
        imagecopyresampled($dst, $src, 0, 0, 0, 0, $nw, $nh, $w, $h);
        return $dst;
    }

    private function squareCrop(\GdImage $src, int $size): \GdImage
    {
        $w = imagesx($src);
        $h = imagesy($src);
        $side = min($w, $h);
        $x = (int) (($w - $side) / 2);
        $y = (int) (($h - $side) / 2);
        $dst = imagecreatetruecolor($size, $size);
        imagealphablending($dst, false);
        imagesavealpha($dst, true);
        imagecopyresampled($dst, $src, 0, 0, $x, $y, $size, $size, $side, $side);
        return $dst;
    }

    /** /path/uuid.jpg + "thumb" → /path/uuid@thumb.jpg */
    private function variantPath(string $path, string $name): string
    {
        $ext = pathinfo($path, PATHINFO_EXTENSION);
        $base = $ext !== '' ? substr($path, 0, -(strlen($ext) + 1)) : $path;
        return $base . '@' . $name . ($ext !== '' ? '.' . $ext : '');
    }
}
