<?php

namespace App\Support;

class PaletteExtractor
{
    /**
     * Extract a usable brand palette from an image file.
     * Returns ['primary' => '#rrggbb', 'accent' => '#rrggbb', 'tint' => '#rrggbb']
     * or null if extraction fails (e.g. GD missing, unreadable file).
     */
    public static function extract(string $path): ?array
    {
        if (!extension_loaded('gd') || !is_readable($path)) {
            return null;
        }

        $info = @getimagesize($path);
        if (!$info) {
            return null;
        }

        $img = match ($info[2]) {
            IMAGETYPE_JPEG => @imagecreatefromjpeg($path),
            IMAGETYPE_PNG  => @imagecreatefrompng($path),
            IMAGETYPE_GIF  => @imagecreatefromgif($path),
            IMAGETYPE_WEBP => function_exists('imagecreatefromwebp') ? @imagecreatefromwebp($path) : null,
            default        => null,
        };
        if (!$img) {
            return null;
        }

        $w = imagesx($img);
        $h = imagesy($img);
        $stepX = max(1, (int) floor($w / 60));
        $stepY = max(1, (int) floor($h / 60));

        $buckets = [];
        for ($y = 0; $y < $h; $y += $stepY) {
            for ($x = 0; $x < $w; $x += $stepX) {
                $rgba = imagecolorat($img, $x, $y);
                $a = ($rgba >> 24) & 0x7F;
                if ($a > 90) {
                    continue; // mostly transparent
                }
                $r = ($rgba >> 16) & 0xFF;
                $g = ($rgba >> 8) & 0xFF;
                $b = $rgba & 0xFF;

                $sum = $r + $g + $b;
                if ($sum < 60 || $sum > 720) {
                    continue; // too dark or too light
                }

                $max = max($r, $g, $b);
                $min = min($r, $g, $b);
                if (($max - $min) < 25) {
                    continue; // too gray
                }

                // quantize to 32-step buckets
                $key = (($r >> 5) << 10) | (($g >> 5) << 5) | ($b >> 5);
                if (!isset($buckets[$key])) {
                    $buckets[$key] = ['r' => 0, 'g' => 0, 'b' => 0, 'n' => 0];
                }
                $buckets[$key]['r'] += $r;
                $buckets[$key]['g'] += $g;
                $buckets[$key]['b'] += $b;
                $buckets[$key]['n']++;
            }
        }
        imagedestroy($img);

        if (empty($buckets)) {
            return null;
        }

        uasort($buckets, fn ($a, $b) => $b['n'] <=> $a['n']);

        $candidates = [];
        foreach ($buckets as $bucket) {
            $candidates[] = [
                'r' => (int) round($bucket['r'] / $bucket['n']),
                'g' => (int) round($bucket['g'] / $bucket['n']),
                'b' => (int) round($bucket['b'] / $bucket['n']),
                'n' => $bucket['n'],
            ];
            if (count($candidates) >= 8) {
                break;
            }
        }

        $primary = $candidates[0];
        $accent = null;
        foreach (array_slice($candidates, 1) as $c) {
            $dr = $c['r'] - $primary['r'];
            $dg = $c['g'] - $primary['g'];
            $db = $c['b'] - $primary['b'];
            if (sqrt($dr * $dr + $dg * $dg + $db * $db) > 60) {
                $accent = $c;
                break;
            }
        }
        if (!$accent) {
            $accent = self::shift($primary, 30);
        }

        $tint = self::mixWithWhite($primary, 0.92);

        return [
            'primary' => self::toHex($primary),
            'accent'  => self::toHex($accent),
            'tint'    => self::toHex($tint),
        ];
    }

    private static function toHex(array $c): string
    {
        return sprintf('#%02x%02x%02x',
            max(0, min(255, $c['r'])),
            max(0, min(255, $c['g'])),
            max(0, min(255, $c['b']))
        );
    }

    private static function shift(array $c, int $delta): array
    {
        return [
            'r' => ($c['r'] + $delta) % 256,
            'g' => ($c['g'] + $delta * 2) % 256,
            'b' => ($c['b'] + $delta * 3) % 256,
        ];
    }

    private static function mixWithWhite(array $c, float $whiteRatio): array
    {
        $w = max(0.0, min(1.0, $whiteRatio));
        return [
            'r' => (int) round($c['r'] * (1 - $w) + 255 * $w),
            'g' => (int) round($c['g'] * (1 - $w) + 255 * $w),
            'b' => (int) round($c['b'] * (1 - $w) + 255 * $w),
        ];
    }
}
