<?php
/**
 * Router Implementation
 *
 * @package Licenzo\Framework\Http\Router
 */

namespace Licenzo\Framework\Http\Router;

use Licenzo\Framework\Contracts\Router\Router as RouterContract;
use Licenzo\Framework\Contracts\Router\Route as RouteContract;
use Licenzo\Framework\Contracts\Router\RouteGroup;
use Licenzo\Framework\Foundation\Application;
use Licenzo\Framework\Http\Request;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Main router implementation
 */
class Router implements RouterContract
{
    /**
     * Application instance
     *
     * @var Application
     */
    protected Application $app;

    /**
     * Route collection
     *
     * @var RouteCollection
     */
    protected RouteCollection $routes;

    /**
     * Group stack for nested groups
     *
     * @var array
     */
    protected array $groupStack = [];

    /**
     * Named routes registry
     *
     * @var array
     */
    protected array $namedRoutes = [];

    /**
     * Route cache enabled
     *
     * @var bool
     */
    protected bool $cacheEnabled = false;

    /**
     * Cached routes
     *
     * @var array|null
     */
    protected $cachedRoutes = [];

    public $namespace = 'licenzo/v1';

    public $prefix = '';

    /**
     * Create a new router instance
     *
     * @param Application $app
     */
    public function __construct(Application $app)
    {
        $this->app = $app;
        $this->routes = new RouteCollection();
    }

    /**
     * Register a GET route
     *
     * @param string $uri
     * @param mixed $action
     * @return RouteContract
     */
    public function get(string $uri, $action): RouteContract
    {
        return $this->addRoute('GET', $uri, $action);
    }

    /**
     * Register a POST route
     *
     * @param string $uri
     * @param mixed $action
     * @return RouteContract
     */
    public function post(string $uri, $action): RouteContract
    {
        return $this->addRoute('POST', $uri, $action);
    }

    /**
     * Register a PUT route
     *
     * @param string $uri
     * @param mixed $action
     * @return RouteContract
     */
    public function put(string $uri, $action): RouteContract
    {
        return $this->addRoute('PUT', $uri, $action);
    }

    /**
     * Register a PATCH route
     *
     * @param string $uri
     * @param mixed $action
     * @return RouteContract
     */
    public function patch(string $uri, $action): RouteContract
    {
        return $this->addRoute('PATCH', $uri, $action);
    }

    /**
     * Register a DELETE route
     *
     * @param string $uri
     * @param mixed $action
     * @return RouteContract
     */
    public function delete(string $uri, $action): RouteContract
    {
        return $this->addRoute('DELETE', $uri, $action);
    }

    /**
     * Create a route group with attributes
     *
     * @param array $attributes
     * @param \Closure $callback
     * @return void
     */
    public function group(array $attributes, \Closure $callback): void
    {
        $this->groupStack[] = $attributes;
        $callback();
        array_pop($this->groupStack);
    }

    /**
     * Create a middleware group
     *
     * @param string|array $middleware
     * @return RouteGroup
     */
    public function middleware($middleware): RouteGroup
    {
        return new \Licenzo\Framework\Http\Router\RouteGroup($this, ['middleware' => (array) $middleware]);
    }

    /**
     * Create a policy group
     *
     * @param string $policy
     * @return RouteGroup
     */
    public function policy($policy): RouteGroup
    {
        return new \Licenzo\Framework\Http\Router\RouteGroup($this, ['policy' => $policy]);
    }

    public function prefix($prefix)
    {
        $this->prefix = $prefix;
    }

    public function namespace($namespace)
    {
        $this->namespace = $namespace;
    }

    public function permissions($permissions)
    {
        return new \Licenzo\Framework\Http\Router\RouteGroup($this, ['permissions' => $permissions]);
    }

    /**
     * Match a request to a route
     *
     * @param Request $request
     * @return RouteContract|null
     */
    public function match(Request $request): ?RouteContract
    {
        return $this->routes->match($request);
    }

    /**
     * Register all routes with WordPress REST API
     *
     * @return void
     */
    public function dispatch(): void
    {
        foreach ($this->getCachedRoutes() as $route) {
            $route->register();
        }
    }

    /**
     * Get all registered routes
     *
     * @return array
     */
    public function getRoutes(): array
    {
        return $this->routes->all();
    }

    /**
     * Get the route collection
     *
     * @return RouteCollection
     */
    public function getRouteCollection(): RouteCollection
    {
        return $this->routes;
    }

    /**
     * Clear all routes
     *
     * @return void
     */
    public function clearRoutes(): void
    {
        $this->routes->clear();
    }

    /**
     * Add a route to the collection
     *
     * @param string $method
     * @param string $uri
     * @param mixed $action
     * @return RouteContract
     */
    protected function addRoute($method, $uri, $action): RouteContract
    {
        $routeOptions = [
            'namespace'  => $this->namespace,
            'prefix'     => $this->prefix,
            'middleware' => [],
            'policy'     => null,
            'name'       => null,
        ];

        $route = Route::create($this->app, $method, $uri, $action, $routeOptions);
        $route->setRouter($this);

        // Apply group attributes
        if (!empty($this->groupStack)) {
            $route = $this->applyGroupAttributes($route);
        }

        $this->routes->add($route);
        return $route;
    }

    /**
     * Apply group attributes to a route
     *
     * @param RouteContract $route
     * @return RouteContract
     */
    protected function applyGroupAttributes(RouteContract $route): RouteContract
    {
        foreach ($this->groupStack as $group) {

            if (isset($group['middleware'])) {
                $route->middleware($group['middleware']);
            }

            if (isset($group['policy'])) {
                $route->policy($group['policy']);
            }

            if (isset($group['prefix'])) {
                $route->setPrefix($group['prefix']);
            }

            if (isset($group['namespace'])) {
                $route->setNamespace($group['namespace']);
            }

            if (isset($group['permission'])) {
                $route->permission($group['permission']);
            }
        }

        return $route;
    }

    /**
     * Add a named route to the registry
     *
     * @param string $name
     * @param RouteContract $route
     * @return void
     */
    public function addNamedRoute(string $name, RouteContract $route): void
    {
        $this->namedRoutes[$name] = $route;
    }

    /**
     * Enable route caching
     *
     * @param bool $enabled
     * @return self
     */
    public function cache(bool $enabled = true): self
    {
        $this->cacheEnabled = $enabled;
        return $this;
    }

    /**
     * Check if route caching is enabled
     *
     * @return bool
     */
    public function isCacheEnabled(): bool
    {
        return $this->cacheEnabled;
    }

    /**
     * Get cached routes or generate cache
     *
     * @return array
     */
    protected function getCachedRoutes(): array
    {
        if (!$this->cacheEnabled) {
            return $this->routes->all();
        }

        if ($this->cachedRoutes === null) {
            $this->cachedRoutes = $this->routes->all();
        }

        return $this->cachedRoutes;
    }

    /**
     * Clear route cache
     *
     * @return void
     */
    public function clearCache(): void
    {
        $this->cachedRoutes = null;
    }
}
