<?php

declare(strict_types=1);

namespace App\Scolarite\Application\Service;

use App\Administration\Domain\Model\SchoolCalendar\SchoolCalendar;
use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Scolarite\Domain\Model\Schedule\ResolvedScheduleSlot;
use App\Scolarite\Domain\Model\Schedule\ScheduleException;
use App\Scolarite\Domain\Repository\ScheduleExceptionRepository;
use App\Scolarite\Domain\Repository\ScheduleSlotRepository;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;

/**
 * Résout l'emploi du temps réel d'une classe pour une semaine donnée.
 *
 * Combine les créneaux récurrents (semaine type), les exceptions ponctuelles,
 * et le calendrier scolaire pour produire la liste des cours effectifs.
 */
final readonly class ScheduleResolver
{
    public function __construct(
        private ScheduleSlotRepository $slotRepository,
        private ScheduleExceptionRepository $exceptionRepository,
    ) {
    }

    /**
     * @return array<ResolvedScheduleSlot>
     */
    public function resolveForWeek(
        ClassId $classId,
        DateTimeImmutable $weekStart,
        TenantId $tenantId,
        SchoolCalendar $calendar,
    ): array {
        $slots = $this->slotRepository->findRecurringByClass($classId, $tenantId);
        $weekEnd = $weekStart->modify('+6 days');

        $exceptionsIndex = $this->indexExceptions(
            $this->exceptionRepository->findForDateRange($tenantId, $weekStart, $weekEnd),
        );

        $resolved = [];

        for ($day = 0; $day < 7; ++$day) {
            $date = $weekStart->modify("+{$day} days");

            if (!$calendar->estJourOuvre($date)) {
                continue;
            }

            foreach ($slots as $slot) {
                if (!$slot->isActiveOnDate($date)) {
                    continue;
                }

                $key = (string) $slot->id . '_' . $date->format('Y-m-d');
                $exception = $exceptionsIndex[$key] ?? null;

                if ($exception?->isCancelled()) {
                    continue;
                }

                $resolved[] = $exception !== null
                    ? ResolvedScheduleSlot::fromSlotWithException($slot, $exception)
                    : ResolvedScheduleSlot::fromSlot($slot, $date);
            }
        }

        return $resolved;
    }

    /**
     * @param array<ScheduleException> $exceptions
     *
     * @return array<string, ScheduleException>
     */
    private function indexExceptions(array $exceptions): array
    {
        $index = [];

        foreach ($exceptions as $exception) {
            $key = (string) $exception->slotId . '_' . $exception->exceptionDate->format('Y-m-d');
            $index[$key] = $exception;
        }

        return $index;
    }
}
