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

use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\CalendarDoc;
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\Person;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Templating\Entity\PersonRender;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
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;

final readonly class CalendarContext implements CalendarContextInterface
{
    public function __construct(
        private BaseContextData $baseContextData,
        private EntityManagerInterface $entityManager,
        private NormalizerInterface $normalizer,
        private PersonRender $personRender,
        private PersonRepository $personRepository,
        private ThirdPartyRender $thirdPartyRender,
        private ThirdPartyRepository $thirdPartyRepository,
        private TranslatableStringHelperInterface $translatableStringHelper,
    ) {}

    public function adminFormReverseTransform(array $data): array
    {
        return array_merge(
            [
                'trackDatetime' => true,
                'askMainPerson' => true,
                'mainPersonLabel' => 'docgen.calendar.Destinee',
                'askThirdParty' => false,
                'thirdPartyLabel' => 'Third party',
            ],
            $data
        );
    }

    public function adminFormTransform(array $data): array
    {
        return $data;
    }

    public function buildAdminForm(FormBuilderInterface $builder): void
    {
        $builder
            ->add('trackDatetime', CheckboxType::class, [
                'required' => false,
                'label' => 'docgen.calendar.Track changes on datetime and warn user if date time is updated after the doc generation',
            ])
            ->add('askMainPerson', CheckboxType::class, [
                'required' => false,
                'label' => 'docgen.calendar.Ask main person',
            ])
            ->add('mainPersonLabel', TextType::class, [
                'required' => false,
                'label' => 'docgen.calendar.Main person label',
            ])
            ->add('askThirdParty', CheckboxType::class, [
                'required' => false,
                'label' => 'docgen.calendar.Ask third party',
            ])
            ->add('thirdPartyLabel', TextType::class, [
                'required' => false,
                'label' => 'docgen.calendar.Third party label',
            ]);
    }

    public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
    {
        $options = $this->getOptions($template);

        $builder->add('title', TextType::class, [
            'required' => true,
            'label' => 'docgen.calendar.title of the generated document',
            'data' => $this->translatableStringHelper->localize($template->getName()),
        ]);

        if ($options['askMainPerson']) {
            $builder->add('mainPerson', EntityType::class, [
                'class' => Person::class,
                'multiple' => false,
                'label' => $options['mainPersonLabel'] ?? 'docgen.calendar.Main person label',
                'required' => false,
                'choices' => $entity->getPersons(),
                'choice_label' => fn (Person $p) => $this->personRender->renderString($p, []),
                'expanded' => false,
            ]);
        }

        if ($options['askThirdParty']) {
            $builder->add('thirdParty', EntityType::class, [
                'class' => ThirdParty::class,
                'multiple' => false,
                'label' => $options['thirdPartyLabel'] ?? 'Third party',
                'choices' => $entity->getProfessionals(),
                'choice_label' => fn (ThirdParty $tp) => $this->thirdPartyRender->renderString($tp, []),
                'expanded' => false,
            ]);
        }
    }

    public function getData(DocGeneratorTemplate $template, mixed $entity, array $contextGenerationData = []): array
    {
        $options = $this->getOptions($template);

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

        if ($options['askMainPerson']) {
            $data['mainPerson'] = $this->normalizer->normalize($contextGenerationData['mainPerson'] ?? null, '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['askThirdParty']) {
            $data['thirdParty'] = $this->normalizer->normalize($contextGenerationData['thirdParty'] ?? null, 'docgen', [
                'docgen:expects' => ThirdParty::class,
                'groups' => ['docgen:read'],
            ]);
        }

        return $data;
    }

    public function getDescription(): string
    {
        return 'docgen.calendar.A base context for generating document on calendar';
    }

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

    public function getFormData(DocGeneratorTemplate $template, $entity): array
    {
        $options = $this->getOptions($template);
        $data = [];

        if ($options['askMainPerson']) {
            $data['mainPerson'] = null;

            if (1 === \count($entity->getPersons())) {
                $data['mainPerson'] = $entity->getPersons()->first();
            }
        }

        if ($options['askThirdParty']) {
            $data['thirdParty'] = null;

            if (1 === \count($entity->getProfessionals())) {
                $data['thirdParty'] = $entity->getProfessionals()->first();
            }
        }

        return $data;
    }

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

    public function getName(): string
    {
        return 'docgen.calendar.Base context for calendar';
    }

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

    public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
    {
        return true;
    }

    public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
    {
        $normalized = [];
        $normalized['title'] = $data['title'] ?? '';

        foreach (['mainPerson', 'thirdParty'] as $k) {
            if (isset($data[$k])) {
                $normalized[$k] = $data[$k]->getId();
            }
        }

        return $normalized;
    }

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

        if (null !== ($data['mainPerson'] ?? null)) {
            if (null === $person = $this->personRepository->find($data['mainPerson'])) {
                throw new \RuntimeException('person not found');
            }
            $denormalized['mainPerson'] = $person;
        }

        if (null !== ($data['thirdParty'] ?? null)) {
            if (null === $thirdParty = $this->thirdPartyRepository->find($data['thirdParty'])) {
                throw new \RuntimeException('third party not found');
            }
            $denormalized['thirdParty'] = $thirdParty;
        }

        return $denormalized;
    }

    public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
    {
        $options = $this->getOptions($template);
        $storedObject->setTitle($contextGenerationData['title']);
        $doc = new CalendarDoc($entity, $storedObject);
        $doc->setTrackDateTimeVersion($options['trackDatetime']);

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

    private function getOptions(DocGeneratorTemplate $template): array
    {
        return $template->getOptions();
    }
}
