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

use Contao\StringUtil;
use Doctrine\ORM\QueryBuilder;
use Ferienpass\AdminBundle\Form\Filter\AbstractFilter;
use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\Form\FormFactoryInterface;

class ListCriteriaApplier
{
    public function __construct(
        private readonly FormFactoryInterface $formFactory,
        #[TaggedLocator('ferienpass_admin.filter')]
        private readonly ServiceLocator $filters
    ) {
    }

    public function apply(QueryBuilder $qb, ?string $filterType, array $searchable, ?string $query, ?string $sorting, array $filterData = [], ?string $viewId = null, array $config = []): void
    {
        $filter = $this->getFilter($filterType);

        // Search
        if ($query) {
            $where = $qb->expr()->andX();
            foreach (array_filter(StringUtil::trimsplit(' ', $query)) as $j => $token) {
                $or = $qb->expr()->orX();

                foreach ($searchable as $i => $field) {
                    $qfield = str_contains((string) $field, '.') ? $field : "i.$field";
                    $or->add("$qfield LIKE :query_$i$j");
                    $qb->setParameter("query_$i$j", "%{$token}%");
                }

                $where->add($or);
            }

            if ($where->count()) {
                $qb->andWhere($where);
            }
        }

        // Sorting
        $sortingValue = ($sorting ?: $filter?->getSortable()[0]) ?? '';

        if (isset($config['groupBy']) && '' !== $config['groupBy']) {
            $filter?->applySortingFor('edition', $qb);
        }

        if ('' !== $sortingValue) {
            $filter?->applySortingFor($sortingValue, $qb);
        }

        // Filters
        if ($filter instanceof AbstractFilter) {
            $filterForm = $this->formFactory->create($filter::class, null, ['view_id' => $viewId]);
            $filterForm->submit($filterData);

            $form = $this->formFactory->create($filter::class, $filterForm->getData(), ['view_id' => $viewId]);

            $filter->apply($qb, $form);
        }
    }

    private function getFilter(?string $filterType): ?AbstractFilter
    {
        if (null === $filterType || !$this->filters->has($filterType)) {
            return null;
        }

        $filter = $this->filters->get($filterType);

        return $filter instanceof AbstractFilter ? $filter : null;
    }
}
