<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\PersonBundle\Export\Filter\PersonFilters;

use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Export\DataTransformerInterface;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\ExportGenerationContext;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Repository\GenderRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

class GenderFilter implements
    ExportElementValidatedInterface,
    FilterInterface,
    DataTransformerInterface
{
    // inject gender repository and find the active genders so that you can pass them to the ChoiceType (ordered by ordering)
    public function __construct(
        private readonly TranslatorInterface $translator,
        private readonly TranslatableStringHelperInterface $translatableStringHelper,
        private readonly GenderRepository $genderRepository,
    ) {}

    public function addRole(): ?string
    {
        return null;
    }

    public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void
    {
        $isIn = $qb->expr()->in('person.gender', ':person_gender');

        $acceptedGenders = $data['accepted_genders_entity'];
        $nullIncluded = in_array(null, $acceptedGenders ?? [], true);

        if (!$nullIncluded) {
            $clause = $isIn;
        } else {
            $clause = $qb->expr()->orX($isIn, $qb->expr()->isNull('person.gender'));
        }

        $qb->andWhere($clause);
        $qb->setParameter('person_gender', array_filter($acceptedGenders ?? [], fn ($gender) => null !== $gender));
    }

    public function applyOn(): string
    {
        return Declarations::PERSON_TYPE;
    }

    public function buildForm(FormBuilderInterface $builder): void
    {
        $genderChoices = $this->genderRepository->findByActiveOrdered();
        $choices = ['None' => null];

        foreach ($genderChoices as $gender) {
            $choices[$this->translatableStringHelper->localize($gender->getLabel())] = $gender->getId();
        }

        $builder->add('accepted_genders_entity', ChoiceType::class, [
            'choices' => $choices,
            'multiple' => true,
            'expanded' => true,
            'placeholder' => 'Select gender',
        ]);
    }

    public function getNormalizationVersion(): int
    {
        return 1;
    }

    public function normalizeFormData(array $formData): array
    {
        if (array_key_exists('accepted_genders', $formData)) {
            $genders = [];
            foreach ($formData['accepted_genders'] as $beforeGender) {
                foreach ($this->genderRepository->findByGenderTranslation(match ($beforeGender) {
                    'both' => 'neutral', default => $beforeGender,
                }) as $gender) {
                    if (null !== $gender) {
                        if ('null' === $beforeGender) {
                            $genders[] = null;
                        }
                        $genders[] = $gender->getId();
                    }
                }
            }

            return ['accepted_genders_entity' => array_unique($genders)];
        }

        return ['accepted_genders_entity' => $formData['accepted_genders_entity']];
    }

    public function denormalizeFormData(array $formData, int $fromVersion): array
    {
        if (array_key_exists('accepted_genders', $formData)) {
            $transformedData = [];
            foreach ($formData['accepted_genders'] as $genderBefore) {
                foreach ($this->genderRepository->findByGenderTranslation(
                    match ($genderBefore) {
                        'both' => 'neutral',
                        default => $genderBefore,
                    }
                ) as $gender) {
                    $transformedData[] = $gender;
                }
            }

            return ['accepted_genders_entity' => $transformedData];
        }

        return ['accepted_genders_entity' => $formData['accepted_genders_entity']];
    }

    public function transformData(?array $before): array
    {
        $transformedData = [];
        $transformedData['accepted_genders_entity'] = $before['accepted_genders_entity'] ?? [];

        if (array_key_exists('accepted_genders', $before)) {
            foreach ($before['accepted_genders'] as $genderBefore) {
                foreach ($this->genderRepository->findByGenderTranslation(
                    match ($genderBefore) {
                        'both' => 'neutral',
                        default => $genderBefore,
                    }
                ) as $gender) {
                    $transformedData['accepted_genders_entity'][] = $gender;
                }
            }
        }

        return $transformedData;
    }

    public function getFormDefaultData(): array
    {
        return [];
    }

    public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array
    {
        $genders = [];

        foreach ($data['accepted_genders_entity'] as $g) {
            if (null === $g) {
                $genders[] = $this->translator->trans('export.filter.person.gender.no_gender');
            } else {
                $g = $this->genderRepository->find($g);
                if (null === $g) {
                    continue;
                }
                $genders[] = $this->translatableStringHelper->localize($g->getLabel());
            }
        }

        return [
            'Filtering by genders: only %genders%',
            ['%genders%' => \implode(', ', $genders)],
        ];
    }

    /**
     * A title which will be used in the label for the form.
     *
     * @return string
     */
    public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface
    {
        return 'Filter by person gender';
    }

    public function validateForm($data, ExecutionContextInterface $context): void
    {
        if (!\is_iterable($data['accepted_genders_entity']) || 0 === \count($data['accepted_genders_entity'])) {
            $context->buildViolation('You should select an option')
                ->addViolation();
        }
    }
}
