<?php
declare(strict_types=1);

namespace App;

class Router
{
    /** @var array<int,array{method:string,pattern:string,regex:string,params:array,handler:mixed,middleware:array}> */
    private array $routes = [];

    public function get(string $pattern, $handler, array $middleware = []): void    { $this->add('GET', $pattern, $handler, $middleware); }
    public function post(string $pattern, $handler, array $middleware = []): void   { $this->add('POST', $pattern, $handler, $middleware); }
    public function put(string $pattern, $handler, array $middleware = []): void    { $this->add('PUT', $pattern, $handler, $middleware); }
    public function delete(string $pattern, $handler, array $middleware = []): void { $this->add('DELETE', $pattern, $handler, $middleware); }
    public function any(string $pattern, $handler, array $middleware = []): void    { $this->add('ANY', $pattern, $handler, $middleware); }

    private function add(string $method, string $pattern, $handler, array $middleware): void
    {
        $params = [];
        $regex = preg_replace_callback('#\{([a-zA-Z_][a-zA-Z0-9_]*)\}#', function ($m) use (&$params) {
            $params[] = $m[1];
            return '([^/]+)';
        }, $pattern);
        $regex = '#^' . $regex . '$#';
        $this->routes[] = compact('method', 'pattern', 'regex', 'params', 'handler', 'middleware');
    }

    public function dispatch(): void
    {
        $uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';

        // Strip the subdirectory base path so routes match as if app is at root
        // e.g. /MysticArtTattoo/public/about → /about
        $script = $_SERVER['SCRIPT_NAME'] ?? '/index.php';
        $basePath = rtrim(dirname($script), '/\\');
        if ($basePath && $basePath !== '.' && $basePath !== '/' && str_starts_with($uri, $basePath)) {
            $uri = substr($uri, strlen($basePath)) ?: '/';
        }

        $method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');

        foreach ($this->routes as $r) {
            if ($r['method'] !== 'ANY' && $r['method'] !== $method) continue;
            if (!preg_match($r['regex'], $uri, $matches)) continue;

            $args = [];
            foreach ($r['params'] as $i => $name) {
                $args[$name] = $matches[$i + 1] ?? null;
            }

            // Middleware chain
            foreach ($r['middleware'] as $mw) {
                if (is_object($mw) && method_exists($mw, 'handle')) {
                    $mw->handle();
                } elseif (is_string($mw) && class_exists($mw)) {
                    $instance = new $mw();
                    if (method_exists($instance, 'handle')) {
                        $instance->handle();
                    }
                } elseif (is_callable($mw)) {
                    $mw();
                }
            }

            $this->invoke($r['handler'], $args);
            return;
        }

        $this->notFound();
    }

    private function invoke($handler, array $args): void
    {
        if (is_callable($handler)) {
            call_user_func_array($handler, array_values($args) ?: [$args]);
            return;
        }
        if (is_array($handler) && count($handler) === 2) {
            [$class, $method] = $handler;
            if (is_string($class)) $class = new $class();
            call_user_func_array([$class, $method], [$args]);
            return;
        }
        if (is_string($handler) && str_contains($handler, '@')) {
            [$class, $method] = explode('@', $handler, 2);
            $class = 'App\\Controllers\\' . $class;
            $instance = new $class();
            call_user_func_array([$instance, $method], [$args]);
            return;
        }
        throw new \RuntimeException('Invalid route handler');
    }

    private function notFound(): void
    {
        http_response_code(404);
        require MAT_ROOT . '/src/Views/errors/404.php';
    }
}
