<?php

namespace Sitecake\Util;

/**
 * GdImage — thin GD wrapper replacing WideImage for image manipulation.
 *
 * Provides the same interface used in ImageService:
 *   loadFromString(), loadFromInput(), create(), getWidth(), getHeight(),
 *   resize(), crop(), asString(), allocateColor(), fill(), merge()
 */
class GdImage
{
    /** @var \GdImage|resource */
    private $gd;

    /** @var string  Original format hint (e.g. 'jpg', 'png', 'webp', 'gif') */
    private string $format;

    private function __construct($gd, string $format = 'jpg')
    {
        $this->gd     = $gd;
        $this->format = strtolower($format);
    }

    // ── Factories ────────────────────────────────────────────────────────────

    /**
     * Load from a binary string.  WideImage::loadFromString() replacement.
     */
    public static function loadFromString(string $data, string $format = ''): self
    {
        $gd = imagecreatefromstring($data);
        if ($gd === false) {
            throw new \RuntimeException('GdImage: could not decode image data');
        }
        imagepalettetotruecolor($gd);
        if ($format === '') {
            $format = self::detectFormat($data);
        }
        return new self($gd, $format);
    }

    /**
     * Load from php://input.  WideImage::load("php://input") replacement.
     */
    public static function loadFromInput(string $format = ''): self
    {
        $data = file_get_contents('php://input');
        if ($data === false || $data === '') {
            throw new \RuntimeException('GdImage: php://input is empty');
        }
        return self::loadFromString($data, $format);
    }

    /**
     * Create a new true-colour canvas.  WideImage::createTrueColorImage() replacement.
     */
    public static function create(int $width, int $height): self
    {
        $gd = imagecreatetruecolor($width, $height);
        if ($gd === false) {
            throw new \RuntimeException('GdImage: imagecreatetruecolor failed');
        }
        return new self($gd, 'jpg');
    }

    // ── Dimensions ───────────────────────────────────────────────────────────

    public function getWidth(): int
    {
        return imagesx($this->gd);
    }

    public function getHeight(): int
    {
        return imagesy($this->gd);
    }

    // ── Transformations ──────────────────────────────────────────────────────

    /**
     * Resize to $targetWidth, maintaining aspect ratio.
     * WideImage->resize($width) replacement.
     */
    public function resize(int $targetWidth): self
    {
        $srcW = $this->getWidth();
        $srcH = $this->getHeight();
        if ($srcW <= 0) {
            return clone $this;
        }
        $targetHeight = (int)round($srcH * $targetWidth / $srcW);
        $dst = imagecreatetruecolor($targetWidth, $targetHeight);
        if ($dst === false) {
            throw new \RuntimeException('GdImage: imagecreatetruecolor failed in resize');
        }
        $this->preserveTransparency($dst);
        imagecopyresampled($dst, $this->gd, 0, 0, 0, 0, $targetWidth, $targetHeight, $srcW, $srcH);
        return new self($dst, $this->format);
    }

    /**
     * Crop by percentage values.
     * WideImage->crop("$left%", "$top%", "$width%", "$height%") replacement.
     *
     * @param string $left   e.g. "10%"
     * @param string $top    e.g. "5%"
     * @param string $width  e.g. "80%"
     * @param string $height e.g. "90%"
     */
    public function crop(string $left, string $top, string $width, string $height): self
    {
        $srcW = $this->getWidth();
        $srcH = $this->getHeight();

        $leftPct   = (float)rtrim($left,   '%') / 100;
        $topPct    = (float)rtrim($top,    '%') / 100;
        $widthPct  = (float)rtrim($width,  '%') / 100;
        $heightPct = (float)rtrim($height, '%') / 100;

        $srcX = (int)round($leftPct  * $srcW);
        $srcY = (int)round($topPct   * $srcH);
        $dstW = (int)round($widthPct * $srcW);
        $dstH = (int)round($heightPct * $srcH);

        // Clamp to image bounds
        $dstW = min($dstW, $srcW - $srcX);
        $dstH = min($dstH, $srcH - $srcY);

        if ($dstW <= 0 || $dstH <= 0) {
            return clone $this;
        }

        $dst = imagecreatetruecolor($dstW, $dstH);
        if ($dst === false) {
            throw new \RuntimeException('GdImage: imagecreatetruecolor failed in crop');
        }
        $this->preserveTransparency($dst);
        imagecopyresampled($dst, $this->gd, 0, 0, $srcX, $srcY, $dstW, $dstH, $dstW, $dstH);
        return new self($dst, $this->format);
    }

    /**
     * Render image to a binary string.
     * WideImage->asString($ext, $quality) replacement.
     */
    public function asString(string $format = '', int $quality = 75): string
    {
        $fmt = strtolower($format ?: $this->format);
        ob_start();
        switch ($fmt) {
            case 'jpg':
            case 'jpeg':
                imagejpeg($this->gd, null, $quality);
                break;
            case 'png':
                // PNG quality 0-9; map JPEG 0-100 → PNG 0-9
                $pngQ = (int)round((100 - $quality) * 9 / 100);
                imagepng($this->gd, null, $pngQ);
                break;
            case 'gif':
                imagegif($this->gd);
                break;
            case 'webp':
                imagewebp($this->gd, null, $quality);
                break;
            default:
                imagejpeg($this->gd, null, $quality);
        }
        return (string)ob_get_clean();
    }

    // ── Canvas helpers (frame operation) ────────────────────────────────────

    /**
     * Allocate a colour in the image.  GD imageallocate() wrapper.
     */
    public function allocateColor(int $r, int $g, int $b): int
    {
        return (int)imagecolorallocate($this->gd, $r, $g, $b);
    }

    /**
     * Flood-fill with $color starting at ($x, $y).  GD imagefill() wrapper.
     */
    public function fill(int $x, int $y, int $color): void
    {
        imagefill($this->gd, $x, $y, $color);
    }

    /**
     * Paste $src onto this image at ($dstX, $dstY) and return the result.
     * WideImage->merge($img, $x, $y) replacement.
     */
    public function merge(self $src, int $dstX, int $dstY): self
    {
        $w = $src->getWidth();
        $h = $src->getHeight();

        // Clone the canvas so we don't mutate $this
        $dst = imagecreatetruecolor($this->getWidth(), $this->getHeight());
        if ($dst === false) {
            throw new \RuntimeException('GdImage: imagecreatetruecolor failed in merge');
        }
        imagecopy($dst, $this->gd, 0, 0, 0, 0, $this->getWidth(), $this->getHeight());
        imagecopy($dst, $src->gd, $dstX, $dstY, 0, 0, $w, $h);
        return new self($dst, $this->format);
    }

    // ── Internal helpers ─────────────────────────────────────────────────────

    private function preserveTransparency($dst): void
    {
        $fmt = $this->format;
        if ($fmt === 'png' || $fmt === 'gif') {
            imagealphablending($dst, false);
            imagesavealpha($dst, true);
            $transparent = imagecolorallocatealpha($dst, 0, 0, 0, 127);
            if ($transparent !== false) {
                imagefill($dst, 0, 0, $transparent);
            }
        }
    }

    private static function detectFormat(string $data): string
    {
        // Read first 12 bytes for magic number detection
        $magic = substr($data, 0, 12);
        if (substr($magic, 0, 4) === "\x89PNG") {
            return 'png';
        }
        if (substr($magic, 0, 6) === 'GIF87a' || substr($magic, 0, 6) === 'GIF89a') {
            return 'gif';
        }
        if (substr($magic, 0, 2) === "\xFF\xD8") {
            return 'jpg';
        }
        if (substr($magic, 8, 4) === 'WEBP') {
            return 'webp';
        }
        return 'jpg';
    }

    public function __clone()
    {
        // Deep clone the GD resource
        $w   = $this->getWidth();
        $h   = $this->getHeight();
        $new = imagecreatetruecolor($w, $h);
        if ($new !== false) {
            imagecopy($new, $this->gd, 0, 0, 0, 0, $w, $h);
            $this->gd = $new;
        }
    }

    public function __destruct()
    {
        if (is_resource($this->gd) || $this->gd instanceof \GdImage) {
            imagedestroy($this->gd);
        }
    }
}
