<?php

declare(strict_types=1);

namespace App\Framework\Core;

use App\Framework\Core\ValueObjects\ClassName;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ReflectionClass;
use Throwable;

class InterfaceImplementationLocator
{
    /**
     * Findet alle Klassen, die ein bestimmtes Interface implementieren
     *
     * @param string $directory Das Basisverzeichnis, in dem gesucht werden soll
     * @param string $interfaceName Der vollständige Name des Interfaces (mit Namespace)
     * @param bool $instantiate Gibt an, ob die gefundenen Klassen instanziiert werden sollen
     * @return array<int, object|string> Array von Instanzen oder Klassennamen
     */
    public function locate(string $directory, string $interfaceName, bool $instantiate = true): array
    {
        $implementations = [];
        $rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory));

        foreach ($rii as $file) {
            if (! $file->isFile() || $file->getExtension() !== 'php') {
                continue;
            }

            try {
                $className = $this->getClassNameFromFile($file->getPathname());

                if (! $className || ! ClassName::create($className)->exists()) {
                    continue;
                }

                $refClass = new ReflectionClass($className);

                // Überprüfen, ob die Klasse das gewünschte Interface implementiert
                if ($refClass->implementsInterface($interfaceName) &&
                    ! $refClass->isAbstract() &&
                    ! $refClass->isInterface()) {

                    if ($instantiate) {
                        // Prüfen, ob der Konstruktor Parameter hat
                        $constructor = $refClass->getConstructor();
                        if ($constructor === null || $constructor->getNumberOfRequiredParameters() === 0) {
                            $implementations[] = $refClass->newInstance();
                        } else {
                            // Bei Konstruktoren mit Pflichtparametern nur den Klassennamen zurückgeben
                            $implementations[] = $className;
                        }
                    } else {
                        $implementations[] = $className;
                    }
                }
            } catch (Throwable $e) {
                #error_log("InterfaceImplementationLocator Warnung: Fehler in Datei {$file->getPathname()}: " . $e->getMessage());
            }
        }

        return $implementations;
    }

    private function getClassNameFromFile(string $file): ?string
    {
        $contents = file_get_contents($file);
        if (
            preg_match('#namespace\s+([^;]+);#', $contents, $nsMatch)
            && preg_match('/class\s+(\w+)/', $contents, $classMatch)
        ) {
            return trim($nsMatch[1]) . '\\' . trim($classMatch[1]);
        }

        return null;
    }
}
