<?php

/**
 * Клас для роботи з зображеннями
 * Зміна розміру, обрізання, конвертація форматів та інші операції з зображеннями
 *
 * @package Flowaxy\Infrastructure\Filesystem
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Filesystem;

use Flowaxy\Contracts\Filesystem\ImageInterface;
use Flowaxy\Support\Facades\Log;
use Exception;

final class Image implements ImageInterface
{
    private string $filePath;
    private ?\GdImage $resource = null;
    private int $width = 0;
    private int $height = 0;
    private int $type = IMAGETYPE_UNKNOWN;
    private string $mimeType = '';

    /**
     * Конструктор
     *
     * @param string|null $filePath Шлях до зображення
     */
    public function __construct(?string $filePath = null)
    {
        if (! extension_loaded('gd')) {
            throw new \Exception('Розширення GD не встановлено');
        }

        if ($filePath !== null) {
            $this->load($filePath);
        }
    }

    /**
     * Завантаження зображення
     *
     * @param string $filePath Шлях до зображення
     * @return self
     * @throws Exception Якщо не вдалося завантажити
     */
    public function load(string $filePath): self
    {
        if (! file_exists($filePath)) {
            throw new \Exception("Зображення не існує: {$filePath}");
        }

        if (! is_readable($filePath)) {
            throw new \Exception("Зображення недоступне для читання: {$filePath}");
        }

        $imageInfo = @getimagesize($filePath);

        if ($imageInfo === false) {
            throw new \Exception("Не вдалося визначити розміри зображення: {$filePath}");
        }

        $this->filePath = $filePath;
        $this->width = $imageInfo[0];
        $this->height = $imageInfo[1];
        $this->type = $imageInfo[2];
        $this->mimeType = $imageInfo['mime'];

        // Створюємо ресурс зображення
        $this->resource = match ($this->type) {
            IMAGETYPE_JPEG => @imagecreatefromjpeg($filePath),
            IMAGETYPE_PNG => @imagecreatefrompng($filePath),
            IMAGETYPE_GIF => @imagecreatefromgif($filePath),
            IMAGETYPE_WEBP => @imagecreatefromwebp($filePath),
            default => throw new \Exception("Непідтримуваний тип зображення: {$this->mimeType}"),
        };

        if ($this->resource === false) {
            throw new \Exception("Не удалось загрузить изображение: {$filePath}");
        }

        // Зберігаємо прозрачність для PNG та GIF
        if ($this->type === IMAGETYPE_PNG || $this->type === IMAGETYPE_GIF) {
            imagealphablending($this->resource, false);
            imagesavealpha($this->resource, true);
        }

        return $this;
    }

    /**
     * Створення нового зображення
     *
     * @param int $width Ширина
     * @param int $height Висота
     * @param array|null $color Колір фону [r, g, b, alpha]
     * @return self
     */
    public function create(int $width, int $height, ?array $color = null): self
    {
        $this->width = $width;
        $this->height = $height;
        $this->type = IMAGETYPE_PNG;
        $this->mimeType = 'image/png';

        $this->resource = imagecreatetruecolor($width, $height);

        imagealphablending($this->resource, false);
        imagesavealpha($this->resource, true);

        if ($color !== null) {
            $bgColor = imagecolorallocatealpha(
                $this->resource,
                $color[0] ?? 255,
                $color[1] ?? 255,
                $color[2] ?? 255,
                $color[3] ?? 0
            );
            imagefill($this->resource, 0, 0, $bgColor);
        }

        return $this;
    }

    /**
     * Змінити розмір зображення
     *
     * @param int $width Нова ширина
     * @param int $height Нова висота
     * @param bool $maintainAspectRatio Зберігати пропорції
     * @return self
     */
    public function resize(int $width, int $height, bool $maintainAspectRatio = true): self
    {
        if ($width === 0 && $height === 0) {
            return $this;
        }

        if ($maintainAspectRatio) {
            // Обчислюємо розміри зі збереженням пропорцій
            $ratio = min($width / $this->width, $height / $this->height);
            $newWidth = (int)($this->width * $ratio);
            $newHeight = (int)($this->height * $ratio);
        } else {
            $newWidth = $width;
            $newHeight = $height;
        }

        $newResource = imagecreatetruecolor($newWidth, $newHeight);

        if ($this->type === IMAGETYPE_PNG || $this->type === IMAGETYPE_GIF) {
            imagealphablending($newResource, false);
            imagesavealpha($newResource, true);
        }

        imagecopyresampled(
            $newResource,
            $this->resource,
            0,
            0,
            0,
            0,
            $newWidth,
            $newHeight,
            $this->width,
            $this->height
        );

        // В PHP 8.0+ GdImage об'єкти автоматично звільняють пам'ять
        $this->resource = null;
        $this->resource = $newResource;
        $this->width = $newWidth;
        $this->height = $newHeight;

        return $this;
    }

    /**
     * Обрезка изображения
     *
     * @param int $x Координата X
     * @param int $y Координата Y
     * @param int $width Ширина обрезки
     * @param int $height Высота обрезки
     * @return self
     */
    public function crop(int $x, int $y, int $width, int $height): self
    {
        $newResource = imagecreatetruecolor($width, $height);

        if ($this->type === IMAGETYPE_PNG || $this->type === IMAGETYPE_GIF) {
            imagealphablending($newResource, false);
            imagesavealpha($newResource, true);
        }

        imagecopyresampled(
            $newResource,
            $this->resource,
            0,
            0,
            $x,
            $y,
            $width,
            $height,
            $width,
            $height
        );

        // В PHP 8.0+ GdImage об'єкти автоматично звільняють пам'ять
        $this->resource = null;
        $this->resource = $newResource;
        $this->width = $width;
        $this->height = $height;

        return $this;
    }

    /**
     * Зберегти зображення
     *
     * @param string $savePath Шлях для збереження
     * @param int $quality Якість (0-100 для JPEG, 0-9 для PNG)
     * @return bool
     * @throws \Exception Якщо не вдалося зберегти
     */
    public function save(string $savePath, int $quality = 90): bool
    {
        if (empty($savePath)) {
            throw new \Exception('Шлях до файлу не встановлено');
        }

        // Створюємо директорію, якщо її немає
        $dir = dirname($savePath);
        if (! is_dir($dir)) {
            if (! @mkdir($dir, 0755, true)) {
                throw new \Exception("Не вдалося створити директорію: {$dir}");
            }
        }

        $result = match ($this->type) {
            IMAGETYPE_JPEG => @imagejpeg($this->resource, $savePath, $quality),
            IMAGETYPE_PNG => (function() use ($savePath, $quality) {
                $pngQuality = (int)($quality / 10);
                $pngQuality = max(0, min(9, $pngQuality));
                return @imagepng($this->resource, $savePath, $pngQuality);
            })(),
            IMAGETYPE_GIF => @imagegif($this->resource, $savePath),
            IMAGETYPE_WEBP => @imagewebp($this->resource, $savePath, $quality),
            default => throw new \Exception("Непідтримуваний тип зображення: {$this->mimeType}"),
        };

        if ($result === false) {
            throw new \Exception("Не вдалося зберегти зображення: {$savePath}");
        }

        @chmod($savePath, 0644);
        $this->filePath = $savePath;

        return true;
    }

    /**
     * Отримання ширини
     *
     * @return int
     */
    public function getWidth(): int
    {
        return $this->width;
    }

    /**
     * Отримання висоти
     *
     * @return int
     */
    public function getHeight(): int
    {
        return $this->height;
    }

    /**
     * Повернути зображення
     *
     * @param int $angle Кут повороту в градусах
     * @return self
     */
    public function rotate(int $angle): self
    {
        if ($this->resource === null) {
            throw new \Exception('Зображення не завантажено');
        }

        // Нормалізуємо кут
        $angle = $angle % 360;
        if ($angle < 0) {
            $angle += 360;
        }

        // Конвертуємо градуси в радіани для imagerotate
        $rotated = imagerotate($this->resource, -$angle, 0);

        if ($rotated === false) {
            throw new \Exception("Не вдалося повернути зображення на кут {$angle}°");
        }

        // Оновлюємо розміри
        $this->width = imagesx($rotated);
        $this->height = imagesy($rotated);

        // В PHP 8.0+ GdImage об'єкти автоматично звільняють пам'ять
        $this->resource = null;
        $this->resource = $rotated;

        return $this;
    }

    /**
     * Відобразити зображення дзеркально
     *
     * @param string $mode Режим відображення: 'horizontal' або 'vertical'
     * @return self
     */
    public function flip(string $mode = 'horizontal'): self
    {
        if ($this->resource === null) {
            throw new \Exception('Зображення не завантажено');
        }

        $newResource = imagecreatetruecolor($this->width, $this->height);

        if ($this->type === IMAGETYPE_PNG || $this->type === IMAGETYPE_GIF) {
            imagealphablending($newResource, false);
            imagesavealpha($newResource, true);
        }

        if ($mode === 'horizontal') {
            // Горизонтальне відображення
            for ($x = 0; $x < $this->width; $x++) {
                imagecopy($newResource, $this->resource, $x, 0, $this->width - $x - 1, 0, 1, $this->height);
            }
        } elseif ($mode === 'vertical') {
            // Вертикальне відображення
            for ($y = 0; $y < $this->height; $y++) {
                imagecopy($newResource, $this->resource, 0, $y, 0, $this->height - $y - 1, $this->width, 1);
            }
        } else {
            throw new \Exception("Невідомий режим відображення: {$mode}. Використовуйте 'horizontal' або 'vertical'");
        }

        // В PHP 8.0+ GdImage об'єкти автоматично звільняють пам'ять
        $this->resource = null;
        $this->resource = $newResource;

        return $this;
    }

    /**
     * Додати водяний знак
     *
     * @param string $watermarkPath Шлях до зображення водяного знака
     * @param string $position Позиція: 'top-left', 'top-right', 'bottom-left', 'bottom-right', 'center'
     * @param int $opacity Прозорість (0-100)
     * @return self
     */
    public function watermark(string $watermarkPath, string $position = 'bottom-right', int $opacity = 50): self
    {
        if ($this->resource === null) {
            throw new \Exception('Зображення не завантажено');
        }

        if (! file_exists($watermarkPath)) {
            throw new \Exception("Водяний знак не існує: {$watermarkPath}");
        }

        $watermarkInfo = @getimagesize($watermarkPath);
        if ($watermarkInfo === false) {
            throw new \Exception("Не вдалося завантажити водяний знак: {$watermarkPath}");
        }

        $watermarkType = $watermarkInfo[2];
        $watermarkWidth = $watermarkInfo[0];
        $watermarkHeight = $watermarkInfo[1];

        // Завантажуємо водяний знак
        $watermarkResource = match ($watermarkType) {
            IMAGETYPE_JPEG => @imagecreatefromjpeg($watermarkPath),
            IMAGETYPE_PNG => @imagecreatefrompng($watermarkPath),
            IMAGETYPE_GIF => @imagecreatefromgif($watermarkPath),
            IMAGETYPE_WEBP => @imagecreatefromwebp($watermarkPath),
            default => throw new \Exception("Непідтримуваний тип зображення водяного знака"),
        };

        if ($watermarkResource === false) {
            throw new \Exception("Не вдалося створити ресурс водяного знака");
        }

        // Обчислюємо позицію
        $x = match ($position) {
            'top-left', 'bottom-left' => 10,
            'top-right', 'bottom-right' => $this->width - $watermarkWidth - 10,
            'center' => (int)(($this->width - $watermarkWidth) / 2),
            default => $this->width - $watermarkWidth - 10,
        };

        $y = match ($position) {
            'top-left', 'top-right' => 10,
            'bottom-left', 'bottom-right' => $this->height - $watermarkHeight - 10,
            'center' => (int)(($this->height - $watermarkHeight) / 2),
            default => $this->height - $watermarkHeight - 10,
        };

        // Застосовуємо прозорість для PNG
        if ($watermarkType === IMAGETYPE_PNG) {
            imagealphablending($watermarkResource, false);
            imagesavealpha($watermarkResource, true);

            // Застосовуємо opacity
            if ($opacity < 100) {
                $opacityAlpha = (int)(127 * (100 - $opacity) / 100);
                imagefilter($watermarkResource, IMG_FILTER_COLORIZE, 0, 0, 0, $opacityAlpha);
            }
        }

        // Накладаємо водяний знак
        imagealphablending($this->resource, true);
        imagecopy($this->resource, $watermarkResource, $x, $y, 0, 0, $watermarkWidth, $watermarkHeight);

        // Звільняємо пам'ять водяного знака
        imagedestroy($watermarkResource);

        return $this;
    }

    /**
     * Вивести зображення
     *
     * @param int $quality Якість (0-100 для JPEG, 0-9 для PNG)
     * @return void
     */
    public function output(int $quality = 90): void
    {
        if ($this->resource === null) {
            throw new \Exception('Зображення не завантажено');
        }

        // Встановлюємо заголовки
        header('Content-Type: ' . $this->mimeType);

        match ($this->type) {
            IMAGETYPE_JPEG => imagejpeg($this->resource, null, $quality),
            IMAGETYPE_PNG => imagepng($this->resource, null, (int)($quality / 10)),
            IMAGETYPE_GIF => imagegif($this->resource),
            IMAGETYPE_WEBP => imagewebp($this->resource, null, $quality),
            default => throw new \Exception("Непідтримуваний тип зображення для виводу: {$this->mimeType}"),
        };
    }

    /**
     * Отримання типу зображення
     *
     * @return int
     */
    public function getType(): int
    {
        return $this->type;
    }

    /**
     * Отримання MIME типу
     *
     * @return string
     */
    public function getMimeType(): string
    {
        return $this->mimeType;
    }

    /**
     * Отримання ресурсу зображення
     *
     * @return \GdImage|null
     */
    public function getResource(): ?\GdImage
    {
        return $this->resource;
    }

    /**
     * Звільнення пам'яті
     */
    public function __destruct()
    {
        // В PHP 8.0+ GdImage об'єкти автоматично звільняють пам'ять через garbage collector
        // Встановлення null достатньо для явного звільнення
        $this->resource = null;
    }

    /**
     * Статичний метод: Створення мініатюри
     *
     * @param string $sourcePath Исходный путь
     * @param string $destinationPath Путь назначения
     * @param int $width Ширина
     * @param int $height Высота
     * @param bool $crop Обрезать ли
     * @return bool
     */
    public static function thumbnail(string $sourcePath, string $destinationPath, int $width, int $height = 0, bool $crop = false): bool
    {
        try {
            $image = new self($sourcePath);
            $image->resize($width, $height, $crop);

            return $image->save($destinationPath, 90);
        } catch (\Exception $e) {
            Log::Error('Image::thumbnail error: ' . $e->getMessage(), ['exception' => $e, 'source' => $sourcePath, 'destination' => $destinationPath]);
            return false;
        }
    }

    /**
     * Статический метод: Конвертация формата
     *
     * @param string $sourcePath Исходный путь
     * @param string $destinationPath Путь назначения
     * @param int $targetType Тип изображения (IMAGETYPE_JPEG, IMAGETYPE_PNG и т.д.)
     * @param int|null $quality Качество
     * @return bool
     */
    public static function convert(string $sourcePath, string $destinationPath, int $targetType, ?int $quality = null): bool
    {
        try {
            $image = new self($sourcePath);

            // Встановлюємо тип зображення перед збереженням
            $image->type = $targetType;
            $image->mimeType = match ($targetType) {
                IMAGETYPE_JPEG => 'image/jpeg',
                IMAGETYPE_PNG => 'image/png',
                IMAGETYPE_GIF => 'image/gif',
                IMAGETYPE_WEBP => 'image/webp',
                default => 'image/png',
            };
            return $image->save($destinationPath, $quality ?? 90);
        } catch (\Exception $e) {
            Log::Error('Image::convert error: ' . $e->getMessage(), ['exception' => $e, 'source' => $sourcePath, 'destination' => $destinationPath]);
            return false;
        }
    }
}
