<?php

declare(strict_types=1);

namespace App\Core;

use Closure;
use RuntimeException;

class Router
{
    private array $routes = [];

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

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

    private function addRoute(
        string $method,
        string $uri,
        array|Closure $action
    ): void {
        $uri = '/' . trim($uri, '/');

        if ($uri === '//') {
            $uri = '/';
        }

        $parameterNames = [];

        $pattern = preg_replace_callback(
            '/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/',
            function (array $matches) use (&$parameterNames): string {
                $parameterNames[] = $matches[1];

                return '([^/]+)';
            },
            $uri
        );

        $this->routes[$method][] = [
            'uri' => $uri,
            'pattern' => '#^' . $pattern . '$#',
            'parameters' => $parameterNames,
            'action' => $action,
        ];
    }

    public function dispatch(
        string $method,
        string $uri
    ): void {
        foreach ($this->routes[$method] ?? [] as $route) {
            if (
                !preg_match(
                    $route['pattern'],
                    $uri,
                    $matches
                )
            ) {
                continue;
            }

            array_shift($matches);

            $this->execute(
                $route['action'],
                $matches
            );

            return;
        }

        http_response_code(404);

        echo '<h1>404</h1>';
        echo '<p>The requested page could not be found.</p>';
    }

    private function execute(
        array|Closure $action,
        array $parameters
    ): void {
        if ($action instanceof Closure) {
            $action(...$parameters);
            return;
        }

        [$controllerClass, $method] = $action;

        if (!class_exists($controllerClass)) {
            throw new RuntimeException(
                'Controller not found: '
                . $controllerClass
            );
        }

        $controller = new $controllerClass();

        if (!method_exists($controller, $method)) {
            throw new RuntimeException(
                'Controller method not found: '
                . $method
            );
        }

        $controller->{$method}(...$parameters);
    }
}