<?php

declare(strict_types=1);

namespace App\Framework\Discovery\Enhanced\Analysis;

use App\Framework\Discovery\Enhanced\ValueObjects\ComponentType;
use App\Framework\Discovery\Enhanced\ValueObjects\DependencyEdge;
use App\Framework\Discovery\Enhanced\ValueObjects\DependencyGraph;
use App\Framework\Discovery\Enhanced\ValueObjects\DependencyNode;
use App\Framework\Mcp\Core\Services\IntelligentMcpCacheManager;
use App\Framework\Mcp\Core\ValueObjects\CacheStrategy;

/**
 * Dependency Graph Analyzer
 *
 * Analyzes component dependencies and creates comprehensive dependency graphs
 * with intelligent caching and performance optimization.
 */
final readonly class DependencyGraphAnalyzer
{
    public function __construct(
        private IntelligentMcpCacheManager $cacheManager
    ) {
    }

    /**
     * Analyze component dependencies and create dependency graph
     */
    public function analyze(array $components): DependencyGraph
    {
        return $this->cacheManager->remember(
            'dependency_analyzer',
            'create_graph',
            ['components_hash' => $this->hashComponents($components)],
            fn () => $this->createDependencyGraph($components),
            CacheStrategy::LONG
        );
    }

    /**
     * Analyze circular dependencies
     */
    public function findCircularDependencies(DependencyGraph $graph): array
    {
        return $this->cacheManager->remember(
            'dependency_analyzer',
            'circular_dependencies',
            ['graph_hash' => $graph->getHash()],
            fn () => $this->detectCycles($graph),
            CacheStrategy::MEDIUM
        );
    }

    /**
     * Get dependency depth analysis
     */
    public function analyzeDependencyDepth(DependencyGraph $graph): array
    {
        return $this->cacheManager->remember(
            'dependency_analyzer',
            'depth_analysis',
            ['graph_hash' => $graph->getHash()],
            fn () => $this->calculateDepths($graph),
            CacheStrategy::MEDIUM
        );
    }

    /**
     * Find component clusters (strongly connected components)
     */
    public function findComponentClusters(DependencyGraph $graph): array
    {
        return $this->cacheManager->remember(
            'dependency_analyzer',
            'component_clusters',
            ['graph_hash' => $graph->getHash()],
            fn () => $this->findStronglyConnectedComponents($graph),
            CacheStrategy::LONG
        );
    }

    /**
     * Analyze dependency impact (what depends on what)
     */
    public function analyzeImpact(DependencyGraph $graph, string $componentName): array
    {
        return $this->cacheManager->remember(
            'dependency_analyzer',
            'impact_analysis',
            ['graph_hash' => $graph->getHash(), 'component' => $componentName],
            fn () => $this->calculateImpact($graph, $componentName),
            CacheStrategy::SHORT
        );
    }

    /**
     * Get optimal loading order based on dependencies
     */
    public function getOptimalLoadingOrder(DependencyGraph $graph): array
    {
        return $this->cacheManager->remember(
            'dependency_analyzer',
            'loading_order',
            ['graph_hash' => $graph->getHash()],
            fn () => $this->topologicalSort($graph),
            CacheStrategy::LONG
        );
    }

    /**
     * Create dependency graph from components
     */
    private function createDependencyGraph(array $components): DependencyGraph
    {
        $nodes = [];
        $edges = [];

        // Create nodes for each component
        foreach ($components as $component) {
            $nodes[] = $this->createDependencyNode($component);
        }

        // Analyze dependencies between components
        foreach ($components as $component) {
            $dependencies = $this->extractDependencies($component);
            foreach ($dependencies as $dependency) {
                if ($this->componentExists($dependency, $components)) {
                    $edges[] = new DependencyEdge(
                        from: $component['name'] ?? $component['class'] ?? 'unknown',
                        to: $dependency,
                        type: $this->determineDependencyType($component, $dependency),
                        strength: $this->calculateDependencyStrength($component, $dependency)
                    );
                }
            }
        }

        return new DependencyGraph($nodes, $edges);
    }

    /**
     * Create dependency node from component
     */
    private function createDependencyNode(array $component): DependencyNode
    {
        return new DependencyNode(
            id: $component['name'] ?? $component['class'] ?? uniqid(),
            name: $component['name'] ?? $component['class'] ?? 'Unknown',
            type: $this->determineComponentType($component),
            metadata: $component['metadata'] ?? [],
            weight: $this->calculateComponentWeight($component)
        );
    }

    /**
     * Extract dependencies from component
     */
    private function extractDependencies(array $component): array
    {
        $dependencies = [];

        // Extract from constructor dependencies
        if (isset($component['constructor_dependencies'])) {
            $dependencies = array_merge($dependencies, $component['constructor_dependencies']);
        }

        // Extract from method dependencies
        if (isset($component['method_dependencies'])) {
            $dependencies = array_merge($dependencies, $component['method_dependencies']);
        }

        // Extract from attribute dependencies
        if (isset($component['attributes'])) {
            foreach ($component['attributes'] as $attribute) {
                if (isset($attribute['dependencies'])) {
                    $dependencies = array_merge($dependencies, $attribute['dependencies']);
                }
            }
        }

        return array_unique($dependencies);
    }

    /**
     * Determine component type from component data
     */
    private function determineComponentType(array $component): ComponentType
    {
        $class = $component['class'] ?? '';
        $name = $component['name'] ?? '';

        return match (true) {
            str_contains($class, 'Controller') => ComponentType::CONTROLLER,
            str_contains($class, 'Service') => ComponentType::SERVICE,
            str_contains($class, 'Repository') => ComponentType::REPOSITORY,
            str_contains($class, 'Middleware') => ComponentType::MIDDLEWARE,
            str_contains($class, 'Command') => ComponentType::COMMAND,
            str_contains($class, 'Event') => ComponentType::EVENT,
            str_contains($class, 'Exception') => ComponentType::EXCEPTION,
            str_contains($class, 'ValueObject') || str_contains($class, 'VO') => ComponentType::VALUE_OBJECT,
            str_contains($class, 'Interface') => ComponentType::INTERFACE,
            default => ComponentType::UNKNOWN
        };
    }

    /**
     * Determine dependency type
     */
    private function determineDependencyType(array $from, string $to): string
    {
        // Analyze how the dependency is used
        return match (true) {
            $this->isConstructorDependency($from, $to) => 'constructor',
            $this->isMethodDependency($from, $to) => 'method',
            $this->isAttributeDependency($from, $to) => 'attribute',
            default => 'unknown'
        };
    }

    /**
     * Calculate dependency strength (0.0 - 1.0)
     */
    private function calculateDependencyStrength(array $from, string $to): float
    {
        $strength = 0.0;

        // Constructor dependency = highest strength
        if ($this->isConstructorDependency($from, $to)) {
            $strength += 0.5;
        }

        // Multiple usage points increase strength
        $usageCount = $this->countUsages($from, $to);
        $strength += min(0.4, $usageCount * 0.1);

        // Interface dependency = lower strength than concrete dependency
        if ($this->isInterfaceDependency($to)) {
            $strength *= 0.8;
        }

        return min(1.0, $strength);
    }

    /**
     * Calculate component weight for graph layout
     */
    private function calculateComponentWeight(array $component): float
    {
        $weight = 1.0;

        // Controllers and services have higher weight
        $type = $this->determineComponentType($component);
        $weight *= match ($type) {
            ComponentType::CONTROLLER => 1.5,
            ComponentType::SERVICE => 1.3,
            ComponentType::REPOSITORY => 1.2,
            ComponentType::MIDDLEWARE => 1.1,
            default => 1.0
        };

        // Components with many dependencies have higher weight
        $dependencyCount = count($this->extractDependencies($component));
        $weight += $dependencyCount * 0.1;

        return $weight;
    }

    /**
     * Detect circular dependencies using DFS
     */
    private function detectCycles(DependencyGraph $graph): array
    {
        $visited = [];
        $recursionStack = [];
        $cycles = [];

        foreach ($graph->getNodes() as $node) {
            if (! isset($visited[$node->id])) {
                $this->dfsDetectCycle($graph, $node->id, $visited, $recursionStack, $cycles, []);
            }
        }

        return $cycles;
    }

    /**
     * DFS helper for cycle detection
     */
    private function dfsDetectCycle(
        DependencyGraph $graph,
        string $nodeId,
        array &$visited,
        array &$recursionStack,
        array &$cycles,
        array $path
    ): bool {
        $visited[$nodeId] = true;
        $recursionStack[$nodeId] = true;
        $path[] = $nodeId;

        foreach ($graph->getEdgesFrom($nodeId) as $edge) {
            $targetId = $edge->to;

            if (! isset($visited[$targetId])) {
                if ($this->dfsDetectCycle($graph, $targetId, $visited, $recursionStack, $cycles, $path)) {
                    return true;
                }
            } elseif (isset($recursionStack[$targetId]) && $recursionStack[$targetId]) {
                // Found cycle
                $cycleStart = array_search($targetId, $path);
                $cycles[] = array_slice($path, $cycleStart);

                return true;
            }
        }

        $recursionStack[$nodeId] = false;

        return false;
    }

    /**
     * Calculate dependency depths
     */
    private function calculateDepths(DependencyGraph $graph): array
    {
        $depths = [];
        $visited = [];

        foreach ($graph->getNodes() as $node) {
            if (! isset($visited[$node->id])) {
                $this->calculateNodeDepth($graph, $node->id, $depths, $visited, []);
            }
        }

        return $depths;
    }

    /**
     * Calculate depth for a single node
     */
    private function calculateNodeDepth(
        DependencyGraph $graph,
        string $nodeId,
        array &$depths,
        array &$visited,
        array $path
    ): int {
        if (isset($depths[$nodeId])) {
            return $depths[$nodeId];
        }

        if (in_array($nodeId, $path)) {
            // Circular dependency detected, return arbitrary depth
            return 0;
        }

        $visited[$nodeId] = true;
        $path[] = $nodeId;

        $maxDepth = 0;
        foreach ($graph->getEdgesFrom($nodeId) as $edge) {
            $targetDepth = $this->calculateNodeDepth($graph, $edge->to, $depths, $visited, $path);
            $maxDepth = max($maxDepth, $targetDepth + 1);
        }

        $depths[$nodeId] = $maxDepth;

        return $maxDepth;
    }

    /**
     * Topological sort for optimal loading order
     */
    private function topologicalSort(DependencyGraph $graph): array
    {
        $inDegree = [];
        $queue = [];
        $result = [];

        // Calculate in-degrees
        foreach ($graph->getNodes() as $node) {
            $inDegree[$node->id] = 0;
        }

        foreach ($graph->getEdges() as $edge) {
            $inDegree[$edge->to]++;
        }

        // Add nodes with no incoming edges to queue
        foreach ($inDegree as $nodeId => $degree) {
            if ($degree === 0) {
                $queue[] = $nodeId;
            }
        }

        // Process queue
        while (! empty($queue)) {
            $nodeId = array_shift($queue);
            $result[] = $nodeId;

            foreach ($graph->getEdgesFrom($nodeId) as $edge) {
                $inDegree[$edge->to]--;
                if ($inDegree[$edge->to] === 0) {
                    $queue[] = $edge->to;
                }
            }
        }

        return $result;
    }

    /**
     * Helper methods for dependency analysis
     */
    private function componentExists(string $dependency, array $components): bool
    {
        foreach ($components as $component) {
            if (($component['name'] ?? $component['class'] ?? '') === $dependency) {
                return true;
            }
        }

        return false;
    }

    private function isConstructorDependency(array $component, string $dependency): bool
    {
        return in_array($dependency, $component['constructor_dependencies'] ?? []);
    }

    private function isMethodDependency(array $component, string $dependency): bool
    {
        return in_array($dependency, $component['method_dependencies'] ?? []);
    }

    private function isAttributeDependency(array $component, string $dependency): bool
    {
        foreach ($component['attributes'] ?? [] as $attribute) {
            if (in_array($dependency, $attribute['dependencies'] ?? [])) {
                return true;
            }
        }

        return false;
    }

    private function isInterfaceDependency(string $dependency): bool
    {
        return str_contains($dependency, 'Interface') || str_ends_with($dependency, 'Contract');
    }

    private function countUsages(array $component, string $dependency): int
    {
        $count = 0;

        if ($this->isConstructorDependency($component, $dependency)) {
            $count++;
        }
        if ($this->isMethodDependency($component, $dependency)) {
            $count++;
        }
        if ($this->isAttributeDependency($component, $dependency)) {
            $count++;
        }

        return $count;
    }

    private function hashComponents(array $components): string
    {
        return md5(serialize($components));
    }

    private function findStronglyConnectedComponents(DependencyGraph $graph): array
    {
        // Simplified implementation - in practice would use Tarjan's algorithm
        return [];
    }

    private function calculateImpact(DependencyGraph $graph, string $componentName): array
    {
        // Find all components that depend on the given component
        $impact = [];
        foreach ($graph->getEdges() as $edge) {
            if ($edge->to === $componentName) {
                $impact[] = $edge->from;
            }
        }

        return $impact;
    }
}
