<?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\Controller\Page;

use Contao\CoreBundle\Exception\PageNotFoundException;
use Ferienpass\CmsBundle\Controller\AbstractController;
use Ferienpass\CmsBundle\Fragment\FragmentReference;
use Ferienpass\CmsBundle\Wishlist\OfferRefResolver;
use Ferienpass\CoreBundle\Entity\Offer\OfferInterface;
use Ferienpass\CoreBundle\HttpCache\CacheTag;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

/**
 * Resolves stateless wishlist share links of the form
 * `/merkliste/<space-separated-offer-refs>`. Each ref is an offer's base58 UUID
 * (the format wishlists store since the UUID migration) or — for share links
 * created before it — a legacy integer offer id; both are resolved here.
 * Wishlist uuid + name arrive as query params (`?liste=<uuid>&liste_name=<name>`)
 * and are imported into the recipient's localStorage by the wishlists Stimulus
 * controller.
 */
#[Route('/merkliste')]
class FavoritesController extends AbstractController
{
    #[Route('/{link}', name: 'cms_favorites_link')]
    public function link(string $link, OfferRefResolver $refResolver): Response
    {
        $this->initializeContaoFramework();

        // Resolve each ref to an offer: base58 UUID (current format) or a
        // legacy integer id (older share links). The fragment still filters by
        // integer id, so we hand it the resolved entities' ids.
        $offerEntities = [];
        foreach (array_filter(explode(' ', $link), static fn (string $t): bool => '' !== $t) as $token) {
            $offer = $refResolver->resolve($token);

            if ($offer instanceof OfferInterface) {
                $offerEntities[] = $offer;
            }
        }

        if ([] === $offerEntities) {
            throw new PageNotFoundException();
        }

        $ids = array_map(static fn (OfferInterface $o) => $o->getId(), $offerEntities);

        // Tag the page composite with each offer in the share-link. The
        // wishlist URL is publicly cacheable (no per-user data on the
        // wrapper), so when any of the listed offers changes — e.g. an
        // admin unpublishes one or edits its title — the cached share
        // page must be purged. The offer_list fragment carries its own
        // tags already; this covers the page composite too.
        if ($offerEntities) {
            $this->tagResponse(array_map(
                static fn (OfferInterface $o) => CacheTag::offer($o),
                $offerEntities,
            ));
        }

        return $this->createPageBuilder()
            // Every share link is a distinct URL over an arbitrary set of offer
            // refs — an unbounded space of pages that are all duplicates of
            // offers indexed under their own canonical URL. Nothing here belongs
            // in an index; `follow` keeps the linked offers reachable.
            ->setMetaRobots('noindex,follow')
            ->addFragment('main', new FragmentReference('ferienpass.fragment.offer_list', ['ids' => $ids]))
            ->getResponse()
        ;
    }
}
