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

use Chill\ActivityBundle\Entity\Activity;
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\StoredObject;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
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\ORM\EntityManagerInterface;
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;

/**
 * @implements DocGeneratorContextWithPublicFormInterface<Activity>
 */
class ActivityContext implements
    DocGeneratorContextWithAdminFormInterface,
    DocGeneratorContextWithPublicFormInterface
{
    public function __construct(
        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
    {
        return $data;
    }

    public function adminFormTransform(array $data): array
    {
        return [
            '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('thirdParty'),
        ];
    }

    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,
            ]);
    }

    /**
     * @param Activity $entity
     */
    public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
    {
        $options = $template->getOptions();
        $persons = $entity->getPersons();

        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, []),
                    'multiple' => false,
                    'required' => false,
                    'expanded' => true,
                    'label' => $options[$key.'Label'],
                    'placeholder' => $this->translator->trans('Any person selected'),
                ]);
            }
        }

        $thirdParties = $entity->getThirdParties();
        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 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 contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
    {
        $normalized = [];

        foreach (['mainPerson', 'person1', 'person2'] as $k) {
            $normalized[$k] = ($data[$k] ?? null)?->getId();
        }

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

        return $normalized;
    }

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

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

        $data['course'] = $this->normalizer->normalize($entity->getAccompanyingPeriod(), 'docgen', ['docgen:expects' => AccompanyingPeriod::class, 'groups' => 'docgen:read']);
        $data['person'] = $this->normalizer->normalize($entity->getPerson(), 'docgen', ['docgen:expects' => Person::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,
                ]);
            }
        }

        if ($options['thirdParty']) {
            $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 activity';
    }

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

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

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

    public function getName(): string
    {
        return 'docgen.Activity 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 storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
    {
        $storedObject->setTitle($this->translatableStringHelper->localize($template->getName()));
        $entity->addDocument($storedObject);

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