<?php

/**
 * Завантажувач системних модулів
 *
 * @package Flowaxy\Core\System
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Core\System;

use Flowaxy\Core\System\Base\BaseModule;
use Flowaxy\Core\System\PathResolver;
use Flowaxy\Support\Facades\Hooks;
use Flowaxy\Support\Facades\Log;
use Flowaxy\Support\Helpers\ClassLoaderHelper;
use Flowaxy\Support\Helpers\FileHelper;
use Error;
use Exception;
use Throwable;

use function array_map;
use function basename;
use function class_exists;
use function glob;
use function in_array;
use function method_exists;

class ModuleLoader
{
    private static array $loadedModules = [];
    private static array $moduleDirs = [];
    private static bool $initialized = false;

    /**
     * Ініціалізація завантажувача
     * Завантажує тільки критично важливі модулі, інші завантажуються за вимогою
     */
    public static function init(): void
    {
        if (self::$initialized) {
            return; // Вже ініціалізовано
        }

        // Встановлюємо директорії модулів (системні та зовнішні)
        $flowaxyRoot = PathResolver::flowaxy();
        $modulesRoot = PathResolver::modules();
        $externalModulesRoot = PathResolver::externalModules();
        self::$moduleDirs = [
            $flowaxyRoot . DS . 'Support' . DS . 'Managers' . DS,
            $modulesRoot . DS,
            $externalModulesRoot . DS,
        ];

        // Завантажуємо тільки критично важливі модулі, які потрібні для роботи системи
        $criticalModules = ['PluginManager', 'ThemeManager', 'SettingsManager']; // Модулі, які потрібно завантажити одразу

        foreach ($criticalModules as $moduleName) {
            self::loadModule($moduleName);
        }

        self::$initialized = true;
    }

    /**
     * Ліниве завантаження модуля за вимогою
     *
     * @param string $moduleName Ім'я модуля
     * @return BaseModule|null
     */
    public static function loadModule(string $moduleName): ?BaseModule
    {
        // Якщо модуль вже завантажено, повертаємо його
        if (isset(self::$loadedModules[$moduleName])) {
            return self::$loadedModules[$moduleName];
        }

        // Пропускаємо службові файли та класи, які не є модулями
        $skipModules = [
            'loader',
            'compatibility',
            'Config',
            'PluginLoader', // Namespaced class, not a global module
            'PluginManager', // Not a BaseModule, managed separately
            'StorageFactory', // Factory class, not a BaseModule
        ];
        if (in_array($moduleName, $skipModules, true)) {
            return null;
        }

        // Перевіряємо, що директорія модулів визначена
        if (empty(self::$moduleDirs)) {
            $flowaxyRoot = PathResolver::flowaxy();
            $modulesRoot = PathResolver::modules();
            $externalModulesRoot = PathResolver::externalModules();
            self::$moduleDirs = [
                $flowaxyRoot . DS . 'Support' . DS . 'Managers' . DS,
                $modulesRoot . DS,
                $externalModulesRoot . DS,
            ];
        }

        $moduleFile = null;
        foreach (self::$moduleDirs as $dir) {
            // Спочатку пробуємо прямий шлях (для Support/Managers/)
            $candidate = $dir . $moduleName . '.php';
            if (FileHelper::exists($candidate)) {
                $moduleFile = $candidate;
                break;
            }

            // Потім пробуємо вкладену структуру (для Modules/ModuleName/ModuleName.php)
            $nestedCandidate = $dir . $moduleName . DS . $moduleName . '.php';
            if (FileHelper::exists($nestedCandidate)) {
                $moduleFile = $nestedCandidate;
                break;
            }
        }

        // Перевіряємо існування файлу модуля
        if ($moduleFile === null) {
            $paths = array_map(fn ($dir) => $dir . $moduleName . '.php', self::$moduleDirs);
            try {
                Log::Warning('ModuleLoader: Файл модуля не знайдено', [
                    'module' => $moduleName,
                    'paths' => $paths,
                ]);
            } catch (Throwable $e) {
                // Ignore logging errors
            }

            return null;
        }

        // Завантажуємо модуль
        return self::loadModuleFile($moduleFile, $moduleName);
    }

    /**
     * Завантаження всіх модулів (для сумісності та відлагодження)
     */
    private static function loadAllModules(): void
    {
        foreach (self::$moduleDirs as $dir) {
            $modules = glob($dir . '/*.php');

            if ($modules === false) {
                continue;
            }

            foreach ($modules as $moduleFile) {
                $moduleName = basename($moduleFile, '.php');

                // Пропускаємо службові файли та вже завантажені модулі
                if ($moduleName === 'loader' || $moduleName === 'compatibility' || isset(self::$loadedModules[$moduleName])) {
                    continue;
                }

                self::loadModuleFile($moduleFile, $moduleName);
            }
        }
    }

    /**
     * Завантаження файлу модуля
     */
    private static function loadModuleFile(string $moduleFile, string $moduleName): ?BaseModule
    {
        try {
            // Переконуємося, що BaseModule завантажено (автозавантажувач має завантажити)
            ClassLoaderHelper::ensureLoaded('Flowaxy\Core\System\Base\BaseModule');

            // Переконуємося, що Cache завантажено, щоб функція cache_remember() була доступна
            ClassLoaderHelper::ensureLoaded('Flowaxy\Infrastructure\Cache\Cache');

            require_once $moduleFile;

            // Перевіряємо, що клас існує (можливо, він знаходиться в namespace)
            // Спочатку перевіряємо модулі з Modules/
            $modulesClassName = 'Flowaxy\Modules\\' . $moduleName . '\\' . $moduleName;
            $managersClassName = 'Flowaxy\Support\Managers\\' . $moduleName;

            $className = null;
            if (class_exists($modulesClassName)) {
                $className = $modulesClassName;
            } elseif (class_exists($managersClassName)) {
                $className = $managersClassName;
            } elseif (class_exists($moduleName)) {
                $className = $moduleName;
            }

            if ($className === null) {
                // Можливо, клас знаходиться в namespace - перевіряємо файл
                try {
                    Log::Warning('ModuleLoader: Клас модуля не знайдено після завантаження файлу', [
                        'module' => $moduleName,
                        'file' => $moduleFile,
                        'tried' => [$modulesClassName, $managersClassName, $moduleName],
                    ]);
                } catch (Throwable $e) {
                    // Ignore logging errors
                }
                return null;
            }

            // Перевіряємо, чи клас має метод getInstance() перед викликом
            if (! method_exists($className, 'getInstance')) {
                try {
                    Log::Warning('ModuleLoader: Клас модуля не має методу getInstance()', [
                        'module' => $moduleName,
                        'file' => $moduleFile,
                    ]);
                } catch (Throwable $e) {
                    // Ignore logging errors
                }
                return null;
            }

            try {
                $module = $className::getInstance();
            } catch (Throwable $e) {
                try {
                    Log::Error('ModuleLoader: Помилка виклику getInstance() на модулі', [
                        'module' => $moduleName,
                        'file' => $moduleFile,
                        'error' => $e->getMessage(),
                        'exception' => $e,
                    ]);
                } catch (Throwable $logError) {
                    // Ignore logging errors
                }
                return null;
            }

            // Перевіряємо, що модуль наслідується від BaseModule
            if (! ($module instanceof BaseModule)) {
                try {
                    Log::Warning('ModuleLoader: Модуль не наслідується від BaseModule', [
                        'module' => $moduleName,
                        'file' => $moduleFile,
                    ]);
                } catch (Throwable $e) {
                    // Ignore logging errors
                }
                return null;
            }

            // Реєструємо хуки модуля
            if (method_exists($module, 'registerHooks')) {
                $module->registerHooks();
            }

            self::$loadedModules[$moduleName] = $module;

            // Логуємо завантаження модуля через хук
            if (class_exists(Hooks::class) && method_exists(Hooks::class, 'dispatch')) {
                \Flowaxy\Support\Helpers\ExceptionHandler::safeExecute(
                    fn() => Hooks::dispatch('module_loaded', $moduleName),
                    null,
                    null,
                    []
                );
            }

            return $module;
        } catch (Exception | Error $e) {
            \Flowaxy\Support\Helpers\ExceptionHandler::logError(
                'ModuleLoader: Помилка завантаження модуля',
                $e,
                [
                    'module' => $moduleName,
                    'file' => $moduleFile,
                ]
            );

            if (class_exists(Hooks::class) && method_exists(Hooks::class, 'dispatch')) {
                \Flowaxy\Support\Helpers\ExceptionHandler::safeExecute(
                    fn() => Hooks::dispatch('module_error', [
                        'module' => $moduleName,
                        'message' => $e->getMessage(),
                        'file' => $moduleFile,
                    ]),
                    null,
                    null,
                    []
                );
            }

            return null;
        }
    }

    /**
     * Отримання завантаженого модуля
     *
     * @param string $moduleName Ім'я модуля
     * @return BaseModule|null
     */
    public static function getModule(string $moduleName): ?BaseModule
    {
        return self::$loadedModules[$moduleName] ?? null;
    }

    /**
     * Отримання списку всіх завантажених модулів
     *
     * @param bool $loadAll Якщо true, завантажує всі модулі (для відлагодження)
     * @return array<string, BaseModule>
     */
    public static function getLoadedModules(bool $loadAll = false): array
    {
        // Якщо запрошено завантаження всіх модулів (для відлагодження/адмінки)
        if ($loadAll && self::$initialized) {
            self::loadAllModules();
        }

        return self::$loadedModules;
    }

    /**
     * Перевірка, чи завантажено модуль
     *
     * @param string $moduleName Ім'я модуля
     * @return bool
     */
    public static function isModuleLoaded(string $moduleName): bool
    {
        return isset(self::$loadedModules[$moduleName]);
    }
}
