<?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\Form\Filter;

use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Service\ServiceMethodsSubscriberTrait;
use Symfony\Contracts\Service\ServiceSubscriberInterface;

abstract class AbstractFilter extends AbstractType implements ServiceSubscriberInterface
{
    use ServiceMethodsSubscriberTrait;

    protected array $filterTypes = [];

    public function getSearchable(): array
    {
        return array_keys($this->getSorting());
    }

    public function applySortingFor(string $field, QueryBuilder $qb): void
    {
        $callable = $this->getSorting()[$field] ?? null;
        if (!\is_callable($callable)) {
            return;
        }

        $callable($qb);
    }

    public function getFilterable(): array
    {
        return array_keys($this->getFilters());
    }

    public function getFilterableGrouped(?string $viewId = null): array
    {
        $grouped = [];
        foreach ($this->getFilters() as $filterName => $filterType) {
            if ($viewId && $filterType instanceof AbstractFilterType && !$filterType->supports($viewId)) {
                continue;
            }
            if ($filterType instanceof AbstractFilterType && !$filterType->shallDisplay($viewId)) {
                continue;
            }
            $section = $filterType instanceof AbstractFilterType ? $filterType->getSection() : null;
            $grouped[$section][] = $filterName;
        }

        return $grouped;
    }

    public function getSortable(): array
    {
        return array_keys($this->getSorting());
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'translation_domain' => 'admin',
            'required' => false,
            'attr' => ['autocomplete' => 'off'],
            'view_id' => null,
        ]);
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $viewId = $options['view_id'];

        foreach ($this->getFilters() as $filterName => $filterType) {
            if ($builder->has($filterName)) {
                continue;
            }

            if ($viewId && $filterType instanceof AbstractFilterType && !$filterType->supports($viewId)) {
                continue;
            }

            $builder->add($filterName, $filterType::class);
        }

        $builder
            ->add('submit', SubmitType::class, [
                'label' => 'Filtern',
            ])
        ;
    }

    public function apply(QueryBuilder $qb, FormInterface $form): void
    {
        $viewId = $form->getConfig()->getOption('view_id');

        foreach ($this->getFilters() as $k => $filter) {
            if (!$filter instanceof AbstractFilterType) {
                continue;
            }

            if ($viewId && !$filter->supports($viewId)) {
                continue;
            }

            if (!$form->has($k)) {
                continue;
            }

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

    /**
     * @return AbstractFilterType[]
     */
    protected function getFilters(): array
    {
        return $this->filterTypes;
    }

    abstract protected function getSorting(): array;
}
