<?php
class Router
{
    private array $routes = [];
    private ?string $lastMatchedRoute = null;

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

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

    private function addRoute(string $method, string $path, string $action): void
    {
        $this->routes[$method][$this->normalize($path)] = $action;
    }

    public function dispatch(string $uri, string $method): void
    {
        $path = $this->normalize(parse_url($uri, PHP_URL_PATH));

        $config = require __DIR__ . '/../../config/config.php';
        if (!empty($config['app']['debug'])) {
            $debugDir = __DIR__ . '/../../storage/logs';
            if (!is_dir($debugDir)) {
                @mkdir($debugDir, 0755, true);
            }
            @file_put_contents($debugDir . '/router_debug.log', json_encode([
                'method' => $method,
                'uri' => $uri,
                'path' => $path,
                'has_route' => $this->matchExists($method, $path),
                'registered_routes' => array_map(
                    static fn($action) => $action,
                    $this->routes[$method] ?? []
                ),
            ]) . PHP_EOL, FILE_APPEND | LOCK_EX);
        }

        $action = $this->findAction($method, $path);
        if ($action === null) {
            http_response_code(404);
            echo 'Page not found';
            return;
        }

        $params = $this->extractParams($path, $this->getMatchedRouteKey($method, $path));
        [$controllerName, $methodName] = explode('@', $action);
        $controllerClass = $controllerName;
        $controllerPath = __DIR__ . '/../controllers/' . $controllerClass . '.php';
        if (file_exists($controllerPath)) {
            require_once $controllerPath;
            $controller = new $controllerClass();
            if (method_exists($controller, $methodName)) {
                $controller->$methodName(...$params);
                return;
            }
        }

        http_response_code(404);
        echo 'Page not found';
    }

    private function findAction(string $method, string $path): ?string
    {
        if (isset($this->routes[$method][$path])) {
            return $this->routes[$method][$path];
        }

        foreach ($this->routes[$method] as $routePath => $action) {
            if ($this->isDynamic($routePath) && $this->matchesDynamic($path, $routePath)) {
                $this->lastMatchedRoute = $routePath;
                return $action;
            }
        }

        return null;
    }

    private function extractParams(string $path, string $routePath): array
    {
        $values = [];
        preg_match_all('#\{([^}]+)\}#', $routePath, $paramNames);
        $pattern = preg_replace('#\{[^}]+\}#', '([^/]+)', $routePath);

        if (preg_match('#^' . $pattern . '$#', $path, $matches)) {
            array_shift($matches);
            $values = $matches;
        }

        return $values;
    }

    private function matchesDynamic(string $path, string $routePath): bool
    {
        $pattern = preg_replace('#\{[^}]+\}#', '([^/]+)', $routePath);
        return (bool)preg_match('#^' . $pattern . '$#', $path);
    }

    private function isDynamic(string $path): bool
    {
        return str_contains($path, '{');
    }

    private function getMatchedRouteKey(string $method, string $path): string
    {
        foreach ($this->routes[$method] as $routePath => $action) {
            if ($routePath === $path) {
                return $routePath;
            }
        }

        foreach ($this->routes[$method] as $routePath => $action) {
            if ($this->isDynamic($routePath) && $this->matchesDynamic($path, $routePath)) {
                return $routePath;
            }
        }

        return $path;
    }

    private function matchExists(string $method, string $path): bool
    {
        return $this->findAction($method, $path) !== null;
    }

    private function normalize(string $path): string
    {
        return rtrim($path, '/') ?: '/';
    }
}
