<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\MainBundle\CRUD\Routing;

use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

class CRUDRoutesLoader extends Loader
{
    private const ALL_INDEX_METHODS = [Request::METHOD_GET, Request::METHOD_HEAD];

    private const ALL_SINGLE_METHODS = [
        Request::METHOD_GET,
        Request::METHOD_POST,
        Request::METHOD_PUT,
        Request::METHOD_DELETE,
    ];

    private bool $isLoaded = false;

    public function __construct(protected array $crudConfig, protected array $apiCrudConfig)
    {
        parent::__construct();
    }

    /**
     * Load routes for CRUD and CRUD Api.
     *
     * @param mixed|null $type
     */
    public function load($resource, $type = null): RouteCollection
    {
        if (true === $this->isLoaded) {
            throw new \RuntimeException('Do not add the "CRUD" loader twice');
        }

        $collection = new RouteCollection();

        foreach ($this->crudConfig as $crudConfig) {
            $collection->addCollection($this->loadCrudConfig($crudConfig));
        }

        foreach ($this->apiCrudConfig as $crudConfig) {
            $collection->addCollection($this->loadApi($crudConfig));
        }

        return $collection;
    }

    /**
     * @param null $type
     */
    public function supports($resource, $type = null): bool
    {
        return 'CRUD' === $type;
    }

    /**
     * Load routes for api single.
     */
    protected function loadApi(array $crudConfig): RouteCollection
    {
        $collection = new RouteCollection();
        $controller = 'csapi_'.$crudConfig['name'].'_controller';

        foreach ($crudConfig['actions'] as $name => $action) {
            // filter only on single actions
            $singleCollection = $action['single_collection'] ?? '_entity' === $name ? 'single' : null;

            if ('collection' === $singleCollection) {
                //                continue;
            }

            $controllerAction = match ($name) {
                '_entity' => 'entityApi',
                '_index' => 'indexApi',
                default => $name.'Api',
            };

            $defaults = [
                '_controller' => $controller.'::'.($action['controller_action'] ?? $controllerAction),
            ];

            // path are rewritten
            // if name === 'default', we rewrite it to nothing :-)
            $localName = \in_array($name, ['_entity', '_index'], true) ? '' : '/'.$name;

            if ('collection' === $action['single_collection'] || '_index' === $name) {
                $localPath = $action['path'] ?? $localName.'.{_format}';
            } else {
                $localPath = $action['path'] ?? '/{id}'.$localName.'.{_format}';
            }
            $path = $crudConfig['base_path'].$localPath;

            $requirements = $action['requirements'] ?? ['{id}' => '\d+'];

            $methods = \array_keys(\array_filter(
                $action['methods'],
                static fn ($value, $key) => $value,
                \ARRAY_FILTER_USE_BOTH
            ));

            if (0 === \count($methods)) {
                throw new \RuntimeException("The api configuration named \"{$crudConfig['name']}\", action \"{$name}\", ".'does not have any allowed methods. You should remove this action from the config or allow, at least, one method');
            }

            if ('_entity' === $name && \in_array(Request::METHOD_POST, $methods, true)) {
                unset($methods[\array_search(Request::METHOD_POST, $methods, true)]);
                $entityPostRoute = $this->createEntityPostRoute(
                    $name,
                    $crudConfig,
                    $action,
                    $controller
                );
                $collection->add(
                    "chill_api_single_{$crudConfig['name']}_{$name}_create",
                    $entityPostRoute
                );
            }

            if (0 === \count($methods)) {
                // the only method was POST,
                // continue to next
                continue;
            }

            $route = new Route($path, $defaults, $requirements);
            $route->setMethods($methods);

            $collection->add('chill_api_single_'.$crudConfig['name'].'_'.$name, $route);
        }

        return $collection;
    }

    /**
     * Load routes for CRUD (without api).
     */
    protected function loadCrudConfig($crudConfig): RouteCollection
    {
        $collection = new RouteCollection();
        $controller = 'cscrud_'.$crudConfig['name'].'_controller';

        foreach ($crudConfig['actions'] as $name => $action) {
            // defaults (controller name)
            $defaults = [
                '_controller' => $controller.':'.($action['controller_action'] ?? $name),
            ];

            if ('index' === $name) {
                $path = '{_locale}'.$crudConfig['base_path'];
                $route = new Route($path, $defaults);
            } elseif ('new' === $name) {
                $path = '{_locale}'.$crudConfig['base_path'].'/'.$name;
                $route = new Route($path, $defaults);
            } else {
                $path = '{_locale}'.$crudConfig['base_path'].($action['path'] ?? '/{id}/'.$name);
                $requirements = $action['requirements'] ?? [
                    '{id}' => '\d+',
                ];
                $route = new Route($path, $defaults, $requirements);
            }

            $collection->add('chill_crud_'.$crudConfig['name'].'_'.$name, $route);
        }

        return $collection;
    }

    private function createEntityPostRoute(string $name, $crudConfig, array $action, $controller): Route
    {
        $localPath = $action['path'].'.{_format}';
        $defaults = [
            '_controller' => $controller.':'.($action['controller_action'] ?? 'entityPost'),
        ];
        $path = $crudConfig['base_path'].$localPath;
        $requirements = $action['requirements'] ?? [];
        $route = new Route($path, $defaults, $requirements);
        $route->setMethods([Request::METHOD_POST]);

        return $route;
    }
}
