<?php
declare(strict_types=1);

namespace App\Core;

final class Router
{
    /** @var array<int, array{method:string,pattern:string,handler:callable|array,auth:bool,roles:array}> */
    private array $routes = [];

    public function get(string $path, callable|array $handler, bool $auth = true, array $roles = []): self
    {
        return $this->add('GET', $path, $handler, $auth, $roles);
    }

    public function post(string $path, callable|array $handler, bool $auth = true, array $roles = []): self
    {
        return $this->add('POST', $path, $handler, $auth, $roles);
    }

    public function patch(string $path, callable|array $handler, bool $auth = true, array $roles = []): self
    {
        return $this->add('PATCH', $path, $handler, $auth, $roles);
    }

    public function delete(string $path, callable|array $handler, bool $auth = true, array $roles = []): self
    {
        return $this->add('DELETE', $path, $handler, $auth, $roles);
    }

    private function add(string $method, string $path, callable|array $handler, bool $auth, array $roles): self
    {
        $this->routes[] = [
            'method' => $method,
            'pattern' => $this->compile($path),
            'handler' => $handler,
            'auth' => $auth,
            'roles' => $roles,
        ];
        return $this;
    }

    private function compile(string $path): string
    {
        $path = '/' . trim($path, '/');
        if ($path === '/') {
            return '#^/$#';
        }
        $regex = preg_replace('#\{([a-zA-Z_][a-zA-Z0-9_]*)\}#', '(?P<$1>[^/]+)', $path);
        return '#^' . $regex . '$#';
    }

    public function dispatch(Request $request): void
    {
        foreach ($this->routes as $route) {
            if ($route['method'] !== $request->method()) {
                continue;
            }
            if (!preg_match($route['pattern'], $request->path(), $matches)) {
                continue;
            }
            $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
            $request->setParams($params);

            if ($route['auth'] && !Auth::instance()->check()) {
                if (is_ajax()) {
                    json_out(['ok' => false, 'error' => 'Unauthorized'], 401);
                }
                redirect('login');
            }
            if ($route['auth'] && Auth::instance()->check() && !Auth::instance()->tenantId()) {
                Auth::instance()->logout();
                redirect('login');
            }
            if ($route['roles'] && !Auth::instance()->hasRole(...$route['roles'])) {
                if (is_ajax()) {
                    json_out(['ok' => false, 'error' => 'Forbidden'], 403);
                }
                http_response_code(403);
                echo 'Forbidden';
                return;
            }

            $handler = $route['handler'];
            if (is_array($handler)) {
                [$class, $method] = $handler;
                $controller = new $class();
                $controller->{$method}($request);
                return;
            }
            $handler($request);
            return;
        }

        http_response_code(404);
        if (is_ajax()) {
            json_out(['ok' => false, 'error' => 'Not found'], 404);
        }
        if (Auth::instance()->check()) {
            view('errors/404', ['title' => 'Not found']);
            return;
        }
        echo 'Not found';
    }
}
