<?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\Service\DocGenerator;

use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Entity\DocumentCategory;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

/**
 * @see AccompanyingPeriodContextTest
 *
 * @template-implements DocGeneratorContextWithPublicFormInterface<AccompanyingPeriod>
 */
class AccompanyingPeriodContext implements
    DocGeneratorContextWithAdminFormInterface,
    DocGeneratorContextWithPublicFormInterface
{
    public function __construct(
        private readonly DocumentCategoryRepository $documentCategoryRepository,
        private readonly NormalizerInterface $normalizer,
        private readonly TranslatableStringHelperInterface $translatableStringHelper,
        private readonly EntityManagerInterface $em,
        private readonly PersonRenderInterface $personRender,
        private readonly PersonRepository $personRepository,
        private readonly TranslatorInterface $translator,
        private readonly BaseContextData $baseContextData,
        private readonly ThirdPartyRender $thirdPartyRender,
        private readonly ThirdPartyRepository $thirdPartyRepository,
    ) {}

    public function adminFormReverseTransform(array $data): array
    {
        if (\array_key_exists('category', $data)) {
            $data['category'] = [
                'idInsideBundle' => $data['category']->getIdInsideBundle(),
                'bundleId' => $data['category']->getBundleId(),
            ];
        }

        return $data;
    }

    public function adminFormTransform(array $data): array
    {
        $r = [
            'mainPerson' => $data['mainPerson'] ?? false,
            'mainPersonLabel' => $data['mainPersonLabel'] ?? $this->translator->trans('docgen.Main person'),
            'person1' => $data['person1'] ?? false,
            'person1Label' => $data['person1Label'] ?? $this->translator->trans('docgen.person 1'),
            'person2' => $data['person2'] ?? false,
            'person2Label' => $data['person2Label'] ?? $this->translator->trans('docgen.person 2'),
            'thirdParty' => $data['thirdParty'] ?? false,
            'thirdPartyLabel' => $data['thirdPartyLabel'] ?? $this->translator->trans('Third party'),
        ];

        if (\array_key_exists('category', $data)) {
            $r['category'] = $this->documentCategoryRepository->find($data['category']);
        }

        return $r;
    }

    public function buildAdminForm(FormBuilderInterface $builder): void
    {
        $builder
            ->add('mainPerson', CheckboxType::class, [
                'required' => false,
                'label' => 'docgen.Ask for main person',
            ])
            ->add('mainPersonLabel', TextType::class, [
                'label' => 'main person label',
                'required' => true,
            ])
            ->add('person1', CheckboxType::class, [
                'required' => false,
                'label' => 'docgen.Ask for person 1',
            ])
            ->add('person1Label', TextType::class, [
                'label' => 'person 1 label',
                'required' => true,
            ])
            ->add('person2', CheckboxType::class, [
                'required' => false,
                'label' => 'docgen.Ask for person 2',
            ])
            ->add('person2Label', TextType::class, [
                'label' => 'person 2 label',
                'required' => true,
            ])
            ->add('thirdParty', CheckboxType::class, [
                'required' => false,
                'label' => 'docgen.Ask for thirdParty',
            ])
            ->add('thirdPartyLabel', TextType::class, [
                'label' => 'docgen.thirdParty label',
                'required' => true,
            ])
            ->add('category', EntityType::class, [
                'placeholder' => 'Choose a document category',
                'class' => DocumentCategory::class,
                'query_builder' => static fn (EntityRepository $er) => $er->createQueryBuilder('c')
                    ->where('c.documentClass = :docClass')
                    ->setParameter('docClass', AccompanyingCourseDocument::class),
                'choice_label' => fn ($entity = null) => $entity ? $this->translatableStringHelper->localize($entity->getName()) : '',
            ]);
    }

    /**
     * @param AccompanyingPeriod $entity
     */
    public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, mixed $entity): void
    {
        $options = $template->getOptions();
        $persons = new ArrayCollection($entity->getCurrentParticipations()->map(static fn (AccompanyingPeriodParticipation $p) => $p->getPerson())->toArray());

        foreach ($entity->getCurrentParticipations() as $p) {
            foreach ($p->getPerson()->getResources() as $r) {
                if (null !== $r->getPerson() && !$persons->contains($r->getPerson())) {
                    $persons->add($r->getPerson());
                }
            }
        }

        if (null !== $entity->getRequestorPerson() && !$persons->contains($entity->getRequestorPerson())) {
            $persons->add($entity->getRequestorPerson());
        }

        foreach ($entity->getResources() as $r) {
            if (null !== $r->getPerson() && !$persons->contains($r->getPerson())) {
                $persons->add($r->getPerson());
            }
        }

        foreach (['mainPerson', 'person1', 'person2'] as $key) {
            if ($options[$key] ?? false) {
                $builder->add($key, EntityType::class, [
                    'class' => Person::class,
                    'choices' => $persons,
                    'choice_label' => fn (Person $p) => $this->personRender->renderString($p, ['addAge' => true]),
                    'multiple' => false,
                    'expanded' => true,
                    'required' => false,
                    'label' => $options[$key.'Label'],
                    'placeholder' => $this->translator->trans('Any person selected'),
                ]);
            }
        }

        $thirdParties = [...array_values(array_filter([$entity->getRequestorThirdParty()])), ...array_values(array_filter(
            array_map(
                fn (Resource $r): ?ThirdParty => $r->getThirdParty(),
                $entity->getResources()->filter(
                    static fn (Resource $r): bool => null !== $r->getThirdParty()
                )->toArray()
            )
        ))];

        if ($options['thirdParty'] ?? false) {
            $builder->add('thirdParty', EntityType::class, [
                'class' => ThirdParty::class,
                'choices' => $thirdParties,
                'choice_label' => fn (ThirdParty $p) => $this->thirdPartyRender->renderString($p, []),
                'multiple' => false,
                'required' => false,
                'expanded' => true,
                'label' => $options['thirdPartyLabel'],
                'placeholder' => $this->translator->trans('Any third party selected'),
            ]);
        }
    }

    public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
    {
        if (!$entity instanceof AccompanyingPeriod) {
            throw new UnexpectedTypeException($entity, AccompanyingPeriod::class);
        }
        $options = $template->getOptions();

        $data = [];
        $data = array_merge($data, $this->baseContextData->getData($contextGenerationData['creator'] ?? null));
        $data['course'] = $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => AccompanyingPeriod::class, 'groups' => 'docgen:read']);

        foreach (['mainPerson', 'person1', 'person2'] as $k) {
            if ($options[$k]) {
                $data[$k] = $this->normalizer->normalize($contextGenerationData[$k], 'docgen', [
                    'docgen:expects' => Person::class,
                    'groups' => 'docgen:read',
                    'docgen:person:with-household' => true,
                    'docgen:person:with-relations' => true,
                    'docgen:person:with-budget' => true,
                ]);
            }
        }

        if ($options['thirdParty'] ?? false) {
            $data['thirdParty'] = $this->normalizer->normalize($contextGenerationData['thirdParty'], 'docgen', [
                'docgen:expects' => ThirdParty::class,
                'groups' => 'docgen:read',
            ]);
        }

        return $data;
    }

    public function getDescription(): string
    {
        return 'docgen.A basic context for accompanying period';
    }

    public function getEntityClass(): string
    {
        return AccompanyingPeriod::class;
    }

    public function getFormData(DocGeneratorTemplate $template, $entity): array
    {
        return [
            'course' => $entity,
        ];
    }

    public static function getKey(): string
    {
        return self::class;
    }

    public function getName(): string
    {
        return 'docgen.Accompanying Period basic';
    }

    public function hasAdminForm(): bool
    {
        return true;
    }

    public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
    {
        $options = $template->getOptions();

        return $options['mainPerson'] || $options['person1'] || $options['person2'] || $options['thirdParty'];
    }

    public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
    {
        $normalized = [];
        foreach (['mainPerson', 'person1', 'person2'] as $k) {
            $normalized[$k] = null !== ($data[$k] ?? null) ? $data[$k]->getId() : null;
        }

        $normalized['thirdParty'] = ($data['thirdParty'] ?? null)?->getId();

        return $normalized;
    }

    public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
    {
        $denormalized = [];

        foreach (['mainPerson', 'person1', 'person2'] as $k) {
            if (null !== ($id = ($data[$k] ?? null))) {
                $denormalized[$k] = $this->personRepository->find($id);
            } else {
                $denormalized[$k] = null;
            }
        }

        if (null !== ($id = ($data['thirdParty'] ?? null))) {
            $denormalized['thirdParty'] = $this->thirdPartyRepository->find($id);
        } else {
            $denormalized['thirdParty'] = null;
        }

        return $denormalized;
    }

    public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
    {
        $doc = new AccompanyingCourseDocument();
        $doc->setTemplate($template)
            ->setTitle($this->translatableStringHelper->localize($template->getName()))
            ->setDate(new \DateTime())
            ->setDescription($this->translatableStringHelper->localize($template->getName()))
            ->setCourse($entity)
            ->setObject($storedObject);

        if (\array_key_exists('category', $template->getOptions())) {
            $doc
                ->setCategory(
                    $this->documentCategoryRepository->find(
                        $template->getOptions()['category']
                    )
                );
        }

        $this->em->persist($doc);
    }
}
