<?php


namespace AutoAdmin\Bundle\BrickBuilder\Brick;


use AutoAdmin\Bundle\BrickBuilder\Brick\Config\BrickConfiguration;
use AutoAdmin\Bundle\BrickBuilder\Brick\Handler\BrickHandlerInterface;
use AutoAdmin\Bundle\BrickBuilder\BrickType\BrickCategory;
use AutoAdmin\Bundle\BrickBuilder\Form\AdminBrickForm;
use AutoAdmin\Bundle\BrickBuilder\Model\BrickInterface;
use AutoAdmin\Bundle\BrickBuilder\Model\Setting;
use Exception;
use RuntimeException;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Contracts\Translation\TranslatorInterface;

class BrickHandlerManager implements BrickHandlerManagerInterface
{
    protected array $handlers = [];
    private BrickConfigManager $configManager;
    private TranslatorInterface $translator;
    private FormFactoryInterface $formFactory;
    private Kernel $kernel;

    public function __construct(
        array $handlers,
        BrickConfigManager $configManager,
        TranslatorInterface $translator,
        FormFactoryInterface $formFactory,
        Kernel $kernel
    ) {
        array_map([$this, 'add'], array_keys($handlers), $handlers);
        $this->configManager = $configManager;
        $this->translator = $translator;
        $this->formFactory = $formFactory;
        $this->kernel = $kernel;
    }

    private function add(string $type, BrickHandlerInterface $handler)
    {
        $this->handlers[$type] = $handler;
    }

    public function getHandler(BrickInterface $brick): ?BrickHandlerInterface
    {
        if (!isset($this->handlers[$brick->getType()])) {
            throw new RuntimeException('Cannot find handler for brick "'.$brick->getType().'"');
        }

        return $this->handlers[$brick->getType()];
    }

    public function getHandlers(): array
    {
        return $this->handlers;
    }

    public function buildForm(BrickInterface $brick): ?FormInterface
    {
        $handler = $this->getHandler($brick);

        if (method_exists($handler, 'configure')) {
            $builder = $this->formFactory->createBuilder(
                AdminBrickForm::class,
                $brick,
                [
                    'csrf_protection' => false,
                    'translation_domain' => 'aa_admin',
                ]
            );
            /** @var BrickConfiguration $configuration */
            $configuration = $this->configManager->getHandlerConfiguration($handler::BRICK_TYPE);
            $formSettings = [];

            /** @var Setting $config */
            foreach ($configuration->getSettings() as $name => $config) {
                if (!empty($config->getFormType())) {
                    $formSettings[$name] = $config;
                }
            }

            foreach ($formSettings as $settingName => $settingConfig) {
                $formType = $settingConfig->getFormType();
                $formTypeOptions = $settingConfig->getFormTypeOptions();
                if ($formTypeOptions) {
                    // if "required" is not set, try to add NotBlank constraint, as field is required by default
                    if (
                        !array_key_exists('required', $formTypeOptions)
                        || $formTypeOptions['required'] === true
                    ) {
                        $formTypeOptions['constraints'] = $formTypeOptions['constraints'] ?? [];
                        if (count($formTypeOptions['constraints']) === 0) {
                            $formTypeOptions['constraints'][] = new NotBlank();
                        }
                    }
                }
                $builder->add($settingName, $formType, $formTypeOptions);
            }
//            $builder->add(
//                'submit',
//                SubmitType::class,
//                [
//                    'label' => 'submit',
//                    'translation_domain' => 'aa_admin',
//                ]
//            );
            $builder->add(
                'submit_and_close',
                SubmitType::class,
                [
                    'label' => 'save',
                    'translation_domain' => 'aa_admin',
                ]
            );

            return $builder->getForm();
        }

        return null;
    }

    public function buildSettings(BrickInterface $brick): BrickInterface
    {
        $handler = $this->getHandler($brick);
        $config = $handler->getConfiguration();
        $settings = $brick->getSettings();

        foreach ($config->getOptions() as $option => $val) {
            $settings[$option] = $val;
        }

        foreach ($config->getSettings() as $setting) {
            if (!array_key_exists($setting->getName(), $settings)) {
                $settings[$setting->getName()] = $setting->getDefaultValue();
            }
        }

        foreach (array_diff_key($brick->getSettings(), $settings) as $exoticKey => $value) {
            throw new Exception(sprintf('Unknown setting key "%s" found in brick #%s', $exoticKey, $brick->getId()));
        }

        foreach (array_diff_key($config->getOptions(), $settings) as $exoticKey => $value) {
            throw new Exception(sprintf('Unknown setting key "%s" found in brick #%s', $exoticKey, $brick->getId()));
        }

        $brick->setSettings($settings);

        return $brick;
    }

    public function getHandlerTypes(): array
    {
        return array_keys($this->handlers);
    }

    public function getBrickCategories(bool $excludeEmpty = false): array
    {
        $categories = BrickCategory::getAll();
        $categorized = array_combine($categories, array_fill(0, count($categories), []));
        foreach ($categorized as $cat => $array) {
            $categorized[$cat]['slug'] = $cat;
            $categorized[$cat]['label'] = $this->translator->trans('brick_cat.' . $cat . '.label', [], 'aa_admin');
            $categorized[$cat]['icon'] = $this->translator->trans('brick_cat.' . $cat . '.icon', [], 'aa_admin');
            $categorized[$cat]['types'] = [];
        }

        /** @var BrickHandlerInterface $handler */
        foreach ($this->handlers as $handler) {
            if (constant(get_class($handler) . '::BRICK_TYPE') === 'brick.root') {
                continue;
            }
            $cat = $handler->getConfiguration()->getCategory();
            if (!isset($categorized[$cat])) {
                throw new Exception(
                    sprintf(
                        'Category "%s" does not exist within known brick categories, please use one of: "%s"',
                        $cat,
                        implode(', ', $categories)
                    )
                );
            }

            $categorized[$cat]['types'][] = constant(get_class($handler) . '::BRICK_TYPE');
        }

        if ($excludeEmpty === true) {
            foreach ($categorized as $k => $v) {
                if (empty($v['types'])) {
                    unset($categorized[$k]);
                }
            }
        }

        return $categorized;
    }

    public function getHierarchyConstraints(): array
    {
//        $categories = BrickCategory::getCategories();
        $allowSameChildrenAs = [];
        $constraints = [];
        /** @var BrickHandlerInterface $handler */
        foreach ($this->handlers as $handler) {
            $id = constant(get_class($handler) . '::BRICK_TYPE');
            if ($id === 'brick.root') {
                continue;
            }
            $constraints[$id] = [
                'allowed_in_types' => $handler->getConfiguration()->getAllowedIn(),
            ];

            if ($handler instanceof AllowSameChildrenAsInterface) {
                $allowSameChildrenAs[$id] = $handler->getAllowSameChildrenAs();
            }
        }

        $indexedByParentBrickId = [];
        foreach ($constraints as $id => $config) {
            $allowedIn = $config['allowed_in_types'];
            foreach ($allowedIn as $allowedParentId) {
                if (empty($indexedByParentBrickId[$allowedParentId])
                    || !in_array($allowedParentId, $indexedByParentBrickId[$allowedParentId])) {
                    $indexedByParentBrickId[$allowedParentId][] = $id;
                }
            }
        }

        foreach ($allowSameChildrenAs as $handlerId => $allowSameChildrenAsIds) {
            $brickTypes = [];
            foreach ($allowSameChildrenAsIds as $allowSameChildrenAsId) {
                $relatedAllowedChildren = $indexedByParentBrickId[$allowSameChildrenAsId] ?? [];
                foreach ($relatedAllowedChildren as $relatedAllowedChild) {
                    $brickTypes[] = $relatedAllowedChild;
                }
            }
            $indexedByParentBrickId[$handlerId] = $brickTypes;
        }

        return $indexedByParentBrickId;
    }

    public function getBrickTypes(bool $excludeEmpty = false): array
    {
        $types = $this->getHandlerTypes();
        $config = array_combine($types, array_fill(0, count($types), []));
        foreach ($config as $type => $item) {
            $item['label'] = $this->translator->trans(
                'brick.' . str_replace('brick.', '', $type) . '.label',
                [],
                'aa_admin'
            );

            $iconPath = '/assets/images/brick-type-icons/'.str_replace('brick.', '', $type).'.svg';
            if (file_exists($this->kernel->getProjectDir().'/public/'.$iconPath)) {
                $item['icon'] = $iconPath;
            }

            $config[$type] = $item;
        }

        return $config;
    }
}
