"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const BlueBaseModuleRegistry_1 = require("./BlueBaseModuleRegistry");
const utils_1 = require("../utils");
async function inputToPlugin(plugin, BB) {
    const { value } = plugin, rest = tslib_1.__rest(plugin, ["value"]);
    const resolvedValue = await value;
    const routes = await resolveRoutes(plugin.routes || (resolvedValue && resolvedValue.routes) || [], BB);
    const final = Object.assign({ assets: {}, components: {}, defaultConfigs: {}, enabled: true, filters: {}, fonts: {}, name: 'Untitled Plugin', themes: {} }, rest, value, { routes });
    // FIXME: Fix typing hell in this project please
    return final;
}
exports.inputToPlugin = inputToPlugin;
/**
 * Creates a BlueBase plugin from input params
 * @param plugin
 */
function createPlugin(plugin) {
    const { assets, components, filters, fonts, themes, routes, value } = plugin, rest = tslib_1.__rest(plugin, ["assets", "components", "filters", "fonts", "themes", "routes", "value"]);
    return Object.assign({ categories: [], defaultConfigs: {}, enabled: true, name: 'Untitled Plugin' }, rest, { value: Object.assign({ assets: assets || {}, components: components || {}, filters: filters || {}, fonts: fonts || {}, routes, themes: themes || {} }, value) });
}
exports.createPlugin = createPlugin;
async function resolveRoutes(routes, BB) {
    // If thunk resolve it
    let finalRoutes = utils_1.resolveThunk(routes || [], BB);
    // If result is a promise, resolve it too
    if (utils_1.isPromise(finalRoutes)) {
        finalRoutes = await utils_1.getDefinitePromise(finalRoutes);
    }
    // Make sure we have an array
    return utils_1.getDefiniteArray(finalRoutes);
}
exports.resolveRoutes = resolveRoutes;
/**
 * 🔌 PluginRegistry
 */
class PluginRegistry extends BlueBaseModuleRegistry_1.BlueBaseModuleRegistry {
    /**
     * Returns a Promise that resolves a Plugin
     * @param keys
     */
    async resolve(...keys) {
        const item = this.findOne(...keys);
        if (!item) {
            throw Error(`Could not resolve any of the following plugins: [${keys.join(', ')}].`);
        }
        const input = Object.assign({}, item, { value: await item.value });
        return inputToPlugin(input, this.BB);
    }
    /**
     * Checks if a plugin is enabled
     * @param key
     */
    isEnabled(key) {
        const item = this.get(key);
        if (!item) {
            throw Error(`Could not check if plugin is enabled. Reason: No plugin registered by key "${key}".`);
        }
        return item.enabled;
    }
    /**
     * Enable a plugin
     * @param key
     */
    async enable(key) {
        const item = this.get(key);
        if (!item) {
            throw Error(`Could not enable plugin. Reason: No plugin registered by key "${key}".`);
        }
        item.enabled = true;
        this.set(key, item);
    }
    /**
     * Disable a plugin
     * @param key
     */
    async disable(key) {
        const item = this.get(key);
        if (!item) {
            throw Error(`Could not disable plugin. Reason: No plugin registered by key "${key}".`);
        }
        item.enabled = false;
        this.set(key, item);
    }
    /**
     * Checks if a config belongs to a plugin. Does so by checking 2 things:
     *
     * 1. Does the config start with 'plugin.{key}.'?
     * 2. Does the config exist in defaultConfigs property of the plugin?
     *
     * Returns true if any of the above are true, otherwise returns false
     *
     * @param key
     */
    hasConfig(key, config) {
        const plugin = this.get(key);
        if (!plugin) {
            throw Error(`Could not check config for a plugin. Reason: No plugin registered by key "${key}".`);
        }
        return (config.startsWith(`plugin.${key}.`) ||
            Object.keys(plugin.defaultConfigs).findIndex(k => k === config) >= 0);
    }
    /**
     * Creates a map of routes for each plugin
     *
     * Ignores plugins if:
     *
     * - Plugin is not enabled
     * - Plugin is not resolved
     */
    async getRouteMap(prefixPluginKey = true) {
        const pluginRoutes = {};
        const pluginRoutePathPrefix = this.BB.Configs.getValue('pluginRoutePathPrefix') || '';
        for (const [key, item] of this.entries()) {
            // Skip if pluign is not loaded, or plugin is not enabled
            if ((item.value.isAsync && !item.value.loaded) || !item.enabled) {
                continue;
            }
            // Resolve plugin
            const plugin = await inputToPlugin(item.value.module, this.BB);
            // Resolve routes, if it's a thunk
            // Put the resovled value in an array, if it's a single item
            // FIXME: Actually, inputToPlugin return value as routes prop resolved.
            // Remove typecasting in future and fix typings
            let routes = plugin.routes;
            // Skip if plugin doesn't have any routes
            if (!routes || routes.length === 0) {
                continue;
            }
            // Add plugin slug as prefix to top level routes
            routes = routes.map(route => (Object.assign({}, route, { path: utils_1.joinPaths(pluginRoutePathPrefix, prefixPluginKey ? key : '', route.path) })));
            // Put the result in the collective result
            pluginRoutes[key] = routes;
        }
        return pluginRoutes;
    }
    /**
     * Register a collection of items.
     * @param collection
     */
    async registerCollection(collection = []) {
        // If its an array
        if (Array.isArray(collection)) {
            for (const item of collection) {
                await this.register(item);
            }
            return;
        }
        // If its an object
        else if (collection === Object(collection)) {
            for (const key of Object.keys(collection)) {
                await this.register(key, collection[key]);
            }
            return;
        }
        throw Error('Could not register collection. Reason: Unknown collection type.');
    }
    /**
     * Convert any input value to an item. This is where you transform inputs and add defualts
     * @param key
     * @param partial
     */
    createItem(key, partial) {
        return super.createItem(key, Object.assign({}, createPlugin(partial), { path: this.createPath(key, partial) }));
    }
    /**
     * Generates a path for the given plugin
     * @param key
     * @param plugin
     */
    createPath(key, plugin) {
        const pluginRoutePathPrefix = this.BB.Configs.getValue('pluginRoutePathPrefix') || '';
        // Resolve routes, if it's a thunk
        // Put the resovled value in an array, if it's a single item
        const routes = utils_1.getDefiniteArray(utils_1.resolveThunk(plugin.routes || plugin.value.routes || [], this.BB));
        // Skip if plugin doesn't have any routes
        if (!routes || routes.length === 0) {
            return;
        }
        return utils_1.joinPaths(pluginRoutePathPrefix, key);
    }
    /**
     * Typeguard to check a given object is an input value
     * @param value
     */
    isInputValue(value) {
        return utils_1.isBlueBaseModule(value) || typeof value === 'object';
    }
}
exports.PluginRegistry = PluginRegistry;
