<?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\CmsBundle\Filter\Type\Offer;

use Doctrine\ORM\QueryBuilder;
use Ferienpass\CoreBundle\Enum\AttendanceStatus;
use Ferienpass\CoreBundle\Enum\OfferStatus;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Translation\TranslatableMessage;
use Symfony\Contracts\Translation\TranslatableInterface;

class FreeSeatsType extends AbstractOfferFilterType
{
    public static function getName(): string
    {
        return 'sofortbuchung';
    }

    public function getParent(): string
    {
        return CheckboxType::class;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        parent::configureOptions($resolver);

        $resolver->setDefaults([
            'label' => 'nur freie Plätze (Sofortbuchung)',
            'false_values' => ['', null],
        ]);
    }

    /**
     * "Sofort buchbar" promises a booking that goes through right now, so every
     * reason it would not must be filtered out here — see
     * {@see \Ferienpass\CoreBundle\Entity\Offer\Base::isInstantlyBookable()},
     * whose conditions this mirrors in DQL for the offer card's chip.
     */
    public function apply(QueryBuilder $qb, FormInterface $form): void
    {
        if ($form->isEmpty()) {
            return;
        }

        $cancelled = OfferStatus::CANCELLED;

        $qb
            ->andWhere($qb->expr()->orX('o.requiresApplication = 0', $qb->expr()->andX('o.requiresApplication = 1', 'o.onlineApplication = 1')))
            ->andWhere($qb->expr()->orX('o.applicationDeadline IS NULL', 'o.applicationDeadline >= CURRENT_TIMESTAMP()'))
            // A cancelled offer stays published (the card draws a ribbon over
            // it), so it reaches this filter and must be dropped explicitly.
            ->andWhere("JSON_CONTAINS_PATH(o.status, 'one', '$.$cancelled') = 0")
            ->leftJoin('o.attendances', 'a')
            // Confirmed *and* waitlisted, matching FirstComeApplicationSystem:
            // spots claimed by the waiting list are gone, and counting only the
            // confirmed advertised sold-out offers as instantly bookable.
            ->addSelect('sum(CASE WHEN a.status IN (:occupying_statuses) THEN 1 ELSE 0 END) AS HIDDEN countOccupied')
            ->setParameter('occupying_statuses', [AttendanceStatus::CONFIRMED, AttendanceStatus::WAITLISTED])
            ->andHaving($qb->expr()->orX(
                // A limit of 0 means "no limit" — reading it as a real cap hid
                // every unlimited offer that spells it 0 instead of NULL.
                'o.maxParticipants IS NULL',
                'o.maxParticipants = 0',
                'o.maxParticipants > countOccupied'
            ))
        ;
    }

    public function getHumanReadableValue(FormInterface $form): string|TranslatableInterface|null
    {
        return new TranslatableMessage('offerList.filter.free_seats');
    }
}
