<?php

declare(strict_types=1);

namespace App\Framework\Core;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Discovery\FileVisitor;
use App\Framework\Discovery\ReflectionAwareVisitor;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Reflection\WrappedReflectionClass;

/**
 * Visitor zum Auffinden von Interface-Implementierungen
 */
final class InterfaceImplementationVisitor implements FileVisitor, ReflectionAwareVisitor
{
    /** @var array<string, mixed> */
    private array $interfaces = [];

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

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

    /**
     * @param array<int, string> $targetInterfaces Die zu suchenden Interfaces
     */
    public function __construct(
        private readonly array $targetInterfaces = []
    ) {
    }

    public function onScanStart(): void
    {
        $this->implementations = [];
        $this->implementationsByClass = [];
    }

    public function onIncrementalScanStart(): void
    {
        // Bei inkrementellem Scan behalten wir vorhandene Implementierungen
        // und aktualisieren sie mit neuen Daten
    }

    public function onIncrementalScanComplete(): void
    {
        // Sortiere nach inkrementellem Scan
        $this->sortImplementations();
    }

    public function visitClass(ClassName $className, FilePath $filePath): void
    {
        // This method is kept for backward compatibility but should not be used
        // when ReflectionProvider is available. The UnifiedDiscoveryService will
        // prefer visitClassWithReflection() when possible.
    }

    public function visitClassWithReflection(ClassName $className, FilePath $filePath, WrappedReflectionClass $reflection): void
    {
        try {
            // Skip abstract classes and interfaces - only process instantiable classes
            if (! $reflection->isInstantiable()) {
                return;
            }

            // Alte Einträge für diese Klasse entfernen (wichtig für inkrementelle Scans)
            $classNameStr = $className->getFullyQualified();
            $this->removeImplementationsForClass($classNameStr);

            // Prüfe, ob die Klasse eines der Ziel-Interfaces implementiert
            foreach ($this->targetInterfaces as $interface) {
                if ($reflection->implementsInterface($interface)) {
                    if (! isset($this->implementations[$interface])) {
                        $this->implementations[$interface] = [];
                    }

                    // Prüfe auf Duplikate
                    if (! in_array($classNameStr, $this->implementations[$interface])) {
                        $this->implementations[$interface][] = $classNameStr;

                        // Umgekehrten Index für schnelleren Zugriff aufbauen
                        if (! isset($this->implementationsByClass[$classNameStr])) {
                            $this->implementationsByClass[$classNameStr] = [];
                        }
                        $this->implementationsByClass[$classNameStr][] = $interface;
                    }
                }
            }
        } catch (\Throwable $e) {
            // Silent failure - skip this class and continue with discovery
        }
    }

    /**
     * Entfernt alle Interface-Implementierungen für eine bestimmte Klasse
     */
    private function removeImplementationsForClass(string $className): void
    {
        if (! isset($this->implementationsByClass[$className])) {
            return;
        }

        foreach ($this->implementationsByClass[$className] as $interface) {
            if (isset($this->implementations[$interface])) {
                $key = array_search($className, $this->implementations[$interface]);
                if ($key !== false) {
                    unset($this->implementations[$interface][$key]);
                    // Array-Indizes neu numerieren
                    $this->implementations[$interface] = array_values($this->implementations[$interface]);
                }
            }
        }

        unset($this->implementationsByClass[$className]);
    }

    public function onScanComplete(): void
    {
        // Sortiere Implementierungen für konsistente Reihenfolge
        $this->sortImplementations();
    }

    /**
     * Sortiert alle Implementierungen für konsistente Ergebnisse
     */
    private function sortImplementations(): void
    {
        foreach ($this->implementations as &$classes) {
            sort($classes);
        }
    }

    public function loadFromCache(Cache $cache): void
    {
        $cacheItem = $cache->get($this->getCacheKey());
        if ($cacheItem->isHit) {
            if (is_array($cacheItem->value) && isset($cacheItem->value['implementations'], $cacheItem->value['byClass'])) {
                $this->implementations = $cacheItem->value['implementations'];
                $this->implementationsByClass = $cacheItem->value['byClass'];
            }
        }
    }

    public function getCacheKey(): CacheKey
    {
        return CacheKey::fromString('interface_implementations');
    }

    /**
     * @return array<string, mixed>
     */
    public function getCacheableData(): array
    {
        return [
            'implementations' => $this->implementations,
            'byClass' => $this->implementationsByClass,
        ];
    }

    /**
     * Gibt alle Implementierungen eines Interfaces zurück
     * @return array<int, string>
     */
    public function getImplementations(string $interface): array
    {
        return $this->implementations[$interface] ?? [];
    }

    /**
     * Prüft, ob eine Klasse ein bestimmtes Interface implementiert
     */
    public function doesImplement(string $className, string $interface): bool
    {
        return isset($this->implementationsByClass[$className]) &&
               in_array($interface, $this->implementationsByClass[$className]);
    }

    /**
     * Gibt alle Interfaces zurück, die eine Klasse implementiert
     * @return array<int, string>
     */
    public function getClassInterfaces(string $className): array
    {
        return $this->implementationsByClass[$className] ?? [];
    }

    /**
     * Gibt alle gefundenen Implementierungen zurück
     * @return array<string, array<int, string>>
     */
    public function getAllImplementations(): array
    {
        return $this->implementations;
    }
}
