<?php
declare(strict_types=1);

namespace App\Framework\Core;

use App\Framework\Cache\Cache;
use App\Framework\Discovery\FileVisitor;

/**
 * Visitor zum Verarbeiten von Attributen in Klassen
 */
final class AttributeMappingVisitor implements FileVisitor
{
    private array $attributeMappers = [];
    private array $mappedAttributes = [];
    private array $mapperByClass = [];
    private array $processingStats = [];

    /**
     * @param array $attributeMappers Array von AttributeMapper-Instanzen
     */
    public function __construct(array $attributeMappers = [])
    {
        $this->attributeMappers = $attributeMappers;
        $this->buildMapperIndex();
    }

    /**
     * Erstellt einen Index der Mapper nach Attributklasse für schnelleren Zugriff
     */
    private function buildMapperIndex(): void
    {
        $this->mapperByClass = [];
        foreach ($this->attributeMappers as $mapper) {
            $attributeClass = $mapper->getAttributeClass();
            $this->mapperByClass[$attributeClass] = $mapper;
        }
    }

    public function onScanStart(): void
    {
        $this->mappedAttributes = [];
        $this->processingStats = [
            'total_classes' => 0,
            'classes_with_attributes' => 0,
            'total_attributes' => 0,
            'processed_attributes' => 0,
            'skipped_reflectors' => 0
        ];
    }

    public function onIncrementalScanStart(): void
    {
        // Bei inkrementellem Scan behalten wir die vorhandenen Mappings bei
        $this->processingStats = [
            'total_classes' => 0,
            'classes_with_attributes' => 0,
            'total_attributes' => 0,
            'processed_attributes' => 0,
            'skipped_reflectors' => 0
        ];
    }

    public function onIncrementalScanComplete(): void
    {
        // Logging der Verarbeitungsstatistiken für inkrementellen Scan
        error_log("Inkrementelle Attribut-Verarbeitung abgeschlossen: " . json_encode($this->processingStats));
    }

    public function visitClass(string $className, string $filePath): void
    {
        if (!class_exists($className)) {
            return;
        }

        $this->processingStats['total_classes']++;
        $hasAttributes = false;

        try {
            $reflection = new \ReflectionClass($className);

            // Schnelle Vorprüfung: Hat die Klasse überhaupt relevante Attribute?
            $relevantAttributes = $this->hasRelevantAttributes($reflection);
            if (!$relevantAttributes) {
                return;
            }

            $hasAttributes = true;
            $this->processingStats['classes_with_attributes']++;

            // Verarbeite alle Attribute auf Klassenebene
            $this->processAttributes($reflection->getAttributes(), $reflection);

            // Verarbeite alle Methoden
            foreach ($reflection->getMethods() as $method) {
                // Schnelle Vorprüfung für jede Methode
                if ($this->hasRelevantAttributes($method)) {
                    $this->processAttributes($method->getAttributes(), $method);
                }
            }

            // Verarbeite alle Eigenschaften
            foreach ($reflection->getProperties() as $property) {
                // Schnelle Vorprüfung für jede Eigenschaft
                if ($this->hasRelevantAttributes($property)) {
                    $this->processAttributes($property->getAttributes(), $property);
                }
            }
        } catch (\Throwable $e) {
            // Fehler beim Verarbeiten der Klasse protokollieren
            error_log("Fehler bei der Attributverarbeitung von {$className}: " . $e->getMessage());
        }
    }

    /**
     * Schnelle Vorprüfung, ob ein Reflector relevante Attribute hat
     */
    private function hasRelevantAttributes(\Reflector $reflector): bool
    {
        if (method_exists($reflector, 'getAttributes')) {
            $attributes = $reflector->getAttributes();
            $this->processingStats['total_attributes'] += count($attributes);

            if (empty($attributes)) {
                return false;
            }

            foreach ($attributes as $attribute) {
                $attributeClass = $attribute->getName();
                if (isset($this->mapperByClass[$attributeClass])) {
                    $mapper = $this->mapperByClass[$attributeClass];
                    if ($mapper->canProcess($reflector)) {
                        return true;
                    } else {
                        $this->processingStats['skipped_reflectors']++;
                    }
                }
            }
        }

        return false;
    }

    private function processAttributes(array $attributes, \Reflector $reflector): void
    {
        foreach ($attributes as $attribute) {
            $attributeClass = $attribute->getName();

            // Direkter Zugriff auf den Mapper über den Index
            if (isset($this->mapperByClass[$attributeClass])) {
                $mapper = $this->mapperByClass[$attributeClass];

                // Zusätzliche Prüfung, ob der Mapper diesen Reflector verarbeiten kann
                if ($mapper->canProcess($reflector)) {
                    try {
                        $attributeInstance = $attribute->newInstance();
                        $mapper->map($attributeInstance, $reflector);
                        $this->processingStats['processed_attributes']++;

                        // Speichere für Caching
                        if (!isset($this->mappedAttributes[$attributeClass])) {
                            $this->mappedAttributes[$attributeClass] = [];
                        }

                        // Serialisierbare Repräsentation des Reflectors
                        $reflectorKey = $this->serializeReflector($reflector);

                        $this->mappedAttributes[$attributeClass][] = [
                            'attribute' => $attributeInstance,
                            'reflector' => $reflectorKey,
                            'metadata' => $mapper->getAttributeMetadata()
                        ];
                    } catch (\Throwable $e) {
                        error_log("Fehler beim Verarbeiten des Attributs {$attributeClass}: " . $e->getMessage());
                    }
                }
            }
        }
    }

    /**
     * Erstellt eine serialisierbare Repräsentation eines Reflectors
     */
    private function serializeReflector(\Reflector $reflector): array
    {
        if ($reflector instanceof \ReflectionClass) {
            return [
                'type' => 'class',
                'name' => $reflector->getName()
            ];
        } elseif ($reflector instanceof \ReflectionMethod) {
            return [
                'type' => 'method',
                'class' => $reflector->getDeclaringClass()->getName(),
                'name' => $reflector->getName()
            ];
        } elseif ($reflector instanceof \ReflectionProperty) {
            return [
                'type' => 'property',
                'class' => $reflector->getDeclaringClass()->getName(),
                'name' => $reflector->getName()
            ];
        }

        return [
            'type' => 'unknown',
            'name' => method_exists($reflector, 'getName') ? $reflector->getName() : 'unknown'
        ];
    }

    public function onScanComplete(): void
    {
        // Logging der Verarbeitungsstatistiken
        error_log("Attribut-Verarbeitung abgeschlossen: " . json_encode($this->processingStats));
    }

    public function loadFromCache(Cache $cache): void
    {
        $cacheItem = $cache->get($this->getCacheKey());
        if ($cacheItem->isHit) {
            $this->mappedAttributes = $cacheItem->value;
        }
    }

    public function getCacheKey(): string
    {
        return 'attribute_mappings';
    }

    public function getCacheableData(): mixed
    {
        return $this->mappedAttributes;
    }

    /**
     * Fügt einen weiteren AttributeMapper hinzu
     */
    public function addAttributeMapper(AttributeMapper $mapper): void
    {
        $this->attributeMappers[] = $mapper;

        // Aktualisiere den Mapper-Index
        $attributeClass = $mapper->getAttributeClass();
        $this->mapperByClass[$attributeClass] = $mapper;
    }

    /**
     * Gibt alle Attribute eines bestimmten Typs zurück
     */
    public function getAttributesOfType(string $attributeClass): array
    {
        return $this->mappedAttributes[$attributeClass] ?? [];
    }

    /**
     * Gibt alle gemappten Attribute zurück
     */
    public function getAllMappedAttributes(): array
    {
        return $this->mappedAttributes;
    }

    /**
     * Gibt die Verarbeitungsstatistiken zurück
     */
    public function getProcessingStats(): array
    {
        return $this->processingStats;
    }
}
