"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PluginManager = void 0;
const merge_1 = __importDefault(require("lodash/merge"));
const path_1 = __importDefault(require("path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const walk_sync_1 = __importDefault(require("walk-sync"));
const flatMap_1 = __importDefault(require("lodash/flatMap"));
const get_1 = __importDefault(require("lodash/get"));
const includes_1 = __importDefault(require("lodash/includes"));
const isError_1 = __importDefault(require("lodash/isError"));
const logger = __importStar(require("../utils/logger"));
const Plugin_1 = require("./Plugin");
const { ignoreTags } = require('../patches');
const _ = {
    flatMap: flatMap_1.default,
    get: get_1.default,
    includes: includes_1.default,
    isError: isError_1.default,
    merge: merge_1.default,
};
const MARKBIND_PLUGIN_DIRECTORY = __dirname;
const MARKBIND_DEFAULT_PLUGIN_DIRECTORY = path_1.default.join(__dirname, 'default');
const MARKBIND_PLUGIN_PREFIX = 'markbind-plugin-';
const PROJECT_PLUGIN_FOLDER_NAME = '_markbind/plugins';
class PluginManager {
    constructor(config, plugins, pluginsContext) {
        this.config = config;
        this.plugins = {};
        /**
         * Raw array of plugin names as read from the site configuration
         */
        this.pluginsRaw = plugins;
        /**
         * Raw representation of the site configuration's plugisnContext key
         */
        this.pluginsContextRaw = pluginsContext;
        // Plugin special tags may modify this
        this.htmlBeautifyOptions = {};
        this._setup();
    }
    _setup() {
        this._collectPlugins();
        this._collectPluginTagConfigs();
    }
    /**
     * Load all plugins of the site
     */
    _collectPlugins() {
        const defaultPluginNames = (0, walk_sync_1.default)(MARKBIND_DEFAULT_PLUGIN_DIRECTORY, {
            directories: false,
            globs: [`${MARKBIND_PLUGIN_PREFIX}*.js`],
        }).map(file => path_1.default.basename(file, '.js'));
        this.pluginsRaw
            .filter(plugin => !_.includes(defaultPluginNames, plugin))
            .forEach(plugin => this._loadPlugin(plugin, false));
        const markbindPrefixRegex = new RegExp(`^${MARKBIND_PLUGIN_PREFIX}`);
        defaultPluginNames
            .filter(plugin => !_.get(this.pluginsContextRaw, [plugin.replace(markbindPrefixRegex, ''), 'off'], false))
            .forEach(plugin => this._loadPlugin(plugin, true));
    }
    /**
     * Loads a plugin
     * @param plugin name of the plugin
     * @param isDefault whether the plugin is a default plugin
     */
    _loadPlugin(plugin, isDefault) {
        try {
            // Check if already loaded
            if (this.plugins[plugin]) {
                logger.warn(`Attempted to reload ${plugin} plugin. Is there a naming conflict?`);
                return;
            }
            const pluginPath = PluginManager._getPluginPath(this.config.rootPath, plugin);
            if (isDefault && !pluginPath.startsWith(MARKBIND_DEFAULT_PLUGIN_DIRECTORY)) {
                // Users can override default plugins with their own in the project folder
                logger.warn(`Default plugin ${plugin} will be overridden`);
            }
            this.plugins[plugin] = new Plugin_1.Plugin(plugin, pluginPath, this.pluginsContextRaw[plugin], this.config.outputPath);
        }
        catch (e) {
            logger.warn(`Unable to load plugin ${plugin}, skipping...\n${e}`);
        }
    }
    /**
     * Retrieves the correct plugin path for a plugin name that exists either in (in decreasing priority):
     * - the MarkBind project's 'plugins' folder
     * - the current folder (__dirname)
     * - the 'default' subdirectory under the current folder
     * - one of the environment's valid node_modules folders, as loaded by node's require(...) method
     * @param projectRootPath root of the MarkBind project
     * @param pluginName name of the plugin
     */
    static _getPluginPath(projectRootPath, pluginName) {
        // Check in project folder
        const pluginPath = path_1.default.join(projectRootPath, PROJECT_PLUGIN_FOLDER_NAME, `${pluginName}.js`);
        if (fs_extra_1.default.existsSync(pluginPath)) {
            return pluginPath;
        }
        // Check in current (__dirname) folder
        const markbindPluginPath = path_1.default.join(MARKBIND_PLUGIN_DIRECTORY, `${pluginName}.js`);
        if (fs_extra_1.default.existsSync(markbindPluginPath)) {
            return markbindPluginPath;
        }
        // Check in default folder
        const markbindDefaultPluginPath = path_1.default.join(MARKBIND_DEFAULT_PLUGIN_DIRECTORY, `${pluginName}.js`);
        if (fs_extra_1.default.existsSync(markbindDefaultPluginPath)) {
            return markbindDefaultPluginPath;
        }
        // Check the environment's node_modules folders
        try {
            const resolvedPluginPath = require.resolve(pluginName);
            return resolvedPluginPath;
        }
        catch (err) {
            // An error may be thrown because the module is not found, or for other reasons.
            // If the error is due to MODULE_NOT_FOUND, search project's node_modules
            if (_.isError(err) && err.code === 'MODULE_NOT_FOUND') {
                return require.resolve(pluginName, { paths: [path_1.default.join(projectRootPath, 'node_modules')] });
            }
            // Re-throw all other errors
            throw err;
        }
    }
    /**
     * Collects the tag configuration of the site's plugins, and injects them into the parsers.
     */
    _collectPluginTagConfigs() {
        const specialTags = new Set(); // "non-html containing" tags parsed like <script>, <style>
        Object.values(this.plugins).forEach((plugin) => {
            const pluginTagConfig = plugin.getTagConfig();
            if (!pluginTagConfig) {
                return;
            }
            Object.entries(pluginTagConfig).forEach(([tagName, tagConfig]) => {
                if (tagConfig.isSpecial) {
                    specialTags.add(tagName.toLowerCase());
                }
            });
            _.merge(PluginManager.tagConfig, pluginTagConfig);
        });
        ignoreTags(specialTags);
        this.htmlBeautifyOptions = {
            indent_size: 2,
            content_unformatted: ['pre', 'textarea', 'script', ...specialTags],
        };
    }
    /**
     * Run the beforeSiteGenerate hooks
     */
    beforeSiteGenerate() {
        Object.values(this.plugins).forEach(plugin => plugin.executeBeforeSiteGenerate());
    }
    /**
     * Run getLinks and getScripts hooks
     */
    collectPluginPageNjkAssets(frontmatter, content, pageAsset) {
        const pluginLinksAndScripts = Object.values(this.plugins)
            .map(plugin => plugin.getPageNjkLinksAndScripts(frontmatter, content, this.config.baseUrl));
        pageAsset.pluginLinks = _.flatMap(pluginLinksAndScripts, pluginResult => pluginResult.links);
        pageAsset.pluginScripts = _.flatMap(pluginLinksAndScripts, pluginResult => pluginResult.scripts);
    }
    postRender(frontmatter, content) {
        return Object.values(this.plugins)
            .reduce((renderedContent, plugin) => plugin.postRender(frontmatter, renderedContent), content);
    }
    processNode(node) {
        Object.values(this.plugins).forEach((plugin) => {
            plugin.processNode(node, this.config);
        });
    }
    postProcessNode(node) {
        Object.values(this.plugins).forEach((plugin) => {
            plugin.postProcessNode(node, this.config);
        });
    }
}
exports.PluginManager = PluginManager;
// Static property for easy access in linkProcessor
PluginManager.tagConfig = {};
//# sourceMappingURL=PluginManager.js.map