<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\ApplicationSystem;

use Doctrine\Common\Collections\Collection;
use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Enum\AttendanceStatus;
use Ferienpass\CoreBundle\Enum\AttendanceTransition;

class FirstComeApplicationSystem extends AbstractApplicationSystem
{
    public static function getType(): string
    {
        return self::FIRSTCOME;
    }

    protected function doGetTransitionName(Attendance $attendance): ?string
    {
        $offer = $attendance->getOffer();
        $currentStatus = $attendance->getStatus();

        // Only assign attendance status for waitlisted or new attendances.
        // All other attendance statuses (confirmed, error, withdrawn,…) are persistent.
        if (AttendanceStatus::WAITING !== $currentStatus && !$attendance->isWaitlisted()) {
            return null;
        }

        $max = $offer->getMaxParticipants();

        // Offers with no participant limit
        if (!$max) {
            return AttendanceTransition::CONFIRM;
        }

        $attendances = $offer->getAttendancesConfirmedOrWaitlisted();
        $position = $this->calculatePosition($attendance, $attendances);

        // Existing participant and spots left
        if (null !== $position && $position < $max) {
            return AttendanceTransition::CONFIRM;
        }

        // New participant and spots left
        if (null === $position && $attendances->count() < $max) {
            return AttendanceTransition::CONFIRM;
        }

        // Already queued up and no spot freed up: leave the record alone. This
        // branch runs on every ReorderParticipantList pass, so it must never
        // retroactively drop someone off a waiting list they are already on —
        // not even when the season meanwhile switched the waiting list off.
        if ($attendance->isWaitlisted()) {
            return null;
        }

        // The offer is full. Whether the applicant may queue up at all is the
        // season's decision, held on the application-system task as maxWaitlist
        // — same semantics the lottery applies in Assignment::drawForOffer():
        // null = unlimited waiting list, 0 = the season works without one, and
        // n = at most n people queued. Without a task (a Ferienpass configured
        // without editions) the historical unlimited waiting list stands.
        $maxWaitlist = $this->getTask()?->getMaxWaitlist();

        if (null !== $maxWaitlist && $offer->getAttendancesWaitlisted()->count() >= $maxWaitlist) {
            return AttendanceTransition::UNFULFILL;
        }

        return AttendanceTransition::WAITLIST;
    }

    /**
     * Calculate the position of a participant in the participant list.
     * Returns NULL for new participants and the integer position (0 = first)
     * for existing participants on the list.
     */
    private function calculatePosition(Attendance $attendance, Collection $collection): ?int
    {
        if ($collection->isEmpty()) {
            return null;
        }

        $i = 0;
        foreach ($collection as $a) {
            if ($a->getParticipant() === $attendance->getParticipant()) {
                return $i;
            }

            ++$i;
        }

        return null;
    }
}
