<?php

declare(strict_types=1);

namespace App\Framework\Router;

use App\Framework\Attributes\Singleton;
use App\Framework\Core\Route;
use App\Framework\Http\Request;
use App\Framework\Router\Exception\RouteNotFound;

/**
 * Service zur Generierung von URLs basierend auf Named Routes
 */
#[Singleton]
final readonly class UrlGenerator
{
    public function __construct(
        private Request $request,
        private CompiledRoutes $routes,
    ) {
    }

    /**
     * Generiert eine URL für eine Named Route
     *
     * @param RouteNameInterface|string $name Name der Route
     * @param array<string, mixed> $parameters Parameter für dynamische Routes
     * @param bool $absolute Ob eine absolute URL generiert werden soll
     * @return string Generierte URL
     * @throws RouteNotFound Wenn die Route nicht gefunden wird
     */
    public function route(RouteNameInterface|string $name, array $parameters = [], bool $absolute = false): string
    {
        $routeName = $name instanceof RouteNameInterface ? $name->value : $name;

        // Debug logging to identify missing route
        error_log("UrlGenerator: Attempting to generate URL for route: '{$routeName}'");

        $route = $this->routes->getNamedRoute($routeName);

        if ($route === null) {
            error_log("UrlGenerator: Route '{$routeName}' not found! Available routes: " . implode(', ', array_keys($this->routes->getAllNamedRoutes())));

            throw new RouteNotFound("Route with name '{$routeName}' not found.");
        }

        $url = $this->buildUrl($route->path, $parameters);

        if ($absolute) {
            return $this->makeAbsolute($url);
        }

        return $url;
    }

    /**
     * Generiert eine absolute URL für eine Named Route
     *
     * @param RouteNameInterface|string $name Name der Route
     * @param array<string, mixed> $parameters Parameter für dynamische Routes
     * @return string Absolute URL
     */
    public function absoluteRoute(RouteNameInterface|string $name, array $parameters = []): string
    {
        return $this->route($name, $parameters, true);
    }

    /**
     * Erstellt eine URL basierend auf dem aktuellen Request mit geänderten Parametern
     *
     * @param array<string, mixed> $parameters Query-Parameter
     * @param bool $absolute Ob eine absolute URL generiert werden soll
     * @return string Generierte URL
     */
    public function current(array $parameters = [], bool $absolute = false): string
    {
        $url = $this->request->path;

        if (! empty($parameters)) {
            $url .= '?' . http_build_query(array_merge(
                $this->request->query->all(),
                $parameters
            ));
        } elseif (! empty($this->request->query->all())) {
            $url .= '?' . http_build_query($this->request->query->all());
        }

        if ($absolute) {
            return $this->makeAbsolute($url);
        }

        return $url;
    }

    /**
     * Prüft, ob die aktuelle Route mit dem gegebenen Namen übereinstimmt
     */
    public function isCurrentRoute(RouteNameInterface|string $name): bool
    {
        $routeName = $name instanceof RouteNameInterface ? $name->value : $name;

        try {
            $route = $this->routes->getNamedRoute($routeName);

            return $route !== null && $route->path === $this->request->path;
        } catch (RouteNotFound) {
            return false;
        }
    }

    /**
     * Erstellt eine URL aus einem Pfad-Template und Parametern
     *
     * @param string $path Pfad-Template (z.B. "/user/{id}/posts/{slug}")
     * @param array<string, mixed> $parameters Parameter-Werte
     * @return string Fertige URL
     */
    private function buildUrl(string $path, array $parameters): string
    {
        $url = $path;
        $queryParams = [];

        // Ersetze Pfad-Parameter
        foreach ($parameters as $key => $value) {
            $placeholder = "{{$key}}";
            if (str_contains($url, $placeholder)) {
                $url = str_replace($placeholder, (string)$value, $url);
                unset($parameters[$key]);
            } else {
                // Übrige Parameter werden zu Query-Parametern
                $queryParams[$key] = $value;
            }
        }

        // Prüfe auf nicht ersetzte Platzhalter
        if (preg_match('/\{[^}]+}/', $url)) {
            throw new \InvalidArgumentException(
                "Missing required parameters for route. URL: {$url}"
            );
        }

        // Füge Query-Parameter hinzu
        if (! empty($queryParams)) {
            $url .= '?' . http_build_query($queryParams);
        }

        return $url;
    }

    /**
     * Macht eine relative URL zu einer absoluten URL
     */
    private function makeAbsolute(string $url): string
    {
        $scheme = $this->request->server->isHttps() ? 'https' : 'http';
        $host = $this->request->server->getHttpHost();

        return "{$scheme}://{$host}{$url}";
    }

    /**
     * Generiert eine URL für eine Asset-Datei
     *
     * @param string $asset Pfad zur Asset-Datei
     * @param bool $absolute Ob eine absolute URL generiert werden soll
     * @return string Asset-URL
     */
    public function asset(string $asset, bool $absolute = false): string
    {
        $url = '/assets/' . ltrim($asset, '/');

        if ($absolute) {
            return $this->makeAbsolute($url);
        }

        return $url;
    }
}
