<?php

declare(strict_types=1);

namespace App\Service\WorkHours;

use App\Entity\Absence;
use App\Enum\TrackingMode;
use App\Service\Contracts\EmployeeContractResolver;
use DateMalformedStringException;
use DateTimeImmutable;

final readonly class WorkedHoursCreditPolicy
{
    public function __construct(
        private EmployeeContractResolver $contractResolver,
        private DailyReferenceMinutesResolver $dailyReferenceResolver,
    ) {}

    /**
     * @throws DateMalformedStringException
     */
    public function computeCreditedMinutes(Absence $absence, string $dateYmd, bool $absentMorning, bool $absentAfternoon): int
    {
        $type = $absence->getType();
        // Certaines absences ne doivent jamais générer d'heures créditées.
        if (!$type?->getCountAsWorkedHours()) {
            return 0;
        }

        $employee = $absence->getEmployee();
        if (null === $employee) {
            return 0;
        }
        $workDate = new DateTimeImmutable($dateYmd);
        $contract = $this->contractResolver->resolveForEmployeeAndDate($employee, $workDate);
        // Les contrats suivis en "présence" ne cumulent pas d'heures en minutes.
        if (TrackingMode::TIME->value !== $contract?->getTrackingMode()) {
            return 0;
        }

        $weekday         = (int) $workDate->format('N');
        $workDaysMinutes = $this->contractResolver->resolveWorkDaysMinutesForEmployeeAndDate($employee, $workDate);
        // Quand un planning est configuré sur la période (contrats non-standards),
        // il prime : jour non programmé = 0 crédit, sinon on utilise les minutes prévues.
        $dayMinutes = $this->resolveContractDayMinutes($contract?->getWeeklyHours(), $weekday, $workDaysMinutes);
        if ($dayMinutes <= 0) {
            return 0;
        }

        // Crédit en demi-journées: matin = 0.5, après-midi = 0.5.
        $halfUnits = ($absentMorning ? 1 : 0) + ($absentAfternoon ? 1 : 0);

        return (int) round(($dayMinutes / 2) * $halfUnits);
    }

    /**
     * @throws DateMalformedStringException
     */
    public function computeCreditedPresenceUnits(
        Absence $absence,
        string $dateYmd,
        bool $absentMorning,
        bool $absentAfternoon
    ): float {
        $employee = $absence->getEmployee();
        if (null === $employee) {
            return 0.0;
        }
        $contract = $this->contractResolver->resolveForEmployeeAndDate($employee, new DateTimeImmutable($dateYmd));
        if (TrackingMode::PRESENCE->value !== $contract?->getTrackingMode()) {
            return 0.0;
        }

        // Règle forfait: les absences ne créditent jamais de présence.
        // Seules les checkboxes cochées par l'employé comptent.
        return 0.0;
    }

    /**
     * Single source of truth = {@see DailyReferenceMinutesResolver}. Weekend=0,
     * schedule precedence, 35h/39h fixed rules, fallback = weeklyHours/5.
     *
     * @param null|array<int, int> $workDaysMinutes planning iso-day → minutes (priorité absolue si fourni)
     */
    public function resolveContractDayMinutes(?int $weeklyHours, int $isoWeekDay, ?array $workDaysMinutes = null): int
    {
        return $this->dailyReferenceResolver->resolve($weeklyHours, $isoWeekDay, $workDaysMinutes);
    }
}
