<?php

/**
 * Load all internal libraries and setup class autoloader
 *
 * @author Andreas Gohr <andi@splitbrain.org>
 */

namespace dokuwiki;

use dokuwiki\Extension\PluginController;

return new class {
    /** @var string[] Common libraries that are always loaded */
    protected array $commonLibs = [
        'defines.php',
        'actions.php',
        'changelog.php',
        'common.php',
        'confutils.php',
        'pluginutils.php',
        'form.php',
        'html.php',
        'httputils.php',
        'infoutils.php',
        'io.php',
        'media.php',
        'pageutils.php',
        'parserutils.php',
        'search.php',
        'template.php',
        'toolbar.php',
        'utf8.php',
        'auth.php',
        'compatibility.php',
        'deprecated.php',
        'legacy.php',
    ];

    /** @var string[] Classname to file mappings */
    protected array $fixedClassNames = [
        'Diff' => 'DifferenceEngine.php',
        'UnifiedDiffFormatter' => 'DifferenceEngine.php',
        'TableDiffFormatter' => 'DifferenceEngine.php',
        'cache' => 'cache.php',
        'cache_parser' => 'cache.php',
        'cache_instructions' => 'cache.php',
        'cache_renderer' => 'cache.php',
        'JpegMeta' => 'JpegMeta.php',
        'FeedParser' => 'FeedParser.php',
        'SafeFN' => 'SafeFN.class.php',
        'Mailer' => 'Mailer.class.php',
        'Doku_Renderer' => 'parser/renderer.php',
        'Doku_Renderer_xhtml' => 'parser/xhtml.php',
        'Doku_Renderer_code' => 'parser/code.php',
        'Doku_Renderer_xhtmlsummary' => 'parser/xhtmlsummary.php',
        'Doku_Renderer_metadata' => 'parser/metadata.php'
    ];

    /**
     * Load common libs and register autoloader
     */
    public function __construct()
    {
        require_once(DOKU_INC . 'vendor/autoload.php');
        spl_autoload_register($this->autoload(...));
        $this->loadCommonLibs();
    }

    /**
     * require all the common libraries
     *
     * @return true
     */
    public function loadCommonLibs()
    {
        foreach ($this->commonLibs as $lib) {
            require_once(DOKU_INC . 'inc/' . $lib);
        }
        return true;
    }

    /**
     * spl_autoload_register callback
     *
     * @param string $className
     * @return bool
     */
    public function autoload($className)
    {
        // namespace to directory conversion
        $classPath = str_replace('\\', '/', $className);

        return $this->autoloadFixedClass($className)
            || $this->autoloadTestMockClass($classPath)
            || $this->autoloadTestClass($classPath)
            || $this->autoloadPluginClass($classPath)
            || $this->autoloadTemplateClass($classPath)
            || $this->autoloadCoreClass($classPath)
            || $this->autoloadNamedPluginClass($className);
    }

    /**
     * Check if the class is one of the fixed names
     *
     * @param string $className
     * @return bool true if the class was loaded, false otherwise
     */
    protected function autoloadFixedClass($className)
    {
        if (isset($this->fixedClassNames[$className])) {
            require($this->fixedClassNames[$className]);
            return true;
        }
        return false;
    }

    /**
     * Check if the class is a test mock class
     *
     * @param string $classPath The class name using forward slashes as namespace separators
     * @return bool true if the class was loaded, false otherwise
     */
    protected function autoloadTestMockClass($classPath)
    {
        if ($this->prefixStrip($classPath, 'dokuwiki/test/mock/')) {
            $file = DOKU_INC . '_test/mock/' . $classPath . '.php';
            if (file_exists($file)) {
                require $file;
                return true;
            }
        }
        return false;
    }

    /**
     * Check if the class is a test mock class
     *
     * @param string $classPath The class name using forward slashes as namespace separators
     * @return bool true if the class was loaded, false otherwise
     */
    protected function autoloadTestClass($classPath)
    {
        if ($this->prefixStrip($classPath, 'dokuwiki/test/')) {
            $file = DOKU_INC . '_test/tests/' . $classPath . '.php';
            if (file_exists($file)) {
                require $file;
                return true;
            }
        }
        return false;
    }

    /**
     * Check if the class is a namespaced plugin class
     *
     * @param string $classPath The class name using forward slashes as namespace separators
     * @return bool true if the class was loaded, false otherwise
     */
    protected function autoloadPluginClass($classPath)
    {
        global $plugin_controller;

        if ($this->prefixStrip($classPath, 'dokuwiki/plugin/')) {
            $classPath = str_replace('/test/', '/_test/', $classPath); // no underscore in test namespace
            $file = DOKU_PLUGIN . $classPath . '.php';
            if (file_exists($file)) {
                $plugin = substr($classPath, 0, strpos($classPath, '/'));
                // don't load disabled plugin classes (only if plugin controller is available)
                if (!defined('DOKU_UNITTEST') && $plugin_controller && plugin_isdisabled($plugin)) return false;

                try {
                    require $file;
                } catch (\Throwable $e) {
                    ErrorHandler::showExceptionMsg($e, "Error loading plugin $plugin");
                }
                return true;
            }
        }
        return false;
    }

    /**
     * Check if the class is a namespaced template class
     *
     * @param string $classPath The class name using forward slashes as namespace separators
     * @return bool true if the class was loaded, false otherwise
     */
    protected function autoloadTemplateClass($classPath)
    {
        // template namespace
        if ($this->prefixStrip($classPath, 'dokuwiki/template/')) {
            $classPath = str_replace('/test/', '/_test/', $classPath); // no underscore in test namespace
            $file = DOKU_INC . 'lib/tpl/' . $classPath . '.php';
            if (file_exists($file)) {
                $template = substr($classPath, 0, strpos($classPath, '/'));

                try {
                    require $file;
                } catch (\Throwable $e) {
                    ErrorHandler::showExceptionMsg($e, "Error loading template $template");
                }
                return true;
            }
        }
        return false;
    }

    /**
     * Check if the class is a namespaced DokuWiki core class
     *
     * @param string $classPath The class name using forward slashes as namespace separators
     * @return bool true if the class was loaded, false otherwise
     */
    protected function autoloadCoreClass($classPath)
    {
        if ($this->prefixStrip($classPath, 'dokuwiki/')) {
            $file = DOKU_INC . 'inc/' . $classPath . '.php';
            if (file_exists($file)) {
                require $file;
                return true;
            }
        }
        return false;
    }

    /**
     * Check if the class is a un-namespaced plugin class following our naming scheme
     *
     * @param string $className
     * @return bool true if the class was loaded, false otherwise
     */
    protected function autoloadNamedPluginClass($className)
    {
        global $plugin_controller;

        if (
            preg_match(
                '/^(' . implode('|', PluginController::PLUGIN_TYPES) . ')_plugin_(' .
                DOKU_PLUGIN_NAME_REGEX .
                ')(?:_([^_]+))?$/',
                $className,
                $m
            )
        ) {
            $c = ((count($m) === 4) ? "/{$m[3]}" : '');
            $plg = DOKU_PLUGIN . "{$m[2]}/{$m[1]}$c.php";
            if (file_exists($plg)) {
                // don't load disabled plugin classes (only if plugin controller is available)
                if (!defined('DOKU_UNITTEST') && $plugin_controller && plugin_isdisabled($m[2])) return false;
                try {
                    require $plg;
                } catch (\Throwable $e) {
                    ErrorHandler::showExceptionMsg($e, "Error loading plugin {$m[2]}");
                }
            }
            return true;
        }
        return false;
    }

    /**
     * Check if the given string starts with the given prefix and strip it
     *
     * @param string $string
     * @param string $prefix
     * @return bool true if the prefix was found and stripped, false otherwise
     */
    protected function prefixStrip(&$string, $prefix)
    {
        if (str_starts_with($string, $prefix)) {
            $string = substr($string, strlen($prefix));
            return true;
        }
        return false;
    }
};
