<?php
declare(strict_types=1);

namespace App\Core;

use App\Foundation\Container;
use RuntimeException;
use App\Security\RouteAuthorization;

final class Router
{
    /** @var array<string,array<string,array{0:class-string,1:string}>> */
    private array $routes = [];

    /** @var list<string> */
    private array $duplicates = [];

    public function __construct(private readonly ?Container $container = null)
    {
    }

    /** @param array{0:class-string,1:string} $handler */
    public function get(string $path, array $handler): void { $this->add('GET', $path, $handler); }

    /** @param array{0:class-string,1:string} $handler */
    public function post(string $path, array $handler): void { $this->add('POST', $path, $handler); }

    /** @param array{0:class-string,1:string} $handler */
    public function add(string $method, string $path, array $handler): void
    {
        $method = strtoupper(trim($method));
        $path = $this->normalizePath($path);
        $key = $method . ' ' . $path;

        if (isset($this->routes[$method][$path])) {
            $this->duplicates[] = $key;
            throw new RuntimeException('Route AXIOM déclarée deux fois : ' . $key);
        }

        if (!isset($handler[0], $handler[1]) || !is_string($handler[0]) || !is_string($handler[1])) {
            throw new RuntimeException('Gestionnaire invalide pour la route ' . $key);
        }

        $this->routes[$method][$path] = [$handler[0], $handler[1]];
    }

    public function dispatch(string $method, string $path): void
    {
        $method = strtoupper($method);
        $path = $this->normalizePath(str_replace('/public', '', $path) ?: '/');
        $handler = $this->routes[$method][$path] ?? null;

        if ($handler === null) {
            SecurityKernel::enforce($method, $path, ['permissions' => ['__route_policy_missing__']]);
            http_response_code(404);
            echo 'Page introuvable.';
            return;
        }

        [$class, $action] = $handler;
        SecurityKernel::enforce($method, $path, RouteAuthorization::policy($method, $path, $class));
        $controller = $this->container !== null && $this->container->has($class)
            ? $this->container->get($class)
            : new $class();

        if (!method_exists($controller, $action)) {
            throw new RuntimeException(sprintf('Action de route introuvable : %s::%s', $class, $action));
        }

        $controller->$action();
    }

    /** @return list<array{method:string,path:string,controller:string,action:string}> */
    public function all(): array
    {
        $rows = [];
        foreach ($this->routes as $method => $routes) {
            foreach ($routes as $path => [$controller, $action]) {
                $rows[] = compact('method', 'path', 'controller', 'action');
            }
        }
        usort($rows, static fn(array $a, array $b): int => [$a['path'], $a['method']] <=> [$b['path'], $b['method']]);
        return $rows;
    }

    public function count(): int
    {
        return count($this->all());
    }

    /** @return list<string> */
    public function duplicates(): array
    {
        return array_values(array_unique($this->duplicates));
    }

    private function normalizePath(string $path): string
    {
        $path = '/' . trim($path, '/');
        return $path === '/' ? '/' : rtrim($path, '/');
    }
}
