<?php

declare(strict_types=1);

namespace App\Framework\DI;

use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\MethodName;
use App\Framework\DI\ValueObjects\DependencyGraphNode;
use App\Framework\Reflection\ReflectionProvider;

/**
 * Dependency Graph für Initializer-Ausführung
 * Analysiert Dependencies und sorgt für korrekte Ausführungsreihenfolge
 */
final class InitializerDependencyGraph
{
    /** @var array<string, DependencyGraphNode> */
    private array $nodes = [];

    /** @var array<string, array<string>> */
    private array $adjacencyList = [];

    /** @var array<string, bool> */
    private array $visited = [];

    /** @var array<string, bool> */
    private array $inStack = [];

    public function __construct(
        private readonly ReflectionProvider $reflectionProvider,
    ) {
    }

    public function addInitializer(string $returnType, ClassName $className, MethodName $methodName): void
    {
        if ($returnType === 'null' || $returnType === 'void') {
            return; // Setup-Initializer werden sofort ausgeführt
        }


        $dependencies = $this->analyzeDependencies($className, $methodName);

        $node = new DependencyGraphNode(
            returnType: $returnType,
            className: $className,
            methodName: $methodName,
            dependencies: $dependencies
        );

        // Debug RouterSetup specifically
        if (str_contains($className->toString(), 'RouterSetup')) {
            error_log("InitializerDependencyGraph: Adding RouterSetup node - " . $className->toString() . " -> " . $returnType);
            error_log("InitializerDependencyGraph: RouterSetup dependencies: " . implode(', ', $dependencies));
        }

        $this->nodes[$returnType] = $node;
        $this->adjacencyList[$returnType] = $dependencies;
    }

    /**
     * Berechnet die optimale Ausführungsreihenfolge basierend auf Dependencies
     * @return array<string> Sortierte Liste von Return-Types
     */
    public function getExecutionOrder(): array
    {
        $this->visited = [];
        $this->inStack = [];
        $result = [];

        foreach (array_keys($this->nodes) as $returnType) {
            if (! isset($this->visited[$returnType])) {
                $this->topologicalSort($returnType, $result);
            }
        }

        return array_reverse($result);
    }

    /**
     * Prüft ob ein Node für den gegebenen Return-Type existiert
     */
    public function hasNode(string $returnType): bool
    {
        $exists = isset($this->nodes[$returnType]);

        // Debug RouterSetup specifically
        if ($returnType === 'App\\Framework\\Router\\Router') {
            error_log("InitializerDependencyGraph: hasNode check for Router - exists: " . ($exists ? 'YES' : 'NO'));
            error_log("InitializerDependencyGraph: Current node count: " . count($this->nodes));
            error_log("InitializerDependencyGraph: Available nodes: " . implode(', ', array_keys($this->nodes)));
        }

        return $exists;
    }

    /**
     * Gibt einen spezifischen Node zurück
     */
    public function getNode(string $returnType): ?DependencyGraphNode
    {
        return $this->nodes[$returnType] ?? null;
    }

    /**
     * Gibt alle Nodes zurück
     *
     * @return array<string, DependencyGraphNode>
     */
    public function getNodes(): array
    {
        return $this->nodes;
    }

    /**
     * Gibt alle Nodes als Array zurück (backward compatibility)
     *
     * @deprecated Use getNodes() for DependencyGraphNode objects
     * @return array<string, array{class: string, method: string, dependencies: string[]}>
     */
    public function getNodesAsArray(): array
    {
        return array_map(fn ($node) => $node->toArray(), $this->nodes);
    }

    /**
     * Analysiert die Dependencies eines Initializers durch Reflection
     */
    private function analyzeDependencies(CLassName $className, MethodName $methodName): array
    {
        try {
            $reflection = $this->reflectionProvider->getClass($className);
            #$reflection = new \ReflectionClass($className);
            $method = $reflection->getMethod($methodName->toString());
            ;
            $dependencies = [];

            foreach ($method->getParameters() as $parameter) {
                $paramType = $parameter->getType();
                if ($paramType instanceof \ReflectionNamedType) {
                    $typeName = $paramType->getName();

                    // Nur Klassen/Interfaces als Dependencies, keine primitiven Typen
                    if (! empty($typeName) && ClassName::create($typeName)->exists()) {
                        $dependencies[] = $typeName;
                    }
                }
            }

            return $dependencies;
        } catch (\Throwable $e) {
            return [];
        }
    }

    /**
     * Topologische Sortierung mit Cycle-Detection
     */
    private function topologicalSort(string $returnType, array &$result): void
    {
        $this->visited[$returnType] = true;
        $this->inStack[$returnType] = true;

        foreach ($this->adjacencyList[$returnType] ?? [] as $dependency) {
            if (isset($this->nodes[$dependency])) {
                if (isset($this->inStack[$dependency]) && $this->inStack[$dependency]) {
                    // Cycle detected - ignore this dependency to prevent infinite loops
                    continue;
                }

                if (! isset($this->visited[$dependency])) {
                    $this->topologicalSort($dependency, $result);
                }
            }
        }

        $this->inStack[$returnType] = false;
        $result[] = $returnType;
    }

    private function hasCycle(string $returnType): bool
    {
        $this->visited[$returnType] = true;
        $this->inStack[$returnType] = true;

        foreach ($this->adjacencyList[$returnType] ?? [] as $dependency) {
            if (isset($this->nodes[$dependency])) {
                if (isset($this->inStack[$dependency]) && $this->inStack[$dependency]) {
                    return true;
                }

                if (! isset($this->visited[$dependency]) && $this->hasCycle($dependency)) {
                    return true;
                }
            }
        }

        $this->inStack[$returnType] = false;

        return false;
    }
}
