<?php

namespace App\Support;

use Illuminate\Foundation\Application;

/**
 * ModuleLoader
 *
 * Discovers and registers all enabled modules found under the /modules directory.
 *
 * Each module must have a module.json manifest in its root directory.
 * The manifest's "provider" key must point to a fully-qualified class name
 * that extends AbstractModuleServiceProvider.
 *
 * A custom PSR-4 autoloader is registered for the Modules\ namespace so that
 * modules uploaded via the admin UI work immediately — no composer dump-autoload
 * is ever required.
 *
 * Usage (called once from AppServiceProvider::register):
 *   ModuleLoader::registerAll($this->app);
 */
class ModuleLoader
{
    /**
     * Scan /modules for valid module manifests and return their metadata.
     *
     * @return array<int, array{slug: string, name: string, provider: string, enabled: bool, path: string, ...}>
     */
    public static function discover(): array
    {
        $modulesPath = base_path('modules');

        if (!is_dir($modulesPath)) {
            return [];
        }

        $modules = [];

        foreach (glob($modulesPath . '/*/module.json') ?: [] as $manifestPath) {
            $meta = json_decode(file_get_contents($manifestPath), true);

            if (!is_array($meta)) {
                continue;
            }

            $meta['path'] = dirname($manifestPath);
            $modules[]    = $meta;
        }

        return $modules;
    }

    /**
     * Register all enabled modules with the application container.
     *
     * This also registers the Modules\ PSR-4 autoloader so that module classes
     * are resolvable without running composer dump-autoload.
     */
    public static function registerAll(Application $app): void
    {
        // Register our runtime autoloader FIRST so that provider classes can be
        // resolved in the loop below, even on a fresh upload with no composer cache.
        static::registerAutoloader();

        foreach (self::discover() as $module) {
            if (($module['enabled'] ?? true) === false) {
                continue;
            }

            $provider = $module['provider'] ?? null;

            if (!$provider) {
                continue;
            }

            if (!class_exists($provider)) {
                // The autoloader ran but still can't find the class — likely a
                // mismatch between module.json "provider" and the actual file path.
                \Illuminate\Support\Facades\Log::warning(
                    "[ModuleLoader] Provider class \"{$provider}\" not found for module \"{$module['name']}\". " .
                    "Check that the class namespace in ModuleServiceProvider.php matches module.json."
                );
                continue;
            }

            $app->register($provider);
        }
    }

    /**
     * Register a PSR-4 autoloader for the Modules\ namespace.
     *
     * Maps  Modules\{ModuleName}\{Sub\Class}
     *   →   modules/{ModuleName}/{Sub/Class}.php
     *
     * This is called automatically by registerAll(), but can also be called
     * standalone if you need class resolution before registerAll() runs.
     *
     * Safe to call multiple times — the autoloader is only registered once.
     */
    public static function registerAutoloader(): void
    {
        static $registered = false;

        if ($registered) {
            return;
        }

        spl_autoload_register(function (string $class): void {
            if (!str_starts_with($class, 'Modules\\')) {
                return;
            }

            // Strip the "Modules\" prefix (8 characters) and convert namespace
            // separators to directory separators.
            $relative = str_replace('\\', '/', substr($class, 8));
            $path     = base_path('modules/' . $relative . '.php');

            if (file_exists($path)) {
                require_once $path;
            }
        });

        $registered = true;
    }

    /**
     * Find a single module's metadata by its slug.
     *
     * @return array|null
     */
    public static function findBySlug(string $slug): ?array
    {
        foreach (self::discover() as $module) {
            if (($module['slug'] ?? '') === $slug) {
                return $module;
            }
        }

        return null;
    }
}
