<?php

/*
 * This source file is available under two different licenses:
 *   - GNU General Public License version 3 (GPLv3)
 *   - DACHCOM Commercial License (DCL)
 * Full copyright and license information is available in
 * LICENSE.md which is distributed with this source code.
 *
 * @copyright  Copyright (c) DACHCOM.DIGITAL AG (https://www.dachcom-digital.com)
 * @license    GPLv3 and DCL
 */

namespace FormBuilderBundle\Manager;

use Doctrine\ORM\EntityManagerInterface;
use FormBuilderBundle\Factory\FormDefinitionFactoryInterface;
use FormBuilderBundle\Model\FormDefinitionInterface;
use FormBuilderBundle\Model\FormFieldContainerDefinitionInterface;
use FormBuilderBundle\Model\FormFieldDefinitionInterface;
use FormBuilderBundle\Repository\FormDefinitionRepositoryInterface;
use Pimcore\Model\User;
use Pimcore\Security\User\TokenStorageUserResolver;

class FormDefinitionManager
{
    public function __construct(
        protected FormDefinitionFactoryInterface $formDefinitionFactory,
        protected FormDefinitionRepositoryInterface $formDefinitionRepository,
        protected TokenStorageUserResolver $storageUserResolver,
        protected EntityManagerInterface $entityManager
    ) {
    }

    public function getById(int $id): ?FormDefinitionInterface
    {
        return $this->formDefinitionRepository->findById($id);
    }

    /**
     * @return array<int, FormDefinitionInterface>
     */
    public function getAll(): array
    {
        return $this->formDefinitionRepository->findAll();
    }

    public function getIdByName(string $name): ?FormDefinitionInterface
    {
        return $this->formDefinitionRepository->findByName($name);
    }

    /**
     * @throws \Exception
     */
    public function save(array $data, ?int $id = null): ?FormDefinitionInterface
    {
        $isUpdate = false;
        if (!is_null($id)) {
            $isUpdate = true;
            $form = $this->getById($id);
        } else {
            $form = $this->formDefinitionFactory->createFormDefinition();
        }

        if (!$form instanceof FormDefinitionInterface) {
            return null;
        }

        $this->updateFormAttributes($data, $form, $isUpdate);
        $this->updateFields($data['form_fields'] ?? [], $form);

        $this->entityManager->persist($form);
        $this->entityManager->flush();

        return $form;
    }

    /**
     * @throws \Exception
     */
    public function saveRawEntity(FormDefinitionInterface $form): void
    {
        $date = new \DateTime();
        $form->setModificationDate($date);

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

    public function delete(int $id): void
    {
        $form = $this->getById($id);

        if (!$form instanceof FormDefinitionInterface) {
            return;
        }

        $this->entityManager->remove($form);
        $this->entityManager->flush();
    }

    /**
     * @throws \Exception
     */
    public function rename(int $id, string $newName): ?FormDefinitionInterface
    {
        $form = $this->getById($id);

        if (!$form instanceof FormDefinitionInterface) {
            return null;
        }

        $form->setName($newName);

        $this->entityManager->persist($form);
        $this->entityManager->flush();

        return $form;
    }

    protected function updateFormAttributes(array $data, FormDefinitionInterface $form, bool $isUpdate = false): void
    {
        $form->setName((string) $data['form_name']);

        if (isset($data['form_group'])) {
            $form->setGroup($data['form_group']);
        }

        $date = new \DateTime();
        if ($isUpdate === false) {
            $form->setCreationDate($date);
            $form->setCreatedBy($this->getAdminUserId());
        }

        $form->setModificationDate($date);
        $form->setModifiedBy($this->getAdminUserId());

        if (isset($data['form_config']) && is_array($data['form_config'])) {
            $form->setConfiguration($data['form_config']);
        }

        if (isset($data['form_conditional_logic']) && is_array($data['form_conditional_logic'])) {
            $form->setConditionalLogic($data['form_conditional_logic']);
        }
    }

    /**
     * @throws \Exception
     */
    public function updateFields(array $data, FormDefinitionInterface $form): void
    {
        $order = 0;
        $fields = [];

        foreach ($this->getValue($data, 'fields', []) as $fieldData) {
            //allow some space for dynamic fields.
            $order += 100;

            $fieldType = $this->getValue($fieldData, 'type');
            $fieldName = $this->getValue($fieldData, 'name');

            if ($fieldType === 'container') {
                $field = $this->generateFormFieldContainer($form, $fieldData, $order);
            } else {
                $field = $this->generateFormField($form, $fieldData, $order);
            }

            $fields[$fieldName] = $field;
        }

        $form->setFields($fields);
    }

    /**
     * @throws \Exception
     */
    protected function generateFormFieldContainer(FormDefinitionInterface $form, array $fieldData, int $order): FormFieldContainerDefinitionInterface
    {
        $fieldType = $this->getValueAsString($fieldData, 'type');
        $fieldSubType = $this->getValueAsString($fieldData, 'sub_type');
        $fieldName = $this->getValueAsString($fieldData, 'name');
        $fieldDisplayName = $this->getValueAsString($fieldData, 'display_name');
        $configParameter = $this->getValue($fieldData, 'configuration');
        $containerFields = $this->getValue($fieldData, 'fields');

        $originalFieldContainer = $form->getFieldContainer($fieldName);

        if (!$originalFieldContainer instanceof FormFieldContainerDefinitionInterface) {
            $fieldContainer = $this->formDefinitionFactory->createFormFieldContainerDefinition();
        } else {
            // @see https://github.com/doctrine/orm/issues/5542
            $fieldContainer = clone $originalFieldContainer;
        }

        $fieldContainer->setName($fieldName);
        $fieldContainer->setDisplayName($fieldDisplayName);
        $fieldContainer->setType($fieldType);
        $fieldContainer->setSubType($fieldSubType);
        $fieldContainer->setOrder($order);

        if (!empty($configParameter) && is_array($configParameter)) {
            $fieldContainer->setConfiguration($configParameter);
        } else {
            $fieldContainer->setConfiguration([]);
        }

        // add sub-fields to container
        if (is_array($containerFields) && count($containerFields) > 0) {
            $parsedContainerFields = [];
            $subOrder = 0;
            foreach ($containerFields as $containerFieldData) {
                //allow some space for dynamic fields.
                $subOrder += 100;
                $parsedContainerFields[] = $this->generateFormField($form, $containerFieldData, $subOrder);
            }
            $fieldContainer->setFields($parsedContainerFields);
        } else {
            $fieldContainer->setFields([]);
        }

        return $fieldContainer;
    }

    protected function generateFormField(FormDefinitionInterface $form, array $fieldData, int $order): FormFieldDefinitionInterface
    {
        $fieldType = $this->getValueAsString($fieldData, 'type');
        $fieldName = $this->getValueAsString($fieldData, 'name');
        $fieldDisplayName = $this->getValueAsString($fieldData, 'display_name');
        $constraints = $this->getValue($fieldData, 'constraints');
        $optionsParameter = $this->getValue($fieldData, 'options');
        $optionalParameter = $this->getValue($fieldData, 'optional');

        $originalField = $form->getField($fieldName);

        if (!$originalField instanceof FormFieldDefinitionInterface) {
            $field = $this->formDefinitionFactory->createFormFieldDefinition();
        } else {
            // @see https://github.com/doctrine/orm/issues/5542
            $field = clone $originalField;
        }

        $field->setName($fieldName);
        $field->setDisplayName($fieldDisplayName);
        $field->setType($fieldType);
        $field->setOrder($order);

        if (!empty($optionsParameter) && is_array($optionsParameter)) {
            $field->setOptions($optionsParameter);
        } else {
            $field->setOptions([]);
        }

        if (!empty($optionalParameter) && is_array($optionalParameter)) {
            $field->setOptional($optionalParameter);
        } else {
            $field->setOptional([]);
        }

        if (!empty($constraints) && is_array($constraints)) {
            $field->setConstraints($constraints);
        } else {
            $field->setConstraints([]);
        }

        return $field;
    }

    protected function getValue(array $data, string $value, mixed $default = null): mixed
    {
        return $data[$value] ?? $default;
    }

    protected function getValueAsString(array $data, string $value, string $default = ''): string
    {
        $value = $this->getValue($data, $value, $default);

        return is_string($value) ? $value : $default;
    }

    protected function getAdminUserId(): int
    {
        $user = $this->storageUserResolver->getUser();

        return $user instanceof User ? (int) $user->getId() : 0;
    }
}
