<?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\AdminBundle\Components;

use Ferienpass\CoreBundle\Entity\MessengerLog;
use Ferienpass\CoreBundle\Entity\SentEmail;
use Ferienpass\CoreBundle\Entity\SentMessage;

/**
 * A unified entry in the outbox list: either a single transactional message (one recipient)
 * or a Rundmail campaign (one MessengerLog group with many recipients).
 */
final class OutboxItem
{
    /**
     * @param array<SentMessage|OutboxRecipient> $recipients
     * @param int|null                           $total      the campaign's real recipient count, which
     *                                                       exceeds the loaded $recipients when the collapsed
     *                                                       list only pulled a preview
     */
    private function __construct(
        public readonly string $kind,
        public readonly string $messageClass,
        public readonly ?string $subject,
        public readonly \DateTimeInterface $ts,
        public readonly array $recipients,
        public readonly ?MessengerLog $group = null,
        private readonly ?int $total = null,
        private readonly ?string $status = null,
    ) {
    }

    public static function transactional(SentEmail $email, ?string $status = null): self
    {
        return new self(
            'transactional',
            $email->getLogEntry()?->getMessage() ?? '',
            $email->getSubject(),
            $email->getCreatedAt(),
            [$email],
            status: $status,
        );
    }

    /**
     * The row's delivery stage. Prefers the status resolved in bulk for the whole
     * list; falls back to the recipient's own, which lazy-loads its engagements.
     */
    public function deliveryStatus(): string
    {
        return $this->status ?? $this->first()->getDeliveryStatus();
    }

    /**
     * @param array<SentMessage|OutboxRecipient> $recipients
     */
    public static function campaign(MessengerLog $group, array $recipients, ?int $total = null): self
    {
        $first = $recipients[0] ?? null;

        return new self(
            'rundmail',
            $group->getMessage(),
            $first instanceof SentEmail || $first instanceof OutboxRecipient ? $first->getSubject() : null,
            $group->getCreatedAt(),
            $recipients,
            $group,
            $total,
        );
    }

    public function isRundmail(): bool
    {
        return 'rundmail' === $this->kind;
    }

    /**
     * A Rundmail with more than one recipient renders with the group affordances
     * (stacked avatars, expand caret, funnel). A single-recipient mailing renders flat.
     */
    public function isGroup(): bool
    {
        return $this->isRundmail() && $this->count() > 1;
    }

    public function count(): int
    {
        return $this->total ?? \count($this->recipients);
    }

    public function first(): SentMessage|OutboxRecipient
    {
        return $this->recipients[0];
    }

    /**
     * Statuses of the *loaded* recipients. For a campaign shown collapsed in the list
     * that is only the preview, so callers that need them for all recipients — the
     * status filter, the detail funnel — must have loaded the campaign in full.
     *
     * @return string[]
     */
    public function statuses(): array
    {
        return array_map(static fn (SentMessage|OutboxRecipient $m) => $m->getDeliveryStatus(), $this->recipients);
    }

    public function hasStatus(string $status): bool
    {
        return \in_array($status, $this->statuses(), true);
    }

    /**
     * Delivery funnel counts across all recipients, mirroring the mockup's aggregation:
     * an opened/clicked message also counts as delivered; clicked also counts as opened.
     *
     * @return array{total:int, gesendet:int, zugestellt:int, geoeffnet:int, geklickt:int, bounce:int}
     */
    public function aggCounts(): array
    {
        $c = ['total' => $this->count(), 'gesendet' => 0, 'zugestellt' => 0, 'geoeffnet' => 0, 'geklickt' => 0, 'bounce' => 0];

        foreach ($this->statuses() as $status) {
            if ('bounce' === $status) {
                ++$c['bounce'];
            } elseif ('gesendet' === $status) {
                ++$c['gesendet'];
            } else {
                ++$c['zugestellt'];
                if ('geoeffnet' === $status || 'geklickt' === $status) {
                    ++$c['geoeffnet'];
                }
                if ('geklickt' === $status) {
                    ++$c['geklickt'];
                }
            }
        }

        return $c;
    }
}
