<?php

declare(strict_types=1);

namespace App\Module\Absence\Domain\Service;

use App\Module\Absence\Domain\Enum\HalfDay;
use DateInterval;
use DatePeriod;
use DateTimeImmutable;

/**
 * Computes the number of days deducted for an absence request, following the
 * business rules of the spec (§5.1): weekends and public holidays are skipped,
 * and half-days on the boundaries subtract 0.5 each.
 */
final readonly class AbsenceDayCalculator
{
    public function __construct(
        private PublicHolidayProvider $holidayProvider,
    ) {}

    /**
     * @param bool $workingDaysOnly true => "jours ouvrés" (Mon-Fri),
     *                              false => "jours ouvrables" (Mon-Sat, Sunday excluded)
     */
    public function countWorkingDays(
        DateTimeImmutable $start,
        DateTimeImmutable $end,
        ?HalfDay $startHalfDay = null,
        ?HalfDay $endHalfDay = null,
        bool $workingDaysOnly = true,
    ): float {
        $start = $start->setTime(0, 0);
        $end   = $end->setTime(0, 0);

        if ($end < $start) {
            return 0.0;
        }

        $days   = 0.0;
        $period = new DatePeriod($start, new DateInterval('P1D'), $end->modify('+1 day'));

        foreach ($period as $day) {
            if ($this->isCountedDay($day, $workingDaysOnly)) {
                ++$days;
            }
        }

        if ($days <= 0.0) {
            return 0.0;
        }

        // A half-day only subtracts 0.5 when its boundary day is actually
        // counted (otherwise a half-day posted on a weekend/holiday would
        // wrongly under-count the absence).
        if ($start->getTimestamp() === $end->getTimestamp()) {
            // Single-day request: both boundaries collapse onto the same day,
            // so a half-day must subtract 0.5 once, never twice.
            if ((null !== $startHalfDay || null !== $endHalfDay) && $this->isCountedDay($start, $workingDaysOnly)) {
                $days -= 0.5;
            }
        } else {
            if (null !== $startHalfDay && $this->isCountedDay($start, $workingDaysOnly)) {
                $days -= 0.5;
            }
            if (null !== $endHalfDay && $this->isCountedDay($end, $workingDaysOnly)) {
                $days -= 0.5;
            }
        }

        return max(0.0, $days);
    }

    private function isCountedDay(DateTimeImmutable $day, bool $workingDaysOnly): bool
    {
        $weekday = (int) $day->format('N'); // 1 (Mon) .. 7 (Sun)

        if (7 === $weekday) {
            return false; // Sunday: never counted
        }
        if (6 === $weekday && $workingDaysOnly) {
            return false; // Saturday: only counted for "jours ouvrables"
        }

        return !$this->holidayProvider->isHoliday($day);
    }
}
