<?php

declare(strict_types=1);

namespace App\Framework\Core;

/**
 * Parser für PHP-Klassendateien ohne externe Abhängigkeiten
 */
final class ClassParser
{
    private static array $classCache = [];

    private static array $tokenCache = [];

    /**
     * Gibt alle Klassen, Interfaces und Traits in einer Datei zurück
     *
     * @param string $file Pfad zur PHP-Datei
     * @return array Namen der in der Datei enthaltenen Klassen
     */
    public static function getClassesInFile(string $file): array
    {
        // Prüfe Cache
        $cacheKey = md5($file . filemtime($file));
        if (isset(self::$classCache[$cacheKey])) {
            return self::$classCache[$cacheKey];
        }

        $classes = [];
        $namespace = '';
        $tokens = self::getTokens($file);

        if ($tokens === null) {
            return [];
        }

        $count = count($tokens);
        $i = 0;

        // Durchlaufe alle Tokens
        while ($i < $count) {
            if ($tokens[$i][0] === T_NAMESPACE) {
                // Namespace finden
                $namespace = self::parseNamespace($tokens, $i);
                #debug("Found namespace: '$namespace' in file: $file");
            } elseif ($tokens[$i][0] === T_CLASS || $tokens[$i][0] === T_INTERFACE || $tokens[$i][0] === T_TRAIT) {
                // Klasse/Interface/Trait finden
                $className = self::parseClassName($tokens, $i);
                if ($className !== null) {
                    #debug("Found class: '$className', current namespace: '$namespace'");

                    if (empty($namespace)) {
                        // Fallback: Versuche Namespace aus Dateiinhalt zu extrahieren
                        $namespace = self::extractNamespaceFromContent($file);
                        #debug("Fallback namespace from content: '$namespace'");
                    }

                    if (! empty($namespace)) {
                        $fullClassName = '\\' . trim($namespace, '\\') . '\\' . $className;
                    } else {
                        $fullClassName = '\\' . $className;
                    }

                    #debug("Final class name: '$fullClassName'");
                    $classes[] = $fullClassName;
                }
            }

            $i++;
        }

        // Ergebnis cachen
        self::$classCache[$cacheKey] = $classes;

        #debug($classes);

        return $classes;
    }

    /**
     * Parst den Namespace aus den Tokens
     */
    private static function parseNamespace(array $tokens, int &$i): string
    {
        $namespace = '';
        $originalI = $i;
        $i++; // T_NAMESPACE Token überspringen

        #debug("Starting namespace parse at token $i, T_NAMESPACE was at $originalI");

        // Debug: Zeige die nächsten paar Tokens
        for ($debugI = $i; $debugI < min($i + 10, count($tokens)); $debugI++) {
            $token = $tokens[$debugI];
        }

        while ($i < count($tokens)) {
            $token = $tokens[$i];

            if (is_array($token)) {

                if ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || $token[0] === T_NAME_QUALIFIED) {
                    $namespace .= $token[1];
                } elseif ($token[0] === T_WHITESPACE) {
                    // Whitespace ignorieren, aber weitermachen
                } else {
                    #debug("Breaking on token: " . token_name($token[0]));
                    break;
                }
            } else {
                if ($token === ';' || $token === '{') {
                    break;
                }
            }

            $i++;
        }

        $result = trim($namespace, '\\');

        #debug("Final parsed namespace: '$result'");
        return $result;
    }

    /**
     * Parst den Klassennamen aus den Tokens
     */
    private static function parseClassName(array $tokens, int &$i): ?string
    {
        $i += 1; // T_CLASS/T_INTERFACE/T_TRAIT Token überspringen

        // Suche nach dem Namen (nächstes T_STRING Token)
        while ($i < count($tokens)) {
            if ($tokens[$i][0] === T_STRING) {
                return $tokens[$i][1];
            } elseif ($tokens[$i] === '{' || $tokens[$i] === ';') {
                // Unerwartetes Ende ohne Klassennamen
                return null;
            }

            $i++;
        }

        return null;
    }

    /**
     * Liest Tokens aus einer Datei mit Caching
     */
    private static function getTokens(string $file): ?array
    {
        if (! file_exists($file)) {
            return null;
        }

        $cacheKey = md5($file . filemtime($file));
        if (isset(self::$tokenCache[$cacheKey])) {
            return self::$tokenCache[$cacheKey];
        }

        $code = file_get_contents($file);
        if ($code === false) {
            return null;
        }

        $tokens = token_get_all($code);
        self::$tokenCache[$cacheKey] = $tokens;

        // Cache-Größe begrenzen
        if (count(self::$tokenCache) > 100) {
            // Entferne ältesten Eintrag
            array_shift(self::$tokenCache);
        }

        return $tokens;
    }

    /**
     * Extrahiert Namespace aus Dateiinhalt als Fallback
     */
    private static function extractNamespaceFromContent(string $file): string
    {
        $content = file_get_contents($file);
        if ($content === false) {
            return '';
        }

        // Regex für namespace-Deklaration
        if (preg_match('/namespace\s+([^;]+);/', $content, $matches)) {
            return trim($matches[1]);
        }

        return '';
    }

    /**
     * Leert den internen Cache
     */
    public static function clearCache(): void
    {
        self::$classCache = [];
        self::$tokenCache = [];
    }

    /**
     * Extrahiert den Klassennamen (mit Namespace) aus einer PHP-Datei
     */
    public 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;
    }
}
