<?php


namespace AutoAdmin\Bundle\BrickBuilder\Brick\Config;


use AutoAdmin\Bundle\Admin\Form\Type\Component\CollectionType;
use AutoAdmin\Bundle\Admin\Form\Type\Component\RichEntityType;
use AutoAdmin\Bundle\Admin\Form\Type\Component\RichSelect;
use AutoAdmin\Bundle\Admin\Form\Type\Component\RichText;
use AutoAdmin\Bundle\BrickBuilder\Brick\ConfigSetting\BrickSettingInterface;
use AutoAdmin\Bundle\BrickBuilder\Brick\ConfigSetting\EntityCollectionSetting;
use AutoAdmin\Bundle\BrickBuilder\Brick\ConfigSetting\EntitySetting;
use AutoAdmin\Bundle\BrickBuilder\Brick\ConfigSetting\ScalarCollectionSetting;
use AutoAdmin\Bundle\BrickBuilder\Brick\ConfigSetting\ScalarSetting;
use AutoAdmin\Bundle\BrickBuilder\Brick\Handler\ColumnBrickHandler;
use AutoAdmin\Bundle\BrickBuilder\BrickType\BrickCategory;
use AutoAdmin\Bundle\BrickBuilder\BrickType\BrickNature;
use AutoAdmin\Bundle\BrickBuilder\Exception\InvalidSettingConfigurationException;
use AutoAdmin\Bundle\BrickBuilder\Model\Setting;
use Exception;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\OptionsResolver\OptionsResolver;

class Configurator
{
    protected array $options = [];
    protected array $reservedOptions = [];
    /**
     * @var array<Setting>
     */
    protected array $settings = [];
    /**
     * @var string[]
     */
    protected array $scalarAutoTypes = [];

    public function __construct()
    {
        $this->options = [
            '_template' => null,
            '_allowed_in' => [ColumnBrickHandler::BRICK_TYPE,],
            '_category' => BrickCategory::CATEGORY_MISC,
            '_nature' => BrickNature::NATURE_CONTENT,
            '_has_form' => false,
        ];
        $this->reservedOptions = array_keys($this->options);
        $this->scalarAutoTypes = [
            CheckboxType::class,
            ChoiceType::class,
            EmailType::class,
            IntegerType::class,
            NumberType::class,
            TextareaType::class,
            TextType::class,
            UrlType::class,
            EntityType::class,
            // aa custom types
            RichText::class,
            RichSelect::class,
        ];
    }

    public function setTemplate(?string $template): void
    {
        $this->options['_template'] = $template;
    }

    public function setAllowedIn(array $brickTypes): void
    {
        $this->options['_allowed_in'] = $brickTypes;
    }

    public function setCategory(?string $category): void
    {
        if (!in_array($category, BrickCategory::getAll())) {
            throw new Exception('Brick category does not exist.');
        }

        $this->options['_category'] = $category;
    }

    public function setNature(string $nature): void
    {
        if (!in_array($nature, BrickNature::getAll())) {
            throw new Exception('Brick category does not exist.');
        }

        $this->options['_nature'] = $nature;
    }

    public function setOption(string $name, $value, bool $replace = false): void
    {
        if (in_array($name, $this->reservedOptions)) {
            throw new Exception('Cannot override reserved options without using dedicated setters.');
        }

        if (array_key_exists($name, $this->options) && $replace === false) {
            throw new Exception('Option already exists, use $replace parameter if you want to replace option.');
        }

        $this->options[$name] = $value;
    }

    /**
     * Will be later deprecated for more precise handlers:
     * @see self::addScalar()
     * @see self::addScalarCollection()
     * @see self::addEntity()
     * @see self::addEntityCollection()
     */
    public function addSetting(Setting $setting): void
    {
        if (array_key_exists($name = $setting->getName(), $this->settings)) {
            throw new \RuntimeException(
                'Setting name "'.$name.'" already used.'.
                ' Explicitly call removeSetting() if expected, otherwise rename setting.'
            );
        }

        // if setting is defined but form type is not,
        // we implicitly set scalar if it is an "auto" scalar form type

        if($setting->getFormType() == EntityType::class && $setting->getType() === null) {
            $setting->setType(EntitySetting::class);
        }

        if (
            $setting->getFormType() !== null
            && $setting->getType() === null
        ) {
            if (!in_array($setting->getFormType(), $this->scalarAutoTypes)) {
                $errorMessage = sprintf('You must precise setting type for brick setting "%s"', $setting->getName());
                throw new InvalidSettingConfigurationException($errorMessage);
            }
            $setting->setType(ScalarSetting::class);
        } elseif (
            $setting->getFormType() === null
            && $setting->getType() === null
        ) {
            // default behavior: scalar + text
            $setting->setType(ScalarSetting::class);
            $setting->setFormType(TextType::class);
        }

        if (!$setting->getFormType() && $setting->getType()) {
            $typeObj = $setting->getType();
            /** @var BrickSettingInterface $type */
            $type = new $typeObj();
            $setting->setFormType($type->getDefaultFormType());
        }

        if (empty($setting->getFormTypeOptions()) && $setting->getType()) {
            $typeObj = $setting->getType();
            /** @var BrickSettingInterface $type */
            $type = new $typeObj();
            $setting->setFormTypeOptions(
                array_merge($setting->getFormTypeOptions(), $type->getDefaultFormTypeOptions())
            );
        }

        // set property path if not already set
        $formOptions = $setting->getFormTypeOptions();
        if (empty($formOptions['property_path'])) {
            $setting->setFormTypeOptions(
                array_merge(['property_path' => 'settings['.$setting->getName().']',], $formOptions)
            );
        }

        $this->settings[$setting->getName()] = $setting;
    }

    public function addScalar(
        string $settingName,
        string $label,
        string $formType = TextType::class,
        array $config = []
    ): self {
        $this->addSetting(
            Setting::new($settingName)
                ->setType(ScalarSetting::class)
                ->setFormType($formType)
                ->setFormTypeOptions(
                    [
                        ...[
                            'label' => $label,
                            'required' => $config['required'] ?? false,
                        ],
                        ...$config,
                    ]
                )
        );

        return $this;
    }

    public function addScalarCollection(
        string $settingName,
        string $label,
        string $formType = TextType::class,
        array $config = []
    ): self {
        $this->addSetting(
            Setting::new($settingName)
                ->setType(ScalarCollectionSetting::class)
                ->setFormType(CollectionType::class)
                ->setFormTypeOptions([
                    ...[
                        'required' => false,
                        'label' => $label,
                        'entry_type' => $formType,
                        'entry_options' => [
                            ...[
                                'label' => false,
                                'required' => false,
                            ],
                            ...$config['entity_options'] ?? [],
                        ],
                    ],
                    ...$config,
                ])
        );

        return $this;
    }

    public function addEntity(
        string $settingName,
        string $label,
        string $entityClass,
        array $config = []
    ): self {
        $this->addSetting(
            Setting::new($settingName)
                ->setType(EntitySetting::class)
                ->setFormType(RichEntityType::class)
                ->setFormTypeOptions([
                    ...[
                        'required' => false,
                        'class' => $entityClass,
                        'label' => $label,
                        'disable_js_sorting' => true,
                    ],
                    ...$config,
                ])
        );

        return $this;
    }

    public function addEntityCollection(
        string $settingName,
        string $label,
        string $entityClass,
        array $config = []
    ): self {
        $this->addSetting(
            Setting::new($settingName)
                ->setType(EntityCollectionSetting::class)
                ->setFormType(CollectionType::class)
                ->setFormTypeOptions([
                    ...[
                        'required' => false,
                        'entry_type' => RichEntityType::class,
                        'entry_options' => [
                            ...[
                                'class' => $entityClass,
                                'label' => $label,
                                'required' => false,
                                'disable_js_sorting' => true,
                            ],
                            ...$config['entity_options'] ?? [],
                        ],
                    ],
                    ...$config,
                ])
        );

        return $this;
    }

    public function removeSetting(string $name): void
    {
        unset($this->settings[$name]);
    }

    public function compile(): array
    {
        $hasForm = false;
        /** @var Setting $setting */
        foreach ($this->settings as $setting) {
            if ($setting->getFormType()) {
                $hasForm = true;
                break;

            }
        }

        return [
            'options' => array_replace_recursive(
                $this->options,
                [
                    '_has_form' => $hasForm,
                ]
            ),
            'settings' => $this->settings,
        ];
    }

    protected function getResolver(): OptionsResolver
    {
        $resolver = new OptionsResolver();
        $resolver->setDefaults(
            [
                'type' => 'text',
                'default_value' => null,
                'form_type' => null,
                'form_type_options' => [],
            ]
        );

        return $resolver;
    }
}
