<?php

declare(strict_types=1);

namespace App\Administration\Domain\Model\SchoolBranding;

use function max;
use function min;
use function sprintf;

/**
 * Service Domain qui valide le contraste entre deux couleurs
 * selon les normes WCAG AA.
 *
 * @see https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html
 * @see NFR-A5: Contraste WCAG AA
 */
final class ContrastValidator
{
    private const float WCAG_AA_RATIO = 4.5;
    private const float WCAG_AA_LARGE_RATIO = 3.0;

    public function validate(BrandColor $foreground, BrandColor $background): ContrastResult
    {
        $ratio = $this->calculateRatio($foreground, $background);

        $passesAA = $ratio >= self::WCAG_AA_RATIO;

        return new ContrastResult(
            ratio: $ratio,
            passesAA: $passesAA,
            passesAALarge: $ratio >= self::WCAG_AA_LARGE_RATIO,
            suggestion: !$passesAA
                ? $this->suggestAlternative($foreground, $background)
                : null,
        );
    }

    private function calculateRatio(BrandColor $foreground, BrandColor $background): float
    {
        $l1 = $this->relativeLuminance($foreground);
        $l2 = $this->relativeLuminance($background);

        $lighter = max($l1, $l2);
        $darker = min($l1, $l2);

        return ($lighter + 0.05) / ($darker + 0.05);
    }

    private function relativeLuminance(BrandColor $color): float
    {
        $rgb = $color->toRgb();
        $r = $this->linearize($rgb['r'] / 255.0);
        $g = $this->linearize($rgb['g'] / 255.0);
        $b = $this->linearize($rgb['b'] / 255.0);

        return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
    }

    private function linearize(float $value): float
    {
        return $value <= 0.03928
            ? $value / 12.92
            : (($value + 0.055) / 1.055) ** 2.4;
    }

    /**
     * Propose une couleur alternative plus sombre qui passe le contraste WCAG AA.
     */
    private function suggestAlternative(BrandColor $foreground, BrandColor $background): BrandColor
    {
        $rgb = $foreground->toRgb();
        $r = $rgb['r'];
        $g = $rgb['g'];
        $b = $rgb['b'];

        for ($i = 0; $i < 256; ++$i) {
            $darkerR = max(0, $r - $i);
            $darkerG = max(0, $g - $i);
            $darkerB = max(0, $b - $i);

            $candidate = new BrandColor(sprintf('#%02X%02X%02X', $darkerR, $darkerG, $darkerB));
            $ratio = $this->calculateRatio($candidate, $background);

            if ($ratio >= self::WCAG_AA_RATIO) {
                return $candidate;
            }
        }

        return new BrandColor('#000000');
    }
}
