<?php

declare(strict_types=1);

namespace App\Module\Absence\Domain\Service;

use DateTimeImmutable;
use DateTimeInterface;

/**
 * Provides French métropole public holidays.
 *
 * Dates are computed in pure PHP: fixed-date holidays are hardcoded and
 * Easter-based ones are derived from the Computus (Meeus/Jones/Butcher
 * Gregorian algorithm), so the provider has no runtime dependency and is
 * fully deterministic. Alsace-Moselle / DOM specifics are out of scope.
 */
final class PublicHolidayProvider
{
    /** @var array<int, array<string, string>> cache of holidays per year */
    private array $cache = [];

    /**
     * @return array<string, string> map of 'Y-m-d' => label, sorted by date
     */
    public function getHolidays(int $year): array
    {
        if (isset($this->cache[$year])) {
            return $this->cache[$year];
        }

        $easter       = $this->easterSunday($year);
        $easterMonday = $easter->modify('+1 day');
        $ascension    = $easter->modify('+39 days');
        $whitMonday   = $easter->modify('+50 days');

        $holidays = [
            sprintf('%d-01-01', $year)     => 'Jour de l\'an',
            $easterMonday->format('Y-m-d') => 'Lundi de Pâques',
            sprintf('%d-05-01', $year)     => 'Fête du Travail',
            sprintf('%d-05-08', $year)     => 'Victoire 1945',
            $ascension->format('Y-m-d')    => 'Ascension',
            $whitMonday->format('Y-m-d')   => 'Lundi de Pentecôte',
            sprintf('%d-07-14', $year)     => 'Fête nationale',
            sprintf('%d-08-15', $year)     => 'Assomption',
            sprintf('%d-11-01', $year)     => 'Toussaint',
            sprintf('%d-11-11', $year)     => 'Armistice 1918',
            sprintf('%d-12-25', $year)     => 'Noël',
        ];

        ksort($holidays);

        return $this->cache[$year] = $holidays;
    }

    public function isHoliday(DateTimeInterface $date): bool
    {
        $holidays = $this->getHolidays((int) $date->format('Y'));

        return isset($holidays[$date->format('Y-m-d')]);
    }

    /**
     * Easter Sunday date for the given year (Gregorian Computus).
     */
    private function easterSunday(int $year): DateTimeImmutable
    {
        $a = $year % 19;
        $b = intdiv($year, 100);
        $c = $year % 100;
        $d = intdiv($b, 4);
        $e = $b % 4;
        $f = intdiv($b + 8, 25);
        $g = intdiv($b - $f + 1, 3);
        $h = (19 * $a + $b - $d - $g + 15) % 30;
        $i = intdiv($c, 4);
        $k = $c % 4;
        $l = (32 + 2 * $e + 2 * $i - $h - $k) % 7;
        $m = intdiv($a + 11 * $h + 22 * $l, 451);

        $month = intdiv($h + $l - 7 * $m + 114, 31);
        $day   = (($h + $l - 7 * $m + 114) % 31) + 1;

        return new DateTimeImmutable(sprintf('%04d-%02d-%02d', $year, $month, $day));
    }
}
