<?php


namespace AutoAdmin\Bundle\Admin\Configuration;


use AutoAdmin\Bundle\Admin\DependencyInjection\Configuration\ConfigurationDefault;
use Exception;
use Symfony\Component\PropertyAccess\PropertyAccessor;

class ConfigManipulator
{
    private array $config;
    private PropertyAccessor $accessor;

    public function __construct(array $config)
    {
        $this->config = $config;
        $this->accessor = new PropertyAccessor();
    }

    public function getConfig(): array
    {
        return $this->config;
    }

    public function get($path)
    {
        $path = '[' . str_replace('.', '][', $path) . ']';

        return $this->accessor->getValue($this->config, $path);
    }

    protected function set($path, $value)
    {
        $path = '[' . str_replace('.', '][', $path) . ']';

        $this->accessor->setValue($this->config, $path, $value);
    }

    public function hasController(string $controller): bool
    {
        return isset($this->config['controllers'][$controller]);
    }

    public function addController(string $controller, ?array $controllerConfig = null): void
    {
        if (isset($this->config['controllers'][$controller])) {
            throw new Exception(sprintf('Controller "%s" already exists in configuration.', $controller));
        }

        if ($controllerConfig === null) {
            $controllerConfig = ConfigurationDefault::getDefaultControllerConfig();
        }

        $controllerConfig = array_merge(['class' => $controller], $controllerConfig);

        $this->config['controllers'][$controller] = $controllerConfig;
    }

    public function addControllerAction(string $controller, string $method, array $actionConfig): void
    {
        if (!isset($this->config['controllers'][$controller])) {
            throw new Exception(sprintf('Controller "%s" does not exist in configuration.', $controller));
        }

        if (isset($this->config['controllers'][$controller][$method])) {
            throw new Exception(
                sprintf('Action "%s" already exist in controller "%s" in configuration.', $method, $controller)
            );
        }

        $this->config['controllers'][$controller]['actions'][$method] = $actionConfig;
    }

    public function getControllerProp(string $controllerFqcn, string $path)
    {
        if (!isset($this->config['controllers'][$controllerFqcn])) {
            throw new Exception(sprintf('Controller "%s" does not exist in configuration.', $controllerFqcn));
        }

        return $this->get('controllers.' . $controllerFqcn . '.' . $path);
    }

    public function setControllerProps(string $controllerFqcn, string $path, $props): void
    {
        foreach ($props as $prop => $value) {
            $this->setControllerProp($controllerFqcn, $path . '.' . $prop, $value);
        }
    }

    public function setControllerProp(string $controllerFqcn, string $path, $value)
    {
        if (!isset($this->config['controllers'][$controllerFqcn])) {
            throw new Exception(sprintf('Controller "%s" does not exist in configuration.', $controllerFqcn));
        }

        $this->set('controllers.' . $controllerFqcn . '.' . $path, $value);
    }

    public function getEntities()
    {
        return $this->get('entities');
    }

    public function getEntity(string $entityFqcn)
    {
        return $this->get('entities.' . $entityFqcn);
    }

    public function getEntityProp(string $entityFqcn, string $path)
    {
        if (!isset($this->config['entities'][$entityFqcn])) {
            throw new Exception(sprintf('Entity "%s" does not exist in configuration.', $entityFqcn));
        }

        return $this->get('entities.' . $entityFqcn . '.' . $path);
    }

    public function setEntityProp(string $entityFqcn, string $path, $value)
    {
        if (!isset($this->config['entities'][$entityFqcn])) {
            throw new Exception(sprintf('Entity "%s" does not exist in configuration.', $entityFqcn));
        }

        $this->set('entities.' . $entityFqcn . '.' . $path, $value);
    }

    public function getEntityActions(string $entityFqcn)
    {
        return $this->get('entities.' . $entityFqcn . '.actions');
    }

    public function getEntityAction(string $entityFqcn, string $action)
    {
        return $this->get('entities.' . $entityFqcn . '.actions.' . $action);
    }

    public function getEntityActionProp(string $entityFqcn, string $action, string $path)
    {
        if (!isset($this->config['entities'][$entityFqcn])) {
            throw new Exception(sprintf('Entity "%s" does not exist in configuration.', $entityFqcn));
        }

        return $this->get('entities.' . $entityFqcn . '.actions.' . $action . '.' . $path);
    }

    public function setEntityActionProp(string $entityFqcn, string $action, string $path, $value)
    {
        if (!isset($this->config['entities'][$entityFqcn])) {
            throw new Exception(sprintf('Entity "%s" does not exist in configuration.', $entityFqcn));
        }

        $this->set('entities.' . $entityFqcn . '.actions.' . $action . '.' . $path, $value);
    }

    public function setEntityActionProps(string $entityFqcn, string $action, array $props): void
    {
        foreach ($props as $prop => $value) {
            $this->setEntityActionProp($entityFqcn, $action, $prop, $value);
        }
    }

    public function getMenuProp(string $menuIndex, string $path)
    {
        return $this->get('menu.' . $menuIndex . '.' . $path);
    }

    public function setMenuProp(string $menuIndex, string $path, $value): void
    {
        $this->set('menu.' . $menuIndex . '.' . $path, $value);
    }

    public function setMenuProps(string $menuIndex, array $props): void
    {
        foreach ($props as $prop => $value) {
            $this->setMenuProp($menuIndex, $prop, $value);
        }
    }

    public function hasTab(string $entityFqcn, string $action, string $tab): bool
    {
        return !empty($this->getTab($entityFqcn, $action, $tab));
    }

    public function getTab(string $entityFqcn, string $action, string $tab)
    {
        return $this->get('entities.' . $entityFqcn . '.tabs.' . $action . '.' . $tab);
    }

    public function addTab(
        string $entityFqcn,
        string $action,
        string $tabSlug,
        ?string $icon = null,
        ?array $mergeConfig = null
    ): void {
        $config = [
            'label' => sprintf('tab.%s.label', $tabSlug),
            'icon' => $icon ?: 'fa fa-cog',
            'routeParams' => [
                'tab' => $tabSlug,
            ],
            ...($mergeConfig?:[])
        ];

        $tabs = $this->get('entities.'.$entityFqcn.'.tabs');
        $tabs[$action][$tabSlug] = $config;
        $this->set('entities.'.$entityFqcn.'.tabs', $tabs);
    }

    public function addEntityFormField(
        string $entityFqcn,
        string $action,
        string $property,
        string $label,
        string $type = null,
        array  $type_options = [],
        string $adminTab = 'default'
    ): void
    {
        $fields = $this->get('entities.' . $entityFqcn . '.form.' . $action . '.' . $adminTab);
        $fields[] = [
            'property' => $property,
            'label' => $label,
            'type' => $type,
            'type_options' => $type_options,
            'admin_tab' => $adminTab,
        ];
        $this->set('entities.' . $entityFqcn . '.form.' . $action . '.' . $adminTab, $fields);
    }

    public function setEntityForm(string $entityFqcn, string $action, array $config): void
    {
        $this->set('entities.' . $entityFqcn . '.form.' . $action, $config);
    }

    public function addAsset(string $type, string $url, bool $fromTop = false): void
    {
        $val = $this->get('assets.' . $type);

        if (!is_array($val)) {
            $val = [];
        }

        if ($fromTop) {
            array_unshift($val, $url);
        } else {
            array_push($val, $url);
        }

        $this->set('assets.' . $type, $val);
    }
}
