<?php

namespace App\Support;

/**
 * Derives a full Tailwind-style 50–950 color palette from a single hex
 * color, so the admin's "Brand Color" setting (one color picker) can drive
 * every `primary-*` shade used throughout the storefront (buttons, links,
 * badges, focus rings, etc.) rather than requiring 11 separate pickers.
 *
 * Output is RGB triplets as space-separated strings ("13 148 136"), not
 * hex or rgb(...) — that's the format tailwind.config.js's
 * `rgb(var(--color-primary-600) / <alpha-value>)` pattern expects when
 * read from a CSS custom property.
 *
 * Approach: convert to HSL, anchor shade 600 to the exact input color
 * (matching this project's existing convention where 600 IS the brand
 * color), and interpolate lightness outward from there — lighter toward
 * white for 50–500, darker toward black for 700–950. Hue and saturation
 * are held constant; this won't perfectly match hand-tuned palettes like
 * Tailwind's own, but it's more than close enough for admin-picked brand
 * colors and never produces a broken/unreadable palette.
 */
class BrandPalette
{
    /** Target lightness (0–100) for every shade except 600, which uses the input's own lightness. */
    private const LIGHTNESS_TARGETS = [
        '50'  => 97.0,
        '100' => 93.0,
        '200' => 86.0,
        '300' => 75.0,
        '400' => 62.0,
        '500' => 50.0,
        '700' => null, // computed relative to the input — see shades()
        '800' => null,
        '900' => null,
        '950' => null,
    ];

    /**
     * @return array<string, string> shade name ("50".."950") => "r g b"
     */
    public static function shades(string $hex): array
    {
        [$h, $s, $l] = self::hexToHsl($hex);

        $result = ['600' => self::hslToRgbTriplet($h, $s, $l)];

        foreach (self::LIGHTNESS_TARGETS as $shade => $targetL) {
            if ($targetL !== null) {
                $result[$shade] = self::hslToRgbTriplet($h, $s, $targetL);
            }
        }

        // Darker shades (700-950) are computed as a fraction of the input's
        // own lightness rather than fixed targets — e.g. a light input
        // color still darkens sensibly instead of clipping straight to
        // near-black at every dark stop.
        $result['700'] = self::hslToRgbTriplet($h, $s, max(0, $l * 0.80));
        $result['800'] = self::hslToRgbTriplet($h, $s, max(0, $l * 0.62));
        $result['900'] = self::hslToRgbTriplet($h, $s, max(0, $l * 0.48));
        $result['950'] = self::hslToRgbTriplet($h, $s, max(0, $l * 0.30));

        ksort($result, SORT_NUMERIC);

        return $result;
    }

    /**
     * @return array{0: float, 1: float, 2: float} [hue 0-360, saturation 0-100, lightness 0-100]
     */
    private static function hexToHsl(string $hex): array
    {
        $hex = ltrim($hex, '#');
        if (strlen($hex) === 3) {
            $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
        }
        if (! preg_match('/^[0-9a-fA-F]{6}$/', $hex)) {
            // Malformed input (e.g. picker cleared mid-edit) — fall back
            // to the project's original teal-600 rather than crashing a
            // page render over a bad color value.
            $hex = '0d9488';
        }

        $r = hexdec(substr($hex, 0, 2)) / 255;
        $g = hexdec(substr($hex, 2, 2)) / 255;
        $b = hexdec(substr($hex, 4, 2)) / 255;

        $max = max($r, $g, $b);
        $min = min($r, $g, $b);
        $l = ($max + $min) / 2;

        if ($max === $min) {
            $h = $s = 0.0;
        } else {
            $d = $max - $min;
            $s = $l > 0.5 ? $d / (2 - $max - $min) : $d / ($max + $min);

            $h = match ($max) {
                $r => ($g - $b) / $d + ($g < $b ? 6 : 0),
                $g => ($b - $r) / $d + 2,
                default => ($r - $g) / $d + 4,
            };
            $h /= 6;
        }

        return [$h * 360, $s * 100, $l * 100];
    }

    private static function hslToRgbTriplet(float $h, float $s, float $l): string
    {
        $h /= 360;
        $s /= 100;
        $l /= 100;

        if ($s === 0.0) {
            $r = $g = $b = $l;
        } else {
            $q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
            $p = 2 * $l - $q;
            $r = self::hueToRgb($p, $q, $h + 1 / 3);
            $g = self::hueToRgb($p, $q, $h);
            $b = self::hueToRgb($p, $q, $h - 1 / 3);
        }

        return implode(' ', [
            (int) round($r * 255),
            (int) round($g * 255),
            (int) round($b * 255),
        ]);
    }

    private static function hueToRgb(float $p, float $q, float $t): float
    {
        if ($t < 0) {
            $t += 1;
        }
        if ($t > 1) {
            $t -= 1;
        }
        if ($t < 1 / 6) {
            return $p + ($q - $p) * 6 * $t;
        }
        if ($t < 1 / 2) {
            return $q;
        }
        if ($t < 2 / 3) {
            return $p + ($q - $p) * (2 / 3 - $t) * 6;
        }

        return $p;
    }
}
