<?php
/*
 * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the LICENSE file in the project root for the full license text.
 */

declare(strict_types=1);

namespace App\Services\Import;

use App\Domain\TaskAssignment;

/**
 * Map an Excel cell fill colour (ARGB hex string, e.g. "FF00B050") to a
 * task-assignment status. Pure: no dependencies, no I/O.
 *
 * Strategy:
 *  - Strip the alpha; treat fully-transparent (alpha=00) as "no fill" → zugewiesen.
 *  - Convert RGB → HSL.
 *  - Bucket by hue + saturation + lightness.
 *
 * Buckets (cf. Phase 20 plan):
 *  - greenish   (H 80..170, S > 0.15) → abgeschlossen
 *  - yellowish  (H 30..80,  S > 0.15) → gestartet (covers Excel orange too)
 *  - reddish    (H 340..360 or 0..20, S > 0.20) → abgebrochen
 *  - everything else (white, light-blue, theme defaults, grey, dark) → zugewiesen
 *
 * Cells with a near-white/desaturated colour are treated as "no status" so
 * the workbook's own background-banding doesn't bleed through as a status
 * tag. The same applies to nulls and to Excel theme colours we can't
 * resolve (the parser passes null in that case).
 */
final class XlsxColorClassifier
{
    /**
     * @param string|null $argb e.g. "FF00B050", "FFFFFF00", or null when no
     *                          user-applied colour exists. Case-insensitive.
     *                          Accepts 6- or 8-character hex (alpha optional).
     */
    public static function classify(?string $argb): string
    {
        if ($argb === null) {
            return TaskAssignment::STATUS_ZUGEWIESEN;
        }
        $hex = strtoupper(trim($argb));
        if ($hex === '') {
            return TaskAssignment::STATUS_ZUGEWIESEN;
        }
        if (str_starts_with($hex, '#')) {
            $hex = substr($hex, 1);
        }
        if (strlen($hex) === 8) {
            $alpha = (int) hexdec(substr($hex, 0, 2));
            if ($alpha === 0) {
                return TaskAssignment::STATUS_ZUGEWIESEN;
            }
            $rgb = substr($hex, 2);
        } elseif (strlen($hex) === 6) {
            $rgb = $hex;
        } else {
            return TaskAssignment::STATUS_ZUGEWIESEN;
        }
        if (!ctype_xdigit($rgb)) {
            return TaskAssignment::STATUS_ZUGEWIESEN;
        }

        $r = (int) hexdec(substr($rgb, 0, 2));
        $g = (int) hexdec(substr($rgb, 2, 2));
        $b = (int) hexdec(substr($rgb, 4, 2));

        [$h, $s, $l] = self::rgbToHsl($r, $g, $b);

        // Near-white / very light: treat as "no status" (covers theme-tint default banding).
        if ($l > 0.96 && $s < 0.20) {
            return TaskAssignment::STATUS_ZUGEWIESEN;
        }
        // Desaturated → grey/black/banner — also no status.
        if ($s < 0.10) {
            return TaskAssignment::STATUS_ZUGEWIESEN;
        }

        // Reddish — both ends of the hue circle.
        if (($h >= 340 || $h < 20) && $s > 0.20) {
            return TaskAssignment::STATUS_ABGEBROCHEN;
        }
        // Yellow / orange: a wider band so Excel orange (FFC000, hue ~38) still
        // counts and pale yellows like FFEB9C (hue ~48) do too.
        if ($h >= 20 && $h < 80) {
            return TaskAssignment::STATUS_GESTARTET;
        }
        // Greens.
        if ($h >= 80 && $h < 170 && $s > 0.15) {
            return TaskAssignment::STATUS_ABGESCHLOSSEN;
        }

        // Blues / teal / purple etc.: zugewiesen (default, transparent).
        return TaskAssignment::STATUS_ZUGEWIESEN;
    }

    /**
     * Convert sRGB (0..255 ints) to HSL (H in [0,360), S/L in [0,1]).
     *
     * @return array{0:float,1:float,2:float} [h, s, l]
     */
    public static function rgbToHsl(int $r, int $g, int $b): array
    {
        $rf = $r / 255.0;
        $gf = $g / 255.0;
        $bf = $b / 255.0;
        $max = max($rf, $gf, $bf);
        $min = min($rf, $gf, $bf);
        $l = ($max + $min) / 2.0;

        if ($max === $min) {
            return [0.0, 0.0, $l];
        }

        $d = $max - $min;
        $s = $l > 0.5 ? $d / (2.0 - $max - $min) : $d / ($max + $min);

        if ($max === $rf) {
            $h = (($gf - $bf) / $d) + ($gf < $bf ? 6.0 : 0.0);
        } elseif ($max === $gf) {
            $h = (($bf - $rf) / $d) + 2.0;
        } else {
            $h = (($rf - $gf) / $d) + 4.0;
        }
        $h *= 60.0;
        return [$h, $s, $l];
    }
}
