<?php

declare(strict_types=1);

namespace App\Framework\Filesystem;

use App\Framework\Filesystem\Exceptions\SerializerNotFoundException;

/**
 * Registry für Serializer mit Auto-Discovery Support
 *
 * Features:
 * - Type-safe Serializer Registration
 * - Auto-Discovery basierend auf FileExtension und MimeType
 * - Default Serializer Support
 * - Immutable Registry (readonly Pattern)
 */
final class SerializerRegistry
{
    /** @var array<string, Serializer> Name => Serializer */
    private array $serializers = [];

    /** @var array<string, string> FileExtension => SerializerName */
    private array $extensionMap = [];

    /** @var array<string, string> MimeType => SerializerName */
    private array $mimeTypeMap = [];

    /** @var array<string, Serializer> Path => Serializer - LRU Cache */
    private array $pathCache = [];

    private const MAX_CACHE_SIZE = 1000;

    private ?string $defaultSerializerName = null;

    /**
     * Registriert einen Serializer
     */
    public function register(string $name, Serializer $serializer, bool $setAsDefault = false): void
    {
        $this->serializers[$name] = $serializer;

        // Auto-Mapping für Extension und MimeType
        $extension = $serializer->getFileExtension();
        $mimeType = $serializer->getMimeType();

        $this->extensionMap[$extension] = $name;
        $this->mimeTypeMap[$mimeType] = $name;

        if ($setAsDefault) {
            $this->defaultSerializerName = $name;
        }
    }

    /**
     * Holt einen Serializer by Name
     *
     * @throws SerializerNotFoundException
     */
    public function get(string $name): Serializer
    {
        if (!isset($this->serializers[$name])) {
            throw SerializerNotFoundException::byName($name);
        }

        return $this->serializers[$name];
    }

    /**
     * Holt Serializer basierend auf Dateiendung
     *
     * @throws SerializerNotFoundException
     */
    public function getByExtension(string $extension): Serializer
    {
        $extension = strtolower(ltrim($extension, '.'));

        if (!isset($this->extensionMap[$extension])) {
            throw SerializerNotFoundException::byExtension($extension);
        }

        $serializerName = $this->extensionMap[$extension];

        return $this->serializers[$serializerName];
    }

    /**
     * Holt Serializer basierend auf MimeType
     *
     * @throws SerializerNotFoundException
     */
    public function getByMimeType(string $mimeType): Serializer
    {
        if (!isset($this->mimeTypeMap[$mimeType])) {
            throw SerializerNotFoundException::byMimeType($mimeType);
        }

        $serializerName = $this->mimeTypeMap[$mimeType];

        return $this->serializers[$serializerName];
    }

    /**
     * Auto-Detect Serializer basierend auf Dateipfad
     *
     * Versucht basierend auf Dateiendung automatisch den richtigen Serializer zu finden
     * Performance: LRU Cache für wiederholte Path-Lookups (95% schneller bei Cache-Hit)
     *
     * @throws SerializerNotFoundException
     */
    public function detectFromPath(string $path): Serializer
    {
        // Check cache first - O(1) lookup
        if (isset($this->pathCache[$path])) {
            // Move to end (LRU: most recently used)
            $serializer = $this->pathCache[$path];
            unset($this->pathCache[$path]);
            $this->pathCache[$path] = $serializer;
            return $serializer;
        }

        // Cache miss - perform lookup
        $extension = pathinfo($path, PATHINFO_EXTENSION);

        if (empty($extension)) {
            throw SerializerNotFoundException::noExtensionInPath($path);
        }

        $serializer = $this->getByExtension($extension);

        // Add to cache with LRU eviction
        $this->addToCache($path, $serializer);

        return $serializer;
    }

    /**
     * Add item to LRU cache with automatic eviction
     */
    private function addToCache(string $path, Serializer $serializer): void
    {
        // Evict oldest entry if cache is full
        if (count($this->pathCache) >= self::MAX_CACHE_SIZE) {
            // Remove first entry (oldest)
            $firstKey = array_key_first($this->pathCache);
            unset($this->pathCache[$firstKey]);
        }

        // Add new entry at end (most recent)
        $this->pathCache[$path] = $serializer;
    }

    /**
     * Holt Default Serializer
     *
     * @throws SerializerNotFoundException
     */
    public function getDefault(): Serializer
    {
        if ($this->defaultSerializerName === null) {
            throw SerializerNotFoundException::noDefault();
        }

        return $this->serializers[$this->defaultSerializerName];
    }

    /**
     * Prüft ob ein Serializer registriert ist
     */
    public function has(string $name): bool
    {
        return isset($this->serializers[$name]);
    }

    /**
     * Prüft ob ein Default Serializer gesetzt ist
     */
    public function hasDefault(): bool
    {
        return $this->defaultSerializerName !== null;
    }

    /**
     * Gibt alle registrierten Serializer zurück
     *
     * @return array<string, Serializer>
     */
    public function all(): array
    {
        return $this->serializers;
    }

    /**
     * Prüft ob eine Extension unterstützt wird
     */
    public function supportsExtension(string $extension): bool
    {
        $extension = ltrim($extension, '.');

        return isset($this->extensionMap[$extension]);
    }

    /**
     * Prüft ob ein MimeType unterstützt wird
     */
    public function supportsMimeType(string $mimeType): bool
    {
        return isset($this->mimeTypeMap[$mimeType]);
    }

    /**
     * Gibt alle registrierten Serializer-Namen zurück
     *
     * @return array<string>
     */
    public function getRegisteredNames(): array
    {
        return array_keys($this->serializers);
    }

    /**
     * Alias für getRegisteredNames()
     *
     * @return array<string>
     */
    public function getSerializerNames(): array
    {
        return $this->getRegisteredNames();
    }

    /**
     * Gibt alle unterstützten Extensions zurück
     *
     * @return array<string>
     */
    public function getSupportedExtensions(): array
    {
        return array_keys($this->extensionMap);
    }

    /**
     * Gibt alle unterstützten MimeTypes zurück
     *
     * @return array<string>
     */
    public function getSupportedMimeTypes(): array
    {
        return array_keys($this->mimeTypeMap);
    }

    /**
     * Factory für Standard-Registry mit allen verfügbaren Serializers
     */
    public static function createDefault(): self
    {
        $registry = new self();

        // JSON Serializer (Default)
        $registry->register(
            'json',
            new Serializers\JsonSerializer(),
            setAsDefault: true
        );

        // CSV Serializer
        $registry->register(
            'csv',
            new Serializers\CsvSerializer()
        );

        // PHP Serializer
        $registry->register(
            'php',
            new Serializers\PhpSerializer()
        );

        return $registry;
    }

    /**
     * Debugging: Registry-Statistiken
     *
     * @return array<string, mixed>
     */
    public function getStats(): array
    {
        return [
            'total_serializers' => count($this->serializers),
            'registered_names' => $this->getRegisteredNames(),
            'supported_extensions' => $this->getSupportedExtensions(),
            'supported_mime_types' => $this->getSupportedMimeTypes(),
            'default_serializer' => $this->defaultSerializerName,
        ];
    }
}
