<?php
declare(strict_types=1);

function cw_default_routes(): array
{
    return [
        'home' => ['path' => '', 'file' => 'home.php', 'title' => 'CW – Control & Watch', 'description' => 'Sicherheit, Verwaltung und Analyse für kompakte Websites.', 'active' => true],
        '404' => ['path' => '404', 'file' => 'error-404.php', 'title' => 'Seite nicht gefunden', 'description' => 'Die angeforderte Seite wurde nicht gefunden.', 'active' => true],
    ];
}

function cw_all_routes(): array
{
    static $routes;
    if (is_array($routes)) return $routes;

    $configured = [];
    $path = project_root() . '/project/config/routes.php';
    if (is_file($path)) {
        try {
            $value = require $path;
            if (is_array($value)) $configured = $value;
        } catch (Throwable $e) {
            error_log('CW Routen-Konfiguration: ' . $e->getMessage());
        }
    }
    if ($configured === []) $configured = cw_default_routes();

    $normalized = [];
    foreach ($configured as $key => $route) {
        $key = preg_replace('/[^a-z0-9_-]/', '', strtolower((string)$key)) ?: '';
        if ($key === '' || !is_array($route)) continue;
        $file = basename((string)($route['file'] ?? ''));
        if (!preg_match('/^[A-Za-z0-9][A-Za-z0-9._-]*\.php$/', $file)) continue;
        $pathValue = trim(str_replace('\\', '/', (string)($route['path'] ?? '')), '/');
        if ($pathValue !== '' && !preg_match('~^[a-zA-Z0-9/_-]+$~', $pathValue)) continue;
        $normalized[$key] = [
            'path' => $pathValue,
            'file' => $file,
            'title' => trim((string)($route['title'] ?? ucfirst($key))),
            'description' => trim((string)($route['description'] ?? '')),
            'active' => !array_key_exists('active', $route) || !empty($route['active']),
        ];
        foreach (['legal', 'form'] as $flag) {
            if (!empty($route[$flag])) $normalized[$key][$flag] = true;
        }
    }
    if (!isset($normalized['404'])) $normalized['404'] = cw_default_routes()['404'];
    return $routes = $normalized;
}

function cw_routes(): array
{
    return array_filter(cw_all_routes(), static fn(array $route): bool => !empty($route['active']));
}

/**
 * Aktive Module können einen Resolver für dynamische Frontend-Routen registrieren.
 * Der Resolver erhält den normalisierten Pfad und liefert entweder null oder eine
 * vollständige Route mit einem aufrufbaren `render`-Handler zurück.
 */
function cw_register_frontend_route_resolver(string $key, callable $resolver, int $priority = 100): void
{
    $key = preg_replace('/[^a-z0-9_.-]/', '', strtolower(trim($key))) ?: '';
    if ($key === '') throw new InvalidArgumentException('Ungültige Kennung für den Frontend-Routenresolver.');
    $GLOBALS['cw_frontend_route_resolvers'][$key] = [
        'key' => $key,
        'priority' => max(-10000, min(10000, $priority)),
        'resolver' => $resolver,
    ];
}

/** @return list<array{key:string,priority:int,resolver:callable}> */
function cw_frontend_route_resolvers(): array
{
    $resolvers = [];
    foreach ((array)($GLOBALS['cw_frontend_route_resolvers'] ?? []) as $resolver) {
        if (!is_array($resolver) || !is_callable($resolver['resolver'] ?? null)) continue;
        $resolvers[] = $resolver;
    }
    usort($resolvers, static fn(array $a, array $b): int => ((int)$a['priority'] <=> (int)$b['priority']) ?: strcmp((string)$a['key'], (string)$b['key']));
    return $resolvers;
}

function cw_request_path(): string
{
    // Mehrfache Slashes zuerst normalisieren. Das muss VOR parse_url()
    // passieren: Eine Request-URI wie //blog/beitrag wird sonst als
    // scheme-relative URL interpretiert und "blog" faelschlich als Host
    // behandelt. Der eigentliche Blog-Prefix ginge dadurch verloren.
    $requestUri = (string)($_SERVER['REQUEST_URI'] ?? '/');
    $requestUri = preg_replace('~^/{2,}~', '/', $requestUri) ?? $requestUri;

    $uri = parse_url($requestUri, PHP_URL_PATH) ?: '/';
    $uri = rawurldecode($uri);
    $uri = preg_replace('~/+~', '/', $uri) ?? $uri;

    $scriptDir = rtrim(str_replace('\\', '/', dirname((string)($_SERVER['SCRIPT_NAME'] ?? '/index.php'))), '/');
    if ($scriptDir !== '' && $scriptDir !== '/' && str_starts_with($uri, $scriptDir . '/')) {
        $uri = substr($uri, strlen($scriptDir));
    }
    return trim($uri, '/');
}

function cw_match_route(string $path): array
{
    foreach (cw_routes() as $key => $route) {
        if ((string)$route['path'] === $path) {
            if ($key === '404') http_response_code(404);
            return ['key' => $key, 'dynamic' => false] + $route;
        }
    }

    foreach (cw_frontend_route_resolvers() as $definition) {
        try {
            $route = ($definition['resolver'])($path);
            if (!is_array($route)) continue;
            if (!is_callable($route['render'] ?? null)) {
                throw new RuntimeException('Dynamische Route besitzt keinen Renderer.');
            }
            $key = preg_replace('/[^a-z0-9_.-]/', '', strtolower((string)($route['key'] ?? $definition['key']))) ?: (string)$definition['key'];
            return array_replace([
                'key' => $key,
                'path' => $path,
                'title' => ucfirst($key),
                'description' => '',
                'active' => true,
                'dynamic' => true,
                'legal' => false,
                'form' => false,
            ], $route, ['key' => $key, 'dynamic' => true]);
        } catch (Throwable $error) {
            error_log('CW dynamische Route ' . (string)$definition['key'] . ': ' . $error->getMessage());
        }
    }

    http_response_code(404);
    return ['key' => '404', 'dynamic' => false] + (cw_all_routes()['404'] ?? cw_default_routes()['404']);
}
