<?php

declare(strict_types=1);

namespace Instinctor\Core\Router;

if ( ! defined( 'ABSPATH' ) ) { exit; }

/**
 * WordPress router driver.
 *
 * REST routes: delegates to register_rest_route on rest_api_init.
 * Frontend named routes: dispatched via template_redirect.
 * URL generation: uses get_permalink on the mapped WP page or home_url.
 */
class WordPressDriver implements DriverInterface
{
    private const NAMESPACE = 'instinctor/v1';

    /** @var array<array{name:string, pattern:string, regex:string, params:string[], handler:callable, middleware:string[]}> */
    private array $routes = [];

    /** @var array<array{path:string, args:array}> */
    private array $restQueue = [];

    public function __construct()
    {
        // Queue REST registrations; flush them when rest_api_init fires
        add_action('rest_api_init', [$this, 'flushRestRoutes']);

        // Dispatch frontend routes via template_redirect
        add_action('template_redirect', [$this, 'dispatch'], 5);
    }

    // -------------------------------------------------------------------------
    // REST
    // -------------------------------------------------------------------------

    public function rest(string $method, string $path, callable $handler, array $options = []): void
    {
        // Build WP route args array
        $args = [
            'methods'             => $this->normalizeMethod($method),
            'callback'            => $handler,
            'permission_callback' => $options['permission_callback']
                ?? ($options['auth'] ?? false ? [$this, 'defaultAuthCheck'] : '__return_true'),
        ];

        if (!empty($options['args'])) {
            $args['args'] = $options['args'];
        }
        if (!empty($options['schema'])) {
            $args['schema'] = $options['schema'];
        }

        // If rest_api_init has already fired, register immediately
        if (did_action('rest_api_init')) {
            register_rest_route(self::NAMESPACE, $path, [$args]);
        } else {
            $this->restQueue[] = ['path' => $path, 'args' => [$args]];
        }
    }

    /**
     * Register a route with multiple methods/handlers in WP array format.
     * Accepts the full WP register_rest_route() $args array directly —
     * used during migration to preserve permission_callback logic unchanged.
     */
    public function restRaw(string $path, array $args): void
    {
        // If rest_api_init has already fired (i.e. we are inside it), register immediately
        if (did_action('rest_api_init')) {
            register_rest_route(self::NAMESPACE, $path, $args);
        } else {
            $this->restQueue[] = ['path' => $path, 'args' => $args];
        }
    }

    /** @internal Called by rest_api_init action */
    public function flushRestRoutes(): void
    {
        foreach ($this->restQueue as $entry) {
            register_rest_route(self::NAMESPACE, $entry['path'], $entry['args']);
        }
    }

    // -------------------------------------------------------------------------
    // Frontend named routes
    // -------------------------------------------------------------------------

    public function add(string $name, string $pattern, callable $handler, array $middleware = []): void
    {
        // Convert :param segments to named regex groups
        $regex  = preg_replace('/:([a-zA-Z_][a-zA-Z0-9_]*)/', '(?P<$1>[^/]+)', $pattern);
        $regex  = '#^' . $regex . '(?:/)?$#';
        $params = [];
        preg_match_all('/:([a-zA-Z_][a-zA-Z0-9_]*)/', $pattern, $m);
        if (!empty($m[1])) {
            $params = $m[1];
        }

        $this->routes[] = [
            'name'       => $name,
            'pattern'    => $pattern,
            'regex'      => $regex,
            'params'     => $params,
            'handler'    => $handler,
            'middleware' => $middleware,
        ];
    }

    /** @internal Called by template_redirect action */
    public function dispatch(): void
    {
        $requestPath = wp_parse_url(sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'] ?? '/')), PHP_URL_PATH) ?: '/';

        foreach ($this->routes as $route) {
            if (!preg_match($route['regex'], $requestPath, $matches)) {
                continue;
            }

            // Run middleware
            foreach ($route['middleware'] as $mw) {
                if (!$this->runMiddleware($mw)) {
                    return;
                }
            }

            // Extract named params
            $params = [];
            foreach ($route['params'] as $param) {
                $params[$param] = $matches[$param] ?? '';
            }

            call_user_func($route['handler'], $params);
            exit;
        }
    }

    public function url(string $name, array $params = []): string
    {
        foreach ($this->routes as $route) {
            if ($route['name'] !== $name) {
                continue;
            }
            $path = $route['pattern'];
            foreach ($params as $key => $value) {
                $path = str_replace(':' . $key, rawurlencode((string) $value), $path);
            }
            return home_url($path);
        }
        return home_url('/');
    }

    public function restBase(): string
    {
        return rest_url(self::NAMESPACE);
    }

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    private function normalizeMethod(string $method): string
    {
        // Map common aliases to WP REST server constants equivalents
        return strtoupper(str_replace('|', ',', $method));
    }

    private function runMiddleware(string $mw): bool
    {
        if ($mw === 'auth') {
            if (!\Instinctor\Core\Auth::check()) {
                wp_safe_redirect(wp_login_url(esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'] ?? ''))));
                exit;
            }
            return true;
        }

        if ($mw === 'admin') {
            if (!\Instinctor\Core\Auth::isAdmin()) {
                wp_die(esc_html__('Unauthorized', 'instinctor'), 403);
            }
            return true;
        }

        if (str_starts_with($mw, 'nonce:')) {
            $action = substr($mw, 6);
            $token  = sanitize_text_field(wp_unslash($_REQUEST['nonce'] ?? ''));
            if (!wp_verify_nonce($token, $action)) {
                wp_die(esc_html__('Invalid security token', 'instinctor'), 403);
            }
            return true;
        }

        return true;
    }

    private function defaultAuthCheck(): bool
    {
        return \Instinctor\Core\Auth::isAdmin();
    }
}
