<?php

namespace apexl\Io\modules\user\classes;

use apexl\Config\Exceptions\MissingConfigException;
use apexl\Io\enums\HttpMethod;
use apexl\Io\exceptions\RouteNotFoundException;
use apexl\Io\includes\Entity;
use apexl\Io\includes\RouteManager;
use apexl\Io\modules\display\Controllers\DisplayController;
use apexl\Io\modules\user\enums\permissions\Common;
use apexl\Io\modules\user\interfaces\PermissionInterface;
use apexl\Io\recommendedIncludes\displayModule;

class userManagedDisplayModule extends displayModule
{
    /**
     * Method to add a route, allows the module to have a base path configured and for easy overrides.
     * This Method adds one extra option over the base, to permissions protect a route.
     * @throws RouteNotFoundException
     */
    protected function addProtectedDisplayRoute(
        RouteManager $routeManager,
        HttpMethod $verb,
        string $name,
        string $pattern,
        $callable,
        PermissionInterface $permission = Common::ALLOW_ALL,
    ): static {
        $this->addRoute(
            $routeManager,
            $verb,
            $name,
            'display/' . ltrim($pattern, '/'),
            $callable
        );
        $routeManager->addRouteArg($name, 'permission', serialize($permission));

        return $this;
    }

    /**
     * Method to add a route, allows the module to have a base path configured and for easy overrides.
     * This Method adds one extra option over the base, to permissions protect a route.
     * @throws RouteNotFoundException
     */
    protected function addProtectedDataRoute(
        RouteManager $routeManager,
        HttpMethod $verb,
        string $name,
        string $pattern,
        $callable,
        PermissionInterface $permission = Common::ALLOW_ALL,
    ): static {
        $this->addRoute(
            $routeManager,
            $verb,
            $name,
            'display/data/' . ltrim($pattern, '/'),
            $callable
        );
        $routeManager->addRouteArg($name, 'permission', serialize($permission));

        return $this;
    }

    /**
     * Method to add a route, allows the module to have a base path configured and for easy overrides.
     * This Method adds one extra option over the base, to permissions protect a route.
     * @throws RouteNotFoundException
     */
    protected function addProtectedFormRoute(
        RouteManager $routeManager,
        string $name,
        string $pattern,
        $callable,
        PermissionInterface $permission = Common::ALLOW_ALL,
    ): static {
        $this->addDisplayRoute(
            $routeManager,
            HttpMethod::GET,
            $name,
            'forms/' . $pattern,
            $callable
        );
        $routeManager->addRouteArg($name, 'permission', serialize($permission));

        return $this;
    }

    /**
     * Method to add a route, allows the module to have a base path configured and for easy overrides.
     * This Method adds one extra option over the base, to permissions protect a route.
     * @throws RouteNotFoundException
     * @throws RouteNotFoundException
     */
    protected function addProtectedEntityFormRoute(
        RouteManager $routeManager,
        string $entityClass,
        string $pattern,
        $callable,
        PermissionInterface $permission = Common::ALLOW_ALL,
    ): static {
        $this->addDisplayRoute(
            $routeManager,
            HttpMethod::GET,
            $entityClass::entityName() . '.entity.form.createUpdate',
            'forms/entities/' . $this->getName() . '/' . $pattern,
            $callable
        );
        $routeManager->addRouteArg(
            $entityClass::entityName() . '.entity.form.createUpdate',
            'permission',
            serialize($permission)
        );
        $routeManager->addRouteArg(
            $entityClass::entityName() . '.entity.form.createUpdate',
            'entity',
            $entityClass
        );

        return $this;
    }

    /**
     * Method to add a route, allows the module to have a base path configured and for easy overrides.
     * This Method adds one extra option over the base, to permissions protect a route.
     * @throws MissingConfigException
     * @throws RouteNotFoundException
     */
    protected function addProtectedRootRoute(
        RouteManager $routeManager,
        HttpMethod $verb,
        string $name,
        string $pattern,
        $callable,
        PermissionInterface $permission = Common::ALLOW_ALL,
    ): static {
        $this->rootRoute($routeManager, $verb, $name, $pattern, $callable);

        if ($this->shouldAddRoutePermissions()) {
            $routeManager->addRouteArg($name, 'permission', serialize($permission));
        }

        return $this;
    }

    /**
     * @throws MissingConfigException
     */
    private function shouldAddRoutePermissions(): bool
    {
        if (
            $this->config->get('auth.disable_route_permissions_locally') &&
            $this->config->get('app.environment') === 'local'
        ) {
            return false;
        }

        return true;
    }

    /**
     * @throws MissingConfigException
     * @throws RouteNotFoundException
     */
    protected function addProtectedAdminRootRoute(
        RouteManager $routeManager,
        HttpMethod $verb,
        string $name,
        string $pattern,
        $callable,
        PermissionInterface $permission = Common::ALLOW_ALL,
    ): static {
        $this->rootRoute($routeManager, $verb, $name, 'admin/' . $pattern, $callable);

        if ($this->shouldAddRoutePermissions()) {
            $routeManager->addRouteArg($name, 'permission', serialize($permission));
        }

        return $this;
    }

    /**
     * Declare intention to build routes for this entity. Will take defaults if not previously defined.
     * @return $this
     * @throws RouteNotFoundException
     * @throws RouteNotFoundException
     */
    /**
     * @param class-string<Entity> $entityClass
     */
    protected function addProtectedEntityRoutes(
        RouteManager $routeManager,
        string $entityClass,
        ?array $callables = null,
        array $patterns = []
    ): static {
        $this->setEntityPatterns($entityClass, $patterns);

        foreach ($this->entityPatterns[$entityClass::entityName()] as $verb => $pattern) {
            if (isset($callables[$verb]) && is_array($callables[$verb])) {
                $this->setEntityCallable(
                    $entityClass,
                    $verb,
                    ($callables[$verb][0] ?? displayController::class . ':' . $verb)
                );
            }
        }

        //build the routes.
        $this->buildEntityRoutes($routeManager, $entityClass, true);
        //protect the built routes
        foreach ($this->entityPatterns[$entityClass::entityName()] as $verb => $pattern) {
            // buildEntityRoutes() (above) only registers a route for verbs
            // that have an entry in $this->entityControllers — subclasses
            // (e.g. userDisplayModule since the 3.4.3 legacy-route removal)
            // may omit verbs like 'all'/'get' entirely. Mirror that guard
            // here, or addRouteArg() throws RouteNotFoundException for a
            // route that was never built.
            if (!isset($this->entityControllers[$entityClass::entityName()][$verb])) {
                continue;
            }
            $routeManager->addRouteArg(
                $entityClass::entityName() . '.display.' . strtolower((string) $verb),
                'permission',
                serialize($callables[$verb][1] ?? Common::ALLOW_ALL)
            );
            $routeManager->addRouteArg(
                $entityClass::entityName() . '.display.' . strtolower((string) $verb),
                'entity',
                $entityClass
            );
        }

        return $this;
    }

    /**
     * @param class-string<Entity> $entityClass
     * @param array $patterns
     * @return $this|userManagedDisplayModule
     */
    protected function setEntityPatterns($entityClass, array $patterns = []): static
    {
        $this->defaultEntityPatterns = [
            'all' => '/list', //Root display pattern, by default load all entities
            'post' => '/create', //no pattern here as we should POST to the base entity path (by default)
            'put' => '/{' . $entityClass::primaryKey() . '}/edit',
            'delete' => '/{' . $entityClass::primaryKey() . '}/delete',
            'get' => '[/{' . $entityClass::primaryKey() . '}]',
        ];
        $this->patternsOverridden = $patterns !== [];
        $this->entityPatterns[$entityClass::entityName()] = array_merge($this->defaultEntityPatterns, $patterns);

        return $this;
    }

    /**
     * Normal structure is /ModuleName/EntityName/{pattern}
     * Build any default entity routes.
     * @param class-string<Entity> $entityClass
     */
    protected function buildEntityRoutes(RouteManager $routeManager, string $entityClass, bool $rootRoutes = false): void
    {
        $method = $rootRoutes ? 'rootRoute' : 'addRoute';
        foreach ($this->entityPatterns[$entityClass::entityName()] as $verb => $pattern) {
            if (isset($this->entityControllers[$entityClass::entityName()][$verb])) {
                $basePattern = $this->patternsOverridden ? '' : (str_replace(
                    'entity',
                    '',
                    strtolower($entityClass::entityName())
                ));
                $this->{$method}(
                    $routeManager,
                    HttpMethod::GET, //Always Get for display.
                    $entityClass::entityName() . '.display.' . strtolower((string) $verb),
                    $basePattern . $pattern,
                    $this->entityControllers[$entityClass::entityName()][$verb]
                );
            }
        }
    }
}
