<?php

declare(strict_types=1);

namespace App\Framework\Database\TypeCaster;

use App\Framework\Database\Exception\DatabaseException;

final class TypeCasterRegistry
{
    /** @var array<string, TypeCasterInterface> */
    private array $casters = [];

    /** @var array<string, TypeCasterInterface> Mapping von Typ zu Caster */
    private array $typeToCaster = [];

    private bool $discovered = false;

    public function __construct()
    {
        $this->discoverCasters();
    }

    public function register(string $casterClass, TypeCasterInterface $caster): void
    {
        $this->casters[$casterClass] = $caster;
        $this->buildTypeMapping();
    }

    public function get(string $casterClass): TypeCasterInterface
    {
        if (!isset($this->casters[$casterClass])) {
            // Automatisches Laden wenn Caster noch nicht registriert
            if (class_exists($casterClass) && is_subclass_of($casterClass, TypeCasterInterface::class)) {
                $this->casters[$casterClass] = new $casterClass();
                $this->buildTypeMapping();
            } else {
                throw new DatabaseException("Type caster {$casterClass} not found or invalid");
            }
        }

        return $this->casters[$casterClass];
    }

    public function has(string $casterClass): bool
    {
        return isset($this->casters[$casterClass]) ||
               (class_exists($casterClass) && is_subclass_of($casterClass, TypeCasterInterface::class));
    }

    /**
     * Findet automatisch den passenden Caster für einen Typ
     */
    public function getCasterForType(string $type): ?TypeCasterInterface
    {
        if (!$this->discovered) {
            $this->discoverCasters();
        }

        return $this->typeToCaster[$type] ?? null;
    }

    /**
     * Entdeckt automatisch alle verfügbaren TypeCaster
     */
    private function discoverCasters(): void
    {
        $casterNamespace = 'App\\Framework\\Database\\TypeCaster\\';
        $casterPath = __DIR__;

        // Alle PHP-Dateien im TypeCaster-Verzeichnis scannen
        $files = glob($casterPath . '/*.php');

        foreach ($files as $file) {
            $className = pathinfo($file, PATHINFO_FILENAME);
            $fullClassName = $casterNamespace . $className;

            // Nur echte Caster-Klassen laden, nicht das Interface
            if ($className !== 'TypeCasterInterface' &&
                class_exists($fullClassName) &&
                is_subclass_of($fullClassName, TypeCasterInterface::class) &&
                !new \ReflectionClass($fullClassName)->isAbstract()) {

                try {
                    $this->casters[$fullClassName] = new $fullClassName();
                } catch (\Exception $e) {
                    // Caster konnte nicht instanziiert werden, überspringen
                }
            }
        }

        $this->buildTypeMapping();
        $this->discovered = true;
    }

    /**
     * Baut die Typ-zu-Caster-Zuordnung auf
     */
    private function buildTypeMapping(): void
    {
        $this->typeToCaster = [];

        foreach ($this->casters as $caster) {
            // Alle bekannten Typen testen
            $typesToTest = $this->getAllKnownTypes();

            foreach ($typesToTest as $type) {
                if ($caster->supports($type)) {
                    $this->typeToCaster[$type] = $caster;
                }
            }
        }
    }

    /**
     * Gibt alle bekannten Typen zurück, die getestet werden sollen
     */
    private function getAllKnownTypes(): array
    {
        $types = [
            // Primitive Typen
            'int', 'float', 'string', 'bool', 'array', 'object',
            // Häufige Value Objects (dynamisch erweitert)
        ];

        // Alle existierenden Value Object Klassen hinzufügen
        $valueObjectsPath = __DIR__ . '/../../../Domain/ValueObjects';
        if (is_dir($valueObjectsPath)) {
            $files = glob($valueObjectsPath . '/*.php');
            foreach ($files as $file) {
                $className = pathinfo($file, PATHINFO_FILENAME);
                $fullClassName = 'App\\Domain\\ValueObjects\\' . $className;
                if (class_exists($fullClassName)) {
                    $types[] = $fullClassName;
                }
            }
        }

        return $types;
    }
}
