<?php declare(strict_types=1);

namespace App\Engine\Settings\Sections;

/**
 * @template TData
 */
abstract readonly class SettingsSection
{

    public const SETTINGS_STORAGE_LOCATION_PREFIX = "upload/settings/";

    public function __construct(
        protected \Symfony\Contracts\Translation\TranslatorInterface $translator,
        protected \Doctrine\ORM\EntityManagerInterface               $entityManager,
        protected \Symfony\Component\HttpFoundation\RequestStack     $requestStack,
        protected \Symfony\Component\Form\FormFactoryInterface       $formFactory,
        protected \Twig\Environment                                  $twig,
        protected \App\Engine\Admin\Storage\StorageUploader          $storageUploader,
    ) {
    }

    public abstract function getGroup(): \App\Engine\Settings\Groups;

    public abstract function getPriority(): int;

    public function handleForm(): string
    {
        $request = $this->requestStack->getCurrentRequest();
        $data = $this->prepareFormData();
        $form = $this->formFactory->create($this->getFormClassName(), $data);

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $form = $this->processFormData($form, $form->getData());
        }

        return $this->twig->render('form/form.html.twig', ['form' => $form->createView()]);
    }

    /**
     * @return TData
     */
    protected function prepareFormData(): mixed
    {
        return $this->get();
    }

    /**
     * @return TData
     */
    public function get(): mixed
    {
        /** @var \App\Engine\Settings\Settings $entity */
        $entity = $this->entityManager->find(\App\Engine\Settings\Settings::class, $this->getId()) ?? $this->createSettingsEntity();
        return $this->deserialize($entity->getText());
    }

    public abstract function getId(): string;

    private function createSettingsEntity(): \App\Engine\Settings\Settings
    {
        $entity = new \App\Engine\Settings\Settings();
        $entity->setId($this->getId());
        $this->entityManager->initializeObject($entity);

        $className = $this->getObjectClassName();
        $settings = new $className();
        $entity->setText($this->serialize($settings));

        return $entity;
    }

    /**
     * @return class-string<TData>
     */
    protected abstract function getObjectClassName(): string;

    /**
     * @param TData $data
     */
    protected function serialize(mixed $data): string
    {
        return $this->getSerializer()->serialize($data, format: "json", context: [
            \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true
        ]);
    }

    private function getSerializer(): \Symfony\Component\Serializer\Serializer
    {
        $classMetadataFactory = new \Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory(new \Symfony\Component\Serializer\Mapping\Loader\AttributeLoader()); // Použití PHP 8 atributů místo AnnotationReader

        $normalizer = new \Symfony\Component\Serializer\Normalizer\ObjectNormalizer($classMetadataFactory, null, null, null, null, null, [
            \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true,
            \Symfony\Component\Serializer\Normalizer\AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object) {
                return $object->getId(); // Vrátí ID místo serializace celého objektu
            },
        ]);

        return new \Symfony\Component\Serializer\Serializer([$normalizer], [new \Symfony\Component\Serializer\Encoder\JsonEncoder()]);
    }

    /**
     * @return TData
     */
    protected function deserialize(string $data): mixed
    {
        return $this->getSerializer()->deserialize($data, type: $this->getObjectClassName(), format: "json", context: [
            \Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true
        ]);
    }

    /**
     * @return class-string<\Symfony\Component\Form\AbstractType>
     */
    protected abstract function getFormClassName(): string;

    /**
     * @param TData $data
     */
    protected function processFormData(\Symfony\Component\Form\FormInterface $form, mixed $data): \Symfony\Component\Form\FormInterface
    {
        $this->set($data);
        $this->getFlashBag()->add("success", $this->translator->trans("admin.action.settings.successfully-updated"));
        return $form;
    }

    /**
     * @param TData $data
     */
    public function set($data): void
    {
        $entity = $this->entityManager->find(\App\Engine\Settings\Settings::class, $this->getId()) ?? $this->createSettingsEntity();
        $entity->setText($this->serialize($data));

        $this->entityManager->persist($entity);
        $this->entityManager->flush();
    }

    protected function getFlashBag(): \Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface
    {
        /** @var \Symfony\Component\HttpFoundation\Session\FlashBagAwareSessionInterface $session */
        $session = $this->requestStack->getSession();
        return $session->getFlashBag();
    }

    public abstract function getTitle(): string;

}