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

use Contao\CoreBundle\Framework\ContaoFramework;
use Contao\PageModel;
use Doctrine\DBAL\Connection;
use Ferienpass\CoreBundle\Entity\Edition;
use Symfony\Cmf\Component\Routing\Event\RouterGenerateEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpFoundation\RequestStack;

#[AsEventListener('cmf_routing.pre_dynamic_generate')]
class CmsUrlGeneratorListener
{
    public function __construct(private readonly Connection $connection, private readonly RequestStack $requestStack, private readonly ContaoFramework $contaoFramework)
    {
    }

    public function __invoke(RouterGenerateEvent $event): void
    {
        if ('cms' !== $event->getRoute()) {
            return;
        }

        $to = $event->getParameters()['to'] ?? '';
        if ('' === $to) {
            throw new \RuntimeException('Please specify the "to" parameter.');
        }

        $event->removeParameter('to');

        $dns = $event->getParameters()['uri'] ?? null;
        $dns ??= $this->requestStack->getMainRequest()?->getHost() ?? '';

        $event->removeParameter('uri');

        $allowedPages = $this->connection->executeQuery(
            <<<'SQL'
                    with recursive cte (id, name, pid) as (
                        select     id,
                                   title,
                                   pid
                        from       tl_page
                        where      type = 'root' and dns=?
                        union all
                        select     p.id,
                                   p.title,
                                   p.pid
                        from       tl_page p
                                       inner join cte
                                                  on p.pid = cte.id
                    ) select * from cte;

                SQL,
            [$dns]
        )->fetchFirstColumn();

        $qb = $this->connection->createQueryBuilder()
            ->select('page.id')
            ->from('tl_page', 'page')
            ->where('type = :type')
            ->orderBy('page.published', 'DESC')
            ->setParameter('type', $to)
        ;

        if ([] !== $allowedPages) {
            $qb->andWhere($qb->expr()->in('page.id', $allowedPages));
        }

        $edition = $event->getParameters()['edition'] ?? null;
        if ($edition instanceof Edition) {
            $qb->andWhere($qb->expr()->or($qb->expr()->eq('page.edition', ':edition'), $qb->expr()->isNull('page.edition')));
            $qb->setParameter('edition', $edition->getId());
            $qb->orderBy('page.edition', 'DESC');
            $event->removeParameter('edition');
        }

        $pageId = $qb
            ->executeQuery()
            ->fetchOne()
        ;

        if (false === $pageId) {
            $this->contaoFramework->initialize();

            $root = PageModel::findOneBy('type', 'root');
            if (null !== $root) {
                $event->setRoute(\sprintf('tl_page.%s.root', $root->id));

                return;
            }

            throw new \RuntimeException(\sprintf('Could not find a page of type "%s"', $to));
        }

        $autoItem = $event->getParameters()['auto_item'] ?? null;
        if (null !== $autoItem) {
            $event->setParameter('parameters', '/'.$autoItem);
            $event->removeParameter('auto_item');
        }

        $event->setRoute('tl_page.'.$pageId);
    }
}
