<?php
/*
 * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * See the LICENSE file in the project root for the full license text.
 */

declare(strict_types=1);

namespace App\Http;

/**
 * Tiny pattern router.
 *
 * Patterns use `{name}` for path segments. Numeric-looking captures are cast
 * to int; everything else stays a string. Handlers receive (Request, array $params).
 */
final class Router
{
    /** @var list<array{method:string,regex:string,names:list<string>,handler:callable}> */
    private array $routes = [];

    public function add(string $method, string $pattern, callable $handler): void
    {
        [$regex, $names] = self::compile($pattern);
        $this->routes[] = [
            'method'  => strtoupper($method),
            'regex'   => $regex,
            'names'   => $names,
            'handler' => $handler,
        ];
    }

    public function get(string $p, callable $h): void    { $this->add('GET', $p, $h); }
    public function post(string $p, callable $h): void   { $this->add('POST', $p, $h); }
    public function patch(string $p, callable $h): void  { $this->add('PATCH', $p, $h); }
    public function delete(string $p, callable $h): void { $this->add('DELETE', $p, $h); }

    public function dispatch(Request $req): Response
    {
        $methodMatchedButNotPath = false;

        foreach ($this->routes as $r) {
            if (!preg_match($r['regex'], $req->path, $m)) {
                continue;
            }
            if ($r['method'] !== $req->method) {
                $methodMatchedButNotPath = true;
                continue;
            }

            $params = [];
            foreach ($r['names'] as $i => $name) {
                $val = $m[$i + 1] ?? '';
                $params[$name] = ctype_digit($val) ? (int) $val : $val;
            }

            $result = ($r['handler'])($req, $params);
            if ($result instanceof Response) {
                return $result;
            }
            if (is_string($result)) {
                return Response::html($result);
            }
            if (is_array($result) || is_object($result) || is_bool($result) || is_null($result)) {
                return Response::json($result);
            }
            return Response::text((string) $result);
        }

        if ($methodMatchedButNotPath) {
            return Response::text('Method Not Allowed', 405);
        }
        return Response::text('Not Found', 404);
    }

    /** @return array{0:string,1:list<string>} */
    private static function compile(string $pattern): array
    {
        $names = [];
        $regex = preg_replace_callback(
            '/\{([A-Za-z_][A-Za-z0-9_]*)\}/',
            function ($m) use (&$names) {
                $names[] = $m[1];
                return '([^/]+)';
            },
            $pattern
        );
        return ['#^' . $regex . '$#', $names];
    }
}
