<?php

declare(strict_types=1);

namespace App\Service\Leave;

use App\Entity\Employee;
use App\Repository\AbsenceRepository;
use DateTimeImmutable;

use function count;

/**
 * Detects continuous MALADIE (sick leave) periods and computes
 * the date ranges where reduced accrual applies (after the first month grace).
 */
final readonly class LongMaladieService
{
    private const int MAX_GAP_DAYS = 3;

    public function __construct(
        private AbsenceRepository $absenceRepository,
    ) {}

    /**
     * Returns date ranges where the reduced maladie accrual rate applies.
     * For continuous maladie periods > 1 month, the first month is excluded (grace period).
     *
     * @return list<array{start: DateTimeImmutable, end: DateTimeImmutable}>
     */
    public function findReducedRatePeriods(
        Employee $employee,
        DateTimeImmutable $from,
        DateTimeImmutable $to
    ): array {
        // Look back 13 months to catch maladie that started before the exercise period
        $extendedFrom = $from->modify('-13 months');
        $dates        = $this->absenceRepository->findMaladieDatesByEmployee($employee, $extendedFrom, $to);
        if ([] === $dates) {
            return [];
        }

        $periods = $this->consolidateIntoPeriods($dates);

        return $this->applyFirstMonthGrace($periods);
    }

    /**
     * Count calendar days in [monthStart, monthEnd] that fall within reduced maladie periods.
     *
     * @param list<array{start: DateTimeImmutable, end: DateTimeImmutable}> $reducedPeriods
     */
    public function countReducedDaysInMonth(
        DateTimeImmutable $monthStart,
        DateTimeImmutable $monthEnd,
        array $reducedPeriods
    ): int {
        $total = 0;
        foreach ($reducedPeriods as $period) {
            $overlapStart = $period['start'] > $monthStart ? $period['start'] : $monthStart;
            $overlapEnd   = $period['end'] < $monthEnd ? $period['end'] : $monthEnd;

            if ($overlapStart > $overlapEnd) {
                continue;
            }

            $total += ((int) $overlapEnd->diff($overlapStart)->format('%a')) + 1;
        }

        return $total;
    }

    /**
     * @param list<DateTimeImmutable> $dates sorted chronologically
     *
     * @return list<array{start: DateTimeImmutable, end: DateTimeImmutable}>
     */
    private function consolidateIntoPeriods(array $dates): array
    {
        $periods = [];
        $start   = $dates[0];
        $prev    = $start;

        for ($i = 1, $count = count($dates); $i < $count; ++$i) {
            $current = $dates[$i];
            $gap     = (int) $prev->diff($current)->format('%a');
            if ($gap > self::MAX_GAP_DAYS) {
                $periods[] = ['start' => $start, 'end' => $prev];
                $start     = $current;
            }
            $prev = $current;
        }
        $periods[] = ['start' => $start, 'end' => $prev];

        return $periods;
    }

    /**
     * @param list<array{start: DateTimeImmutable, end: DateTimeImmutable}> $periods
     *
     * @return list<array{start: DateTimeImmutable, end: DateTimeImmutable}>
     */
    private function applyFirstMonthGrace(array $periods): array
    {
        $result = [];
        foreach ($periods as $period) {
            $gracedStart = $period['start']->modify('+1 month');
            if ($gracedStart > $period['end']) {
                continue;
            }
            $result[] = ['start' => $gracedStart, 'end' => $period['end']];
        }

        return $result;
    }
}
