<?php

declare(strict_types=1);

namespace App\Administration\Domain\Model\AcademicYear;

use App\Administration\Domain\Exception\InvalidPeriodCountException;
use App\Administration\Domain\Exception\PeriodsCoverageGapException;
use App\Administration\Domain\Exception\PeriodsOverlapException;

use function count;

use DateTimeImmutable;

use function usort;

/**
 * Value Object encapsulant la configuration complète des périodes d'une année scolaire.
 *
 * Invariants :
 * - Le nombre de périodes correspond au PeriodType (3 pour trimestres, 2 pour semestres)
 * - Aucun chevauchement entre périodes
 * - Les périodes sont contiguës (pas de trou)
 */
final readonly class PeriodConfiguration
{
    /** @var AcademicPeriod[] */
    public array $periods;

    /**
     * @param AcademicPeriod[] $periods
     */
    public function __construct(
        public PeriodType $type,
        array $periods,
    ) {
        if (count($periods) !== $this->type->expectedCount()) {
            throw InvalidPeriodCountException::forType(
                $this->type->value,
                $this->type->expectedCount(),
                count($periods),
            );
        }

        $sorted = $periods;
        usort($sorted, static fn (AcademicPeriod $a, AcademicPeriod $b): int => $a->startDate <=> $b->startDate);

        self::validateNoOverlap($sorted);
        self::validateContiguity($sorted);

        $this->periods = $sorted;
    }

    public function currentPeriod(DateTimeImmutable $now): ?AcademicPeriod
    {
        foreach ($this->periods as $period) {
            if ($period->containsDate($now)) {
                return $period;
            }
        }

        return null;
    }

    public function periodBySequence(int $sequence): ?AcademicPeriod
    {
        foreach ($this->periods as $period) {
            if ($period->sequence === $sequence) {
                return $period;
            }
        }

        return null;
    }

    public function startDate(): DateTimeImmutable
    {
        return $this->periods[0]->startDate;
    }

    public function endDate(): DateTimeImmutable
    {
        return $this->periods[count($this->periods) - 1]->endDate;
    }

    /**
     * @param AcademicPeriod[] $periods Sorted by startDate
     */
    private static function validateNoOverlap(array $periods): void
    {
        for ($i = 1; $i < count($periods); ++$i) {
            if ($periods[$i]->startDate <= $periods[$i - 1]->endDate) {
                throw PeriodsOverlapException::between(
                    $periods[$i - 1]->label,
                    $periods[$i]->label,
                );
            }
        }
    }

    /**
     * @param AcademicPeriod[] $periods Sorted by startDate
     */
    private static function validateContiguity(array $periods): void
    {
        for ($i = 1; $i < count($periods); ++$i) {
            $previousEnd = $periods[$i - 1]->endDate;
            $nextStart = $periods[$i]->startDate;

            $dayAfterPreviousEnd = $previousEnd->modify('+1 day');

            if ($dayAfterPreviousEnd->format('Y-m-d') !== $nextStart->format('Y-m-d')) {
                throw PeriodsCoverageGapException::gapBetween(
                    $periods[$i - 1]->label,
                    $periods[$i]->label,
                );
            }
        }
    }
}
