<?php
namespace Shared\Image;

class ImageManipulator
{
    protected int $width;
    protected int $height;
    /** @var resource|\GdImage */
    protected $image;
    protected ?string $file = null;

    /**
     * Image manipulator constructor
     *
     * @param string|null $file Path to image file or image data as string
     */
    public function __construct(?string $file = null)
    {
        if ($file !== null) {
            $this->file = $file;

            if (is_file($file)) {
                $this->setImageFile($file);
            } else {
                $this->setImageString($file);
            }
        }
    }

    /**
     * Set image resource from file
     *
     * @param string $file Path to image file
     * @return self
     * @throws \InvalidArgumentException
     */
    public function setImageFile(string $file): self
    {
        if (!(is_readable($file) && is_file($file))) {
            throw new \InvalidArgumentException("Image file $file is not readable");
        }

        if (isset($this->image) && $this->isValidImageResource($this->image)) {
            imagedestroy($this->image);
        }

        [$width, $height, $type] = getimagesize($file);

        if ($width === false || $height === false) {
            throw new \InvalidArgumentException("Unable to get image size for $file");
        }

        error_log("Loaded image size from file: width: $width, height: $height, type: $type");

        switch ($type) {
            case IMAGETYPE_GIF:
                $this->image = imagecreatefromgif($file);
                break;
            case IMAGETYPE_JPEG:
                $this->image = imagecreatefromjpeg($file);
                break;
            case IMAGETYPE_PNG:
                $this->image = imagecreatefrompng($file);
                break;
            case IMAGETYPE_WEBP:
                $this->image = imagecreatefromwebp($file);
                break;
            default:
                throw new \InvalidArgumentException("Image type $type not supported");
        }

        if (!$this->isValidImageResource($this->image)) {
            throw new \InvalidArgumentException("Failed to create image from $file");
        }

        $this->width = imagesx($this->image);
        $this->height = imagesy($this->image);

        error_log("Set image dimensions: width: {$this->width}, height: {$this->height}");

        if ($this->width === 0 || $this->height === 0) {
            throw new \InvalidArgumentException("Image dimensions are invalid (width: $this->width, height: $this->height)");
        }

        return $this;
    }

    /**
     * Set image resource from string data
     *
     * @param string $data Image data as string
     * @return self
     * @throws \RuntimeException
     */
    public function setImageString(string $data): self
    {
        if (isset($this->image) && $this->isValidImageResource($this->image)) {
            imagedestroy($this->image);
        }

        $image = imagecreatefromstring($data);
        if (!$this->isValidImageResource($image)) {
            throw new \RuntimeException('Cannot create image from data string');
        }

        $this->image = $image;
        $this->width = imagesx($this->image);
        $this->height = imagesy($this->image);

        error_log("Set image dimensions from string: width: {$this->width}, height: {$this->height}");

        if ($this->width === 0 || $this->height === 0) {
            throw new \RuntimeException("Image dimensions are invalid (width: $this->width, height: $this->height)");
        }

        return $this;
    }

    /**
     * Resamples the current image
     *
     * @param int  $width                New width
     * @param int  $height               New height
     * @param bool $constrainProportions Constrain current image proportions when resizing
     * @return self
     * @throws \RuntimeException
     */
    public function resample(int $width, int $height, bool $constrainProportions = true): self
    {
        if (!isset($this->image) || !$this->isValidImageResource($this->image)) {
            throw new \RuntimeException('No image set');
        }

        if ($constrainProportions) {
            if ($this->height === 0) {
                throw new \RuntimeException('Image height is zero, cannot calculate aspect ratio');
            }

            $aspectRatio = $this->width / $this->height;

            // Ustaw domyślną wysokość, jeśli podana jest równa zero
            if ($height === 0) {
                $height = (int) round($width / $aspectRatio);
            }

            if ($width / $height > $aspectRatio) {
                $width = (int) round($height * $aspectRatio);
            } else {
                $height = (int) round($width / $aspectRatio);
            }

            if ($width <= 0 || $height <= 0) {
                throw new \RuntimeException('Calculated dimensions are invalid (width: ' . $width . ', height: ' . $height . ')');
            }
        }

        // reszta kodu metody
        return $this;
    }


    /**
     * Enlarge canvas
     *
     * @param int        $width  Canvas width
     * @param int        $height Canvas height
     * @param array      $rgb    RGB colour values [R, G, B]
     * @param int|null   $xpos   X-Position of image in new canvas, null for centre
     * @param int|null   $ypos   Y-Position of image in new canvas, null for centre
     * @return self
     * @throws \RuntimeException
     */
    public function enlargeCanvas(int $width, int $height, array $rgb = [], ?int $xpos = null, ?int $ypos = null): self
    {
        if (!isset($this->image) || !$this->isValidImageResource($this->image)) {
            throw new \RuntimeException('No image set');
        }

        $width = max($width, $this->width);
        $height = max($height, $this->height);

        $temp = imagecreatetruecolor($width, $height);
        if (!$this->isValidImageResource($temp)) {
            throw new \RuntimeException('Failed to create a new image for enlarging canvas');
        }

        // Fill background if RGB provided
        if (count($rgb) === 3) {
            [$r, $g, $b] = $rgb;
            $bg = imagecolorallocate($temp, $r, $g, $b);
            imagefill($temp, 0, 0, $bg);
        } else {
            // Preserve transparency
            imagealphablending($temp, false);
            imagesavealpha($temp, true);
            $transparent = imagecolorallocatealpha($temp, 255, 255, 255, 127);
            imagefilledrectangle($temp, 0, 0, $width, $height, $transparent);
        }

        // Calculate positions
        if ($xpos === null) {
            $xpos = (int) round(($width - $this->width) / 2);
        }
        if ($ypos === null) {
            $ypos = (int) round(($height - $this->height) / 2);
        }

        // Logowanie przed kopiowaniem obrazu na nowe płótno
        error_log("Enlarging canvas: xpos: $xpos, ypos: $ypos");

        if (!imagecopy(
            $temp,
            $this->image,
            $xpos,
            $ypos,
            0,
            0,
            $this->width,
            $this->height
        )) {
            throw new \RuntimeException('Failed to copy image onto enlarged canvas');
        }

        return $this->_replace($temp);
    }

    /**
     * Crop image
     *
     * @param int|array $x1 Top left x-coordinate of crop box or array of coordinates [x1, y1, x2, y2]
     * @param int       $y1 Top left y-coordinate of crop box
     * @param int       $x2 Bottom right x-coordinate of crop box
     * @param int       $y2 Bottom right y-coordinate of crop box
     * @return self
     * @throws \RuntimeException
     */
    public function crop($x1, int $y1 = 0, int $x2 = 0, int $y2 = 0): self
    {
        if (!isset($this->image) || !$this->isValidImageResource($this->image)) {
            throw new \RuntimeException('No image set');
        }

        if (is_array($x1) && count($x1) === 4) {
            [$x1, $y1, $x2, $y2] = $x1;
        }

        $x1 = max((int)$x1, 0);
        $y1 = max($y1, 0);
        $x2 = min($x2, $this->width);
        $y2 = min($y2, $this->height);

        $cropWidth = $x2 - $x1;
        $cropHeight = $y2 - $y1;

        // Logowanie wymiarów do przycięcia
        error_log("Cropping image: x1: $x1, y1: $y1, x2: $x2, y2: $y2, cropWidth: $cropWidth, cropHeight: $cropHeight");

        if ($cropWidth <= 0 || $cropHeight <= 0) {
            throw new \RuntimeException('Invalid crop dimensions');
        }

        $temp = imagecreatetruecolor($cropWidth, $cropHeight);
        if (!$this->isValidImageResource($temp)) {
            throw new \RuntimeException('Failed to create a new image for cropping');
        }

        // Preserve transparency
        imagealphablending($temp, false);
        imagesavealpha($temp, true);
        $transparent = imagecolorallocatealpha($temp, 255, 255, 255, 127);
        imagefilledrectangle($temp, 0, 0, $cropWidth, $cropHeight, $transparent);

        if (!imagecopy(
            $temp,
            $this->image,
            0,
            0,
            $x1,
            $y1,
            $cropWidth,
            $cropHeight
        )) {
            throw new \RuntimeException('Failed to crop image');
        }

        return $this->_replace($temp);
    }

    /**
     * Replace current image resource with a new one
     *
     * @param resource|\GdImage $res New image resource
     * @return self
     * @throws \UnexpectedValueException
     */
    protected function _replace($res): self
    {
        if (!$this->isValidImageResource($res)) {
            throw new \UnexpectedValueException('Invalid image resource');
        }

        if (isset($this->image) && $this->isValidImageResource($this->image)) {
            imagedestroy($this->image);
        }

        $this->image = $res;
        $this->width = imagesx($res);
        $this->height = imagesy($res);

        error_log("Replaced image dimensions: width: {$this->width}, height: {$this->height}");

        if ($this->width === 0 || $this->height === 0) {
            throw new \UnexpectedValueException("Replaced image has invalid dimensions (width: $this->width, height: $this->height)");
        }

        return $this;
    }

    /**
     * Save current image to file
     *
     * @param string   $fileName Path to save the image
     * @param int|null $type     Image type (IMAGETYPE_*) or null to auto-detect from file extension
     * @return void
     * @throws \RuntimeException
     */
    public function save(string $fileName, ?int $type = null): void
    {
        $dir = dirname($fileName);
        if (!is_dir($dir)) {
            if (!mkdir($dir, 0755, true) && !is_dir($dir)) {
                throw new \RuntimeException('Error creating directory ' . $dir);
            }
        }

        // Auto-detect type from file extension if not provided
        if ($type === null) {
            $extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
            switch ($extension) {
                case 'gif':
                    $type = IMAGETYPE_GIF;
                    break;
                case 'jpeg':
                case 'jpg':
                    $type = IMAGETYPE_JPEG;
                    break;
                case 'png':
                    $type = IMAGETYPE_PNG;
                    break;
                case 'webp':
                    $type = IMAGETYPE_WEBP;
                    break;
                default:
                    $type = IMAGETYPE_JPEG;
            }
        }

        error_log("Saving image to $fileName with type $type");

        try {
            switch ($type) {
                case IMAGETYPE_WEBP:
                    if (!imagewebp($this->image, $fileName)) {
                        throw new \RuntimeException('Failed to save image as WEBP');
                    }
                    break;
                case IMAGETYPE_GIF:
                    if (!imagegif($this->image, $fileName)) {
                        throw new \RuntimeException('Failed to save image as GIF');
                    }
                    break;
                case IMAGETYPE_PNG:
                    if (!imagepng($this->image, $fileName)) {
                        throw new \RuntimeException('Failed to save image as PNG');
                    }
                    break;
                case IMAGETYPE_JPEG:
                default:
                    if (!imagejpeg($this->image, $fileName, 95)) {
                        throw new \RuntimeException('Failed to save image as JPEG');
                    }
            }
            error_log("Image saved successfully to $fileName");
        } catch (\Exception $ex) {
            throw new \RuntimeException('Error saving image file to ' . $fileName . ': ' . $ex->getMessage());
        }
    }

    /**
     * Returns the GD image resource
     *
     * @return resource|\GdImage
     */
    public function getResource()
    {
        return $this->image;
    }

    /**
     * Get current image width
     *
     * @return int
     */
    public function getWidth(): int
    {
        return $this->width;
    }

    /**
     * Get current image height
     *
     * @return int
     */
    public function getHeight(): int
    {
        return $this->height;
    }

    /**
     * Destructor to clean up the image resource
     */
    public function __destruct()
    {
        if (isset($this->image) && $this->isValidImageResource($this->image)) {
            imagedestroy($this->image);
        }
    }

    /**
     * Compatibility helper for PHP 7.4 (resource) and PHP 8+ (GdImage).
     *
     * @param mixed $image
     */
    private function isValidImageResource($image): bool
    {
        if (is_resource($image)) {
            return true;
        }

        return class_exists('GdImage', false) && $image instanceof \GdImage;
    }
}
