<?php

namespace apexl\Io\includes;

use apexl\ClassEngine\EngineSingleton;
use apexl\Config\Configuration as Config;
use apexl\Io\enums\HttpMethod;
use apexl\Io\services\pathUtility;
use apexl\Io\utilityIncludes\route;
use Psr\Http\Server\MiddlewareInterface;
use Slim\App;

class Routes extends EngineSingleton
{
    protected static ?App $Io;
    /** @var array<string, route> */
    protected static array $routes = [];
    protected static string $baseRoute = '/';

    protected function __construct()
    {
        parent::__construct();
        self::$Io = System::$app;
    }

    public static function reset()
    {
        parent::reset();
        self::$Io = null;
        self::$routes = [];
        self::$baseRoute = '/';
    }

    /**
     * Function to set a base route for all routes.
     * Applies at build time to allow contributed modules to override it.
     */
    public static function setBaseRoute(string $route = '/')
    {
        self::$baseRoute = $route;
    }

    public static function getFullyQualifiedRoutePattern(
        string $name,
        array $replacements = [],
        bool $stripTags = true
    ): string|false {
        return sprintf(
            '%s%s',
            Config::getInstance()->app->site->backend_domain,
            self::getRoutePattern($name, $replacements, $stripTags)
        );
    }

    /**
     * Get a route object
     */
    public static function getRoutePattern(
        string $name,
        array $replacements = [],
        bool $stripTags = true
    ): string|false {
        if ($route = self::getRoute($name)) {
            $pattern = $route->getPattern();
            foreach ($replacements as $token => $value) {
                $pattern = str_replace(['{' . $token . '}', '[', ']'], [$value, '', ''], $pattern);
            }
            //No Replacements? Remove any tokens and optionals.
            //@todo do with regex?
            if ($stripTags && str_contains($pattern, '{')) {
                $parts = explode('/', (string) $pattern);
                $pattern = '';

                foreach ($parts as $part) {
                    if ($part) {
                        $clean = str_contains($part, '{') ? '' : str_replace(['[', ']'], '', $part);
                        if ($clean) {
                            $pattern .= '/' . $clean;
                        }
                    }
                }
            }

            return $pattern;
        }

        return false;
    }

    /**
     * Get a route object
     */
    public static function getRoute($name): ?route
    {
        return self::$routes[$name] ?? null;
    }

    /**
     * Add arg to already built route, store reference in routes static
     */
    public static function addRouteArg(string $name, string $arg, mixed $value): void
    {
        $path = pathUtility::getInstance();
        $path->setCurrentRouteArgs($arg, $value);

        if ($route = self::getRoute($name)) {
            $route->setArg($arg, $value);
        }
    }

    /**
     * Set a route object
     */
    public static function setRoute(string $name, route $route): void
    {
        self::$routes[$name] = $route;
    }

    /**
     * Function to build the set routes into a format Slim can understand.
     */
    public static function buildRoutes()
    {
        $routes = self::getRoutes();

        foreach ($routes as $route) {
            /** @var \Slim\Routing\Route $slimRoute */
            $slimRoute = self::$Io->{$route->getMethod()->forSlim()}(
                sprintf(
                    '%s/%s',
                    rtrim(self::$baseRoute, '/'),
                    ltrim($route->getPattern(), '/'),
                ),
                $route->getCallable()
            );

            $slimRoute->setName($route->getName());

            foreach ($route->getArgs() as $argName => $argVal) {
                $slimRoute->setArgument($argName, $argVal);
            }

            foreach ($route->getMiddleware() as $middleware) {
                $slimRoute->addMiddleware($middleware);
            }
        }
    }

    /**
     * Get all defined routes
     *
     * @return array<string, route>
     */
    public static function getRoutes(): array
    {
        return Hook::processHook('routePreBuild', self::$routes);
    }

    public static function get(string $name, string $pattern, $callable, array $middleware = [])
    {
        self::addRoute(HttpMethod::GET, $name, $pattern, $callable, $middleware);
    }

    public static function addRoute(HttpMethod $method, $name, $pattern, $callable, $middleware = [], $args = []): void
    {
        self::$routes[$name] = new route(
            $name,
            $pattern,
            $callable,
            $method,
            $args,
            $middleware,
        );
    }

    /**
     * @param MiddlewareInterface[] $middleware
     */
    public static function post(string $name, string $pattern, $callable, array $middleware = []): void
    {
        self::addRoute(HttpMethod::POST, $name, $pattern, $callable, $middleware);
    }

    public static function put(string $name, string $pattern, $callable, array $middleware = []): void
    {
        self::addRoute(HttpMethod::PUT, $name, $pattern, $callable, $middleware);
    }

    public static function delete(string $name, string $pattern, $callable, array $middleware = []): void
    {
        self::addRoute(HttpMethod::DELETE, $name, $pattern, $callable, $middleware);
    }
}
