<?php
/**
 * Front Controller Router
 * Routes: GET/POST /path -> controller method
 */
class Router {
    private array $routes = [];
    private string $prefix = '';

    public function prefix(string $prefix): self {
        $clone = clone $this;
        $clone->prefix = rtrim($prefix, '/');
        return $clone;
    }

    public function get(string $path, callable|array $handler): void {
        $this->addRoute('GET', $path, $handler);
    }

    public function post(string $path, callable|array $handler): void {
        $this->addRoute('POST', $path, $handler);
    }

    public function any(string $path, callable|array $handler): void {
        $this->addRoute('ANY', $path, $handler);
    }

    private function addRoute(string $method, string $path, $handler): void {
        $full = $this->prefix . '/' . ltrim($path, '/');
        $full = rtrim($full, '/') ?: '/';
        $this->routes[] = compact('method','full','handler') + ['path' => $path];
    }

    public function dispatch(string $uri, string $method): void {
        $uri    = strtok($uri, '?');
        $uri    = '/' . trim($uri, '/') ?: '/';

        foreach ($this->routes as $route) {
            if ($route['method'] !== $method && $route['method'] !== 'ANY') continue;
            $pattern = preg_replace('/\{[a-z_]+\}/', '([^/]+)', $route['full']);
            $pattern = '#^' . $pattern . '$#';
            if (preg_match($pattern, $uri, $matches)) {
                array_shift($matches); // remove full match
                $this->call($route['handler'], $matches);
                return;
            }
        }
        http_response_code(404);
        include APP_ROOT . '/views/errors/404.php';
    }

    private function call($handler, array $params): void {
        if (is_callable($handler)) {
            call_user_func_array($handler, $params);
        } elseif (is_array($handler)) {
            [$class, $method] = $handler;
            $obj = new $class();
            call_user_func_array([$obj, $method], $params);
        }
    }
}
