<?php

declare(strict_types=1);

namespace Fennec\Autoload;

/**
 * Autoloader maison à mapping de préfixes de namespace vers répertoires.
 *
 * Ne contient aucun mapping en dur : les correspondances concrètes (ex. Fennec\, App\)
 * sont enregistrées par le point d'entrée du projet qui l'utilise via addNamespace(),
 * pas par le framework lui-même, afin que fennec/ reste réutilisable tel quel.
 */
final class Autoloader
{
    /** @var array<string, string> Préfixe de namespace (avec backslash final) => répertoire de base absolu */
    private array $prefixes = [];

    /**
     * Enregistre un préfixe de namespace et son répertoire de base associé.
     *
     * @param string $prefix Préfixe de namespace (ex. "Fennec\\"), avec ou sans backslash final.
     * @param string $baseDirectory Chemin du répertoire racine correspondant à ce préfixe.
     *
     * @throws \InvalidArgumentException Si $baseDirectory n'existe pas ou n'est pas un répertoire.
     */
    public function addNamespace(string $prefix, string $baseDirectory): void
    {
        if (!is_dir($baseDirectory)) {
            throw new \InvalidArgumentException(sprintf(
                'Base directory "%s" does not exist for namespace prefix "%s".',
                $baseDirectory,
                $prefix,
            ));
        }

        $normalizedPrefix = rtrim($prefix, '\\') . '\\';
        $this->prefixes[$normalizedPrefix] = rtrim($baseDirectory, '/\\');
    }

    /**
     * Enregistre cet autoloader auprès du moteur PHP.
     */
    public function register(): void
    {
        spl_autoload_register($this->loadClass(...));
    }

    /**
     * Callback d'autoload appelé par le moteur PHP pour toute classe non encore chargée.
     *
     * @internal Appelée par spl_autoload_register, pas destinée à un appel direct depuis App\.
     *
     * @return bool True si le fichier correspondant a été trouvé et chargé, false si aucun
     *              préfixe enregistré ne correspond au nom de classe.
     *
     * @throws ClassNotFoundException Si un préfixe correspond mais que le fichier est introuvable.
     */
    public function loadClass(string $className): bool
    {
        $filePath = $this->resolveFilePath($className);

        if ($filePath === null) {
            return false;
        }

        if (!is_file($filePath)) {
            throw new ClassNotFoundException($className, $filePath);
        }

        require $filePath;

        return true;
    }

    /**
     * Résout le nom de classe vers un chemin de fichier via le préfixe le plus long correspondant.
     */
    private function resolveFilePath(string $className): ?string
    {
        $matchedPrefix = '';

        foreach ($this->prefixes as $prefix => $baseDirectory) {
            if (str_starts_with($className, $prefix) && strlen($prefix) > strlen($matchedPrefix)) {
                $matchedPrefix = $prefix;
            }
        }

        if ($matchedPrefix === '') {
            return null;
        }

        $relativeClassName = substr($className, strlen($matchedPrefix));
        $relativePath = str_replace('\\', DIRECTORY_SEPARATOR, $relativeClassName) . '.php';

        return $this->prefixes[$matchedPrefix] . DIRECTORY_SEPARATOR . $relativePath;
    }
}
