<?php

declare(strict_types=1);

namespace App\Framework\Router;

use App\Framework\Attributes\Singleton;
use App\Framework\Core\DynamicRoute;
use App\Framework\Http\Method;
use App\Framework\Http\Request;

#[Singleton]
final readonly class HttpRouter
{
    public function __construct(
        #public RouteCollection $routes,
        public CompiledRoutes $optimizedRoutes,
    ) {}

    /**
     * Versucht, eine Route zu finden.
     *
     * @param string $method HTTP-Methode, zB. GET, POST
     * @param string $path URI-Pfad, zB. /user/123
     */
    public function match(Request $request): RouteContext
    {
        $method = $request->method;
        $path = $request->path;

        if($staticRoute = $this->optimizedRoutes->getStaticRoute($method->value, $path)) {
            return new RouteContext(
                match: new RouteMatchSuccess($staticRoute),
                method: $method,
                path: $path
            );
        }

        if($dynamicMatch = $this->matchDynamicOptimized($method, $path)) {
            return new RouteContext(
                match: new RouteMatchSuccess($dynamicMatch),
                method: $method,
                path: $path
            );
        }

        return new RouteContext(
            match: new NoRouteMatch(),
            method: $method,
            path: $path
        );

        /*
        $method = $request->method;
        $path = $request->path;

        if (!$this->routes->has($method)) {
            return new RouteContext(
                match: new NoRouteMatch(),
                method: $method,
                path: $path
            );
        }

        $match =  $this->matchStatic($method, $path)
            ?? $this->matchDynamic($method, $path)
            ?? new NoRouteMatch();

        return new RouteContext(
            match: $match,
            method: $method,
            path: $path
        );*/
    }


    private function matchStatic(Method $method, string $path): ?RouteMatch
    {
        if (isset($this->routes->getStatic($method)[$path])) {
            $handler = $this->routes->getStatic($method)[$path];
            return new RouteMatchSuccess($handler);
        }
        return null;
    }

    private function matchDynamic(Method $method, string $path): ?RouteMatch
    {
        /* @var $route DynamicRoute */
        foreach ($this->routes->getDynamic($method) as $route) {

            if (preg_match($route->regex, $path, $matches)) {

                array_shift($matches); // remove full match

                // Überprüfe ob die Anzahl der URL-Parameter stimmt
                if (count($route->paramNames) !== count($matches)) {
                    continue;
                }

                $paramValues = array_combine($route->paramNames, $matches);

                if (!$paramValues) {
                    continue;
                }

                $dynamicRoute = new DynamicRoute(
                    $route->regex,
                    $route->paramNames,
                    $route->controller,
                    $route->action,
                    $route->parameters,
                    $paramValues,
                    $route->name,
                    $route->path,
                );
                return new RouteMatchSuccess($dynamicRoute);
            }
        }

        return null;
    }

    private function matchDynamicOptimized(Method $method, string $path): ?DynamicRoute
    {
        $pattern = $this->optimizedRoutes->getCompiledPattern($method->value);

        if(!$pattern || !preg_match($pattern->regex, $path, $matches)) {
            return null;
        }

        $routeIndex = $this->findMatchingRouteIndex($matches, $method);
        $routeData = $pattern->routes[$routeIndex];
        $paramValues = $this->extractParameters($matches, $routeData['paramMap']);

        $originalRoute = $routeData['route'];
        return new DynamicRoute(
            regex: $originalRoute->regex,
            paramNames: $originalRoute->paramNames,
            controller: $originalRoute->controller,
            action: $originalRoute->action,
            parameters: $originalRoute->parameters,
            paramValues: $paramValues,
            name: $originalRoute->name,
            path: $originalRoute->path,
            attributes: $originalRoute->attributes ?? [],
        );
    }

    private function findMatchingRouteIndex(array $matches, $method): int
    {
        $pattern = $this->optimizedRoutes->getCompiledPattern($method->value);

        foreach($pattern->routes as $index => $routeData) {
            $routeGroupIndex = $routeData['routeGroupIndex'];

            if (isset($matches[$routeGroupIndex]) && $matches[$routeGroupIndex] !== '') {
                return $index;
            }
        }

        return 0;


        for($i = 1; $i < count($matches); $i++) {
            if($matches[$i] !== '') {
                return $i - 1;
            }
        }
        return 0;
    }

    private function extractParameters(array $matches, array $paramMap): array
    {
        $params = [];
        foreach($paramMap as $paramName => $matchIndex) {
            if (isset($matches[$matchIndex]) && $matches[$matchIndex] !== '') {
                $params[$paramName] = $matches[$matchIndex];
            }
        }
        return $params;
    }
}
