<?php

declare(strict_types=1);

namespace App\Framework\Http;

/**
 * Result of middleware dependency resolution with metadata
 */
final readonly class ResolvedMiddlewareStack
{
    /**
     * @param array<int, string> $middlewares
     * @param array<string, mixed> $dependencyGraph
     */
    public function __construct(
        private array $middlewares,
        private array $dependencyGraph
    ) {
    }

    /**
     * Get middlewares in resolved execution order
     * @return array<int, string>
     */
    public function getMiddlewares(): array
    {
        return $this->middlewares;
    }

    /**
     * Get dependency graph for debugging
     * @return array<string, mixed>
     */
    public function getDependencyGraph(): array
    {
        return $this->dependencyGraph;
    }

    /**
     * Get count of resolved middlewares
     */
    public function count(): int
    {
        return count($this->middlewares);
    }

    /**
     * Check if a specific middleware was resolved
     */
    public function has(string $middlewareClass): bool
    {
        return in_array($middlewareClass, $this->middlewares, true);
    }

    /**
     * Get dependencies for a specific middleware
     * @return array<int, string>
     */
    public function getDependenciesFor(string $middlewareClass): array
    {
        return $this->dependencyGraph[$middlewareClass]['dependencies'] ?? [];
    }

    /**
     * Get services that a middleware provides
     * @return array<int, string>
     */
    public function getProvidedBy(string $middlewareClass): array
    {
        return $this->dependencyGraph[$middlewareClass]['provides'] ?? [];
    }

    /**
     * Check if a middleware is marked as critical
     */
    public function isCritical(string $middlewareClass): bool
    {
        return $this->dependencyGraph[$middlewareClass]['is_critical'] ?? false;
    }

    /**
     * Get all critical middlewares
     * @return array<int, string>
     */
    public function getCriticalMiddlewares(): array
    {
        /** @var array<int, string> */
        $critical = [];

        foreach ($this->middlewares as $middleware) {
            if ($this->isCritical($middleware)) {
                $critical[] = $middleware;
            }
        }

        return $critical;
    }

    /**
     * Get detailed resolution info for debugging
     * @return array<string, mixed>
     */
    public function getResolutionInfo(): array
    {
        return [
            'total_middlewares' => count($this->middlewares),
            'execution_order' => array_map(fn ($class) => basename($class), $this->middlewares),
            'critical_middlewares' => array_map(fn ($class) => basename($class), $this->getCriticalMiddlewares()),
            'dependency_chains' => $this->getDependencyChains(),
            'circular_dependencies' => $this->detectCircularDependencies(),
        ];
    }

    /**
     * Get dependency chains for visualization
     * @return array<string, mixed>
     */
    private function getDependencyChains(): array
    {
        /** @var array<string, array<int, string>> */
        $chains = [];

        foreach ($this->middlewares as $middleware) {
            $dependencies = $this->getDependenciesFor($middleware);
            if (! empty($dependencies)) {
                $chains[basename($middleware)] = array_map(fn ($dep) => basename($dep), $dependencies);
            }
        }

        return $chains;
    }

    /**
     * Detect any circular dependencies in the graph
     * @return array<int, array<int, string>>
     */
    private function detectCircularDependencies(): array
    {
        /** @var array<int, array<int, string>> */
        $circular = [];
        /** @var array<string, bool> */
        $visited = [];

        foreach (array_keys($this->dependencyGraph) as $node) {
            if (! isset($visited[$node])) {
                $result = $this->detectCircularDfs($node, $visited, []);
                if ($result['hasCircular']) {
                    $circular = array_merge($circular, $result['circular']);
                }
                $visited = array_merge($visited, $result['visited']);
            }
        }

        return $circular;
    }

    /**
     * DFS for circular dependency detection (returns result instead of using references)
     * @param array<string, bool> $visited
     * @param array<string, bool> $stack
     * @return array<string, mixed>
     */
    private function detectCircularDfs(string $node, array $visited, array $stack): array
    {
        $visited[$node] = true;
        $stack[$node] = true;
        /** @var array<int, array<int, string>> */
        $circular = [];

        /** @var array<int, string> */
        $dependencies = $this->dependencyGraph[$node]['dependencies'] ?? [];

        foreach ($dependencies as $dependency) {
            if (! isset($visited[$dependency])) {
                $result = $this->detectCircularDfs($dependency, $visited, $stack);
                $visited = array_merge($visited, $result['visited']);
                $circular = array_merge($circular, $result['circular']);
            } elseif (isset($stack[$dependency])) {
                $circular[] = [$node, $dependency];
            }
        }

        unset($stack[$node]);

        return [
            'visited' => $visited,
            'circular' => $circular,
            'hasCircular' => ! empty($circular),
        ];
    }

    /**
     * String representation for debugging
     */
    public function __toString(): string
    {
        $middlewareNames = array_map(fn ($class) => basename($class), $this->middlewares);

        return sprintf(
            'ResolvedMiddlewareStack[%d middlewares: %s]',
            count($this->middlewares),
            implode(' → ', $middlewareNames)
        );
    }
}
