<?php

declare(strict_types=1);

namespace App\Service\Contracts;

use App\Dto\Contracts\ContractPhase;
use App\Entity\Employee;
use App\Entity\EmployeeContractPeriod;
use DateTimeImmutable;
use LogicException;

final readonly class EmployeeContractPhaseResolver
{
    private ?DateTimeImmutable $dataStartDate;

    public function __construct(string $dataStartDate = '')
    {
        $trimmed = trim($dataStartDate);
        if ('' === $trimmed) {
            $this->dataStartDate = null;

            return;
        }

        $parsed              = DateTimeImmutable::createFromFormat('!Y-m-d', $trimmed);
        $this->dataStartDate = $parsed instanceof DateTimeImmutable ? $parsed : null;
    }

    /**
     * @return list<ContractPhase>
     */
    public function resolvePhases(Employee $employee): array
    {
        $periods = $employee->getContractPeriods()->toArray();
        usort(
            $periods,
            static fn (EmployeeContractPeriod $a, EmployeeContractPeriod $b): int => $a->getStartDate() <=> $b->getStartDate()
        );

        $today     = new DateTimeImmutable('today');
        $phases    = [];
        $group     = [];
        $signature = null;

        foreach ($periods as $period) {
            $currentSignature = $this->signature($period);
            if (null !== $signature && $currentSignature !== $signature) {
                $phases[] = $this->buildPhase($group, $today);
                $group    = [];
            }
            $group[]   = $period;
            $signature = $currentSignature;
        }

        if ([] !== $group) {
            $phases[] = $this->buildPhase($group, $today);
        }

        // Hide phases entirely before the application's data start date: no usable
        // work-hour or absence data exists before that date, so exposing them would
        // confuse HR (e.g. legacy contract periods predating the software launch).
        if (null !== $this->dataStartDate) {
            $dataStart = $this->dataStartDate;
            $phases    = array_values(array_filter(
                $phases,
                static fn (ContractPhase $phase): bool => null === $phase->endDate || $phase->endDate >= $dataStart,
            ));
        }

        // Most recent first.
        return array_reverse($phases);
    }

    private function signature(EmployeeContractPeriod $period): string
    {
        $contract = $period->getContract();
        $type     = $contract?->getType()->value ?? '';
        $hours    = $contract?->getWeeklyHours() ?? -1;
        $driver   = $period->getIsDriver() ? '1' : '0';

        return sprintf('%s|%d|%s', $type, $hours, $driver);
    }

    /**
     * @param non-empty-list<EmployeeContractPeriod> $group
     */
    private function buildPhase(array $group, DateTimeImmutable $today): ContractPhase
    {
        $first = $group[0];
        $last  = end($group);

        $endDate   = $last->getEndDate();
        $isCurrent = null === $endDate || $endDate >= $today;

        $contract = $first->getContract();

        return new ContractPhase(
            id: (int) $first->getId(),
            contractType: $contract?->getType() ?? throw new LogicException('Phase requires a contract type'),
            weeklyHours: $contract?->getWeeklyHours(),
            isDriver: $first->getIsDriver(),
            startDate: $first->getStartDate(),
            endDate: $endDate,
            periodIds: array_map(static fn (EmployeeContractPeriod $p): int => (int) $p->getId(), $group),
            isCurrent: $isCurrent,
            contractNature: $first->getContractNatureEnum(),
        );
    }
}
