<?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\Notification;

use Ferienpass\CoreBundle\Entity\Attendance;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Enum\AttendanceStatus;
use Ferienpass\CoreBundle\Notifier\Message\EmailMessage;
use Symfony\Component\Notifier\Message\EmailMessage as SymfonyEmailMessage;
use Symfony\Component\Notifier\Notification\EmailNotificationInterface;
use Symfony\Component\Notifier\Recipient\EmailRecipientInterface;
use Symfony\Component\Notifier\Recipient\RecipientInterface;

/**
 * Roster changes since the last digest, sent to the same accounts that receive the
 * participant list ({@see ParticipantListNotification}) and only once that list has
 * gone out — before then the list itself is the news.
 *
 * Deliberately a digest and not one mail per change: a lottery run or a batch of
 * decisions flips dozens of attendances at once, and a channel that floods on
 * those days is a channel nobody reads on the day one withdrawal actually matters.
 */
class AttendanceChangesNotification extends AbstractNotification implements NotificationInterface, EditionAwareNotificationInterface, EmailNotificationInterface
{
    /**
     * Target statuses that give a seat back. Everything else the digest reports
     * (confirmed, waitlisted) takes one.
     */
    public const array FREED_STATUSES = [AttendanceStatus::WITHDRAWN, AttendanceStatus::REJECTED, AttendanceStatus::UNFULFILLED];

    private OfferInterface $offer;

    /** @var list<array{attendance: Attendance, status: string, changedAt: \DateTimeInterface}> */
    private array $changes = [];

    public static function getName(): string
    {
        return 'attendance_changes';
    }

    public function getChannels(RecipientInterface $recipient): array
    {
        return ['email'];
    }

    public function offer(OfferInterface $offer): static
    {
        $this->offer = $offer;

        return $this;
    }

    /**
     * @param list<array{attendance: Attendance, status: string, changedAt: \DateTimeInterface}> $changes
     */
    public function changes(array $changes): static
    {
        $this->changes = $changes;

        return $this;
    }

    public function getContext(): array
    {
        return array_merge(parent::getContext(), [
            'offer' => $this->offer,
            'changes' => $this->changes,
            // Template helper, not an editable token: lets the mail colour a row by
            // whether the seat came back without restating the status list in Twig.
            'freedStatuses' => self::FREED_STATUSES,
            // The counts are what an editable subject line can realistically use —
            // "{{ freed }} Abmeldung(en)" reads well, a loop over $changes does not.
            'freed' => \count(array_filter($this->changes, static fn (array $c): bool => \in_array($c['status'], self::FREED_STATUSES, true))),
            'added' => \count(array_filter($this->changes, static fn (array $c): bool => AttendanceStatus::CONFIRMED === $c['status'])),
        ]);
    }

    public static function getAvailableTokens(): array
    {
        return array_merge(parent::getAvailableTokens(), ['offer', 'changes', 'freed', 'added']);
    }

    public function asEmailMessage(EmailRecipientInterface $recipient, ?string $transport = null): ?SymfonyEmailMessage
    {
        return EmailMessage::fromFerienpassNotification($this, $recipient);
    }
}
