<?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\EventListener\Doctrine\Offer;

use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
use Doctrine\ORM\Events;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\Entity\OfferAttachment;
use Ferienpass\CoreBundle\Entity\OfferMedia;
use Ferienpass\CoreBundle\Message\SyncOfferVariants;
use Symfony\Component\Messenger\MessageBusInterface;

/**
 * Adding, removing, or reordering OfferMedia / OfferAttachment rows does
 * not trigger Doctrine's postUpdate on the parent Offer entity — only
 * direct field changes do. {@see VariantListener} therefore never fires
 * for pure junction-table churn, leaving variants with stale media and
 * attachments after the base offer is edited.
 *
 * Listen on the junction entities directly: any change to a base offer's
 * media or attachment collection enqueues a {@see SyncOfferVariants}
 * command so {@see \Ferienpass\CoreBundle\MessageHandler\SyncOfferVariants}
 * can propagate the resulting collection to every variant.
 *
 * Variants themselves never reach the dispatch path: they have
 * isVariantBase() === false, so hasVariants() === false, and the handler
 * propagating to them creates fresh junction rows whose offer is the
 * variant (also non-base) — no infinite loop.
 */
#[AsEntityListener(event: Events::postPersist, entity: OfferMedia::class)]
#[AsEntityListener(event: Events::postUpdate, entity: OfferMedia::class)]
#[AsEntityListener(event: Events::postRemove, entity: OfferMedia::class)]
#[AsEntityListener(event: Events::postPersist, entity: OfferAttachment::class)]
#[AsEntityListener(event: Events::postUpdate, entity: OfferAttachment::class)]
#[AsEntityListener(event: Events::postRemove, entity: OfferAttachment::class)]
class MediaAttachmentVariantSyncListener
{
    /**
     * Dedupe within the same request — a typical media save touches several
     * OfferMedia rows (one per image plus reorders) and we only need to
     * sync the variants once.
     *
     * @var array<string, true>
     */
    private array $scheduled = [];

    public function __construct(private readonly MessageBusInterface $commandBus)
    {
    }

    public function postPersist(OfferMedia|OfferAttachment $entity): void
    {
        $this->maybeScheduleSync($entity->getOffer());
    }

    public function postUpdate(OfferMedia|OfferAttachment $entity): void
    {
        $this->maybeScheduleSync($entity->getOffer());
    }

    public function postRemove(OfferMedia|OfferAttachment $entity): void
    {
        $this->maybeScheduleSync($entity->getOffer());
    }

    private function maybeScheduleSync(OfferInterface $offer): void
    {
        if (!$offer->hasVariants()) {
            return;
        }

        $key = (string) $offer->getUuid();
        if (isset($this->scheduled[$key])) {
            return;
        }

        $this->scheduled[$key] = true;
        $this->commandBus->dispatch(new SyncOfferVariants($key));
    }
}
