"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const BlueBaseModuleRegistry_1 = require("./BlueBaseModuleRegistry");
const utils_1 = require("../utils");
const lodash_isfunction_1 = tslib_1.__importDefault(require("lodash.isfunction"));
const lodash_isnil_1 = tslib_1.__importDefault(require("lodash.isnil"));
/**
 * Default priority for a hook.
 */
exports.DEFAULT_HOOK_PRIORITY = 10;
/**
 * 🎣 HookRegistry
 */
class HookRegistry extends BlueBaseModuleRegistry_1.BlueBaseModuleRegistry {
    /**
     * Registers a nested hook collection.
     *
     * @param collections
     */
    async registerNestedCollection(collections = {}) {
        // If hooks field is a thunk, then call the thunk function
        collections = utils_1.resolveThunk(collections, this.BB);
        // Extract hook names. These are events that are being subscribed to
        const eventNames = Object.keys(collections);
        // Iterate over each hook name
        for (const eventName of eventNames) {
            // Extract collection for each event
            const singleCollection = utils_1.getDefiniteArray(collections[eventName]);
            for (let item of singleCollection) {
                // Make sure item is resolved if its a promise
                item = await utils_1.getDefiniteBlueBaseModule(item);
                // Register each item indivitually
                if (this.isInputValue(item)) {
                    await this.register({ event: eventName, value: item });
                    continue;
                }
                const newItem = Object.assign({ event: eventName }, item);
                if (!this.isInputItem(newItem)) {
                    throw Error(`Could not register Hook. Reason: Input is not a hook item.`);
                }
                await this.register(newItem);
            }
        }
    }
    /**
     * Get all hooks for a specific event
     * @param event
     */
    findAllByEvent(event) {
        return this.filter((_value, _key, item) => item.event === event);
    }
    /**
     * Run all hook listeners in a waterfall.
     * Each listener function gets 3 arguments:
     * 	- value
     * 	- args
     * 	- context
     *
     * Each listener function is expected to return a value.
     *
     * Example Usage: BB.Hooks.run('hook-name', val, args);
     *
     * @param eventName Name of the hook
     * @param value Initial value to send to the hook
     * @param args Any extra arguments to pass to the hook
     */
    async run(eventName, initialValue, args = {}) {
        let value = initialValue;
        // Get all hook items registered for this event
        const hookItems = Object.values(this.findAllByEvent(eventName));
        // If there are no hook items registered for this hook
        if (hookItems.length === 0) {
            return value;
        }
        // Sort items based on priority
        hookItems.sort((a, b) => a.priority - b.priority);
        // Run waterfall
        for (const item of hookItems) {
            // Resolve handler functions
            const handler = await item.value;
            // Check if handler is a valid function
            if (!lodash_isfunction_1.default(handler)) {
                throw Error(`Handler of HookListener "${item.key}" in hook "${eventName}" is not a function.`);
            }
            // Execute hook function
            const result = await handler(value, Object.assign({}, args), this.BB);
            // If the hook didn't return any value, return previous accumulator
            if (typeof result === 'undefined' && typeof initialValue !== 'undefined') {
                // if result of current iteration is undefined, don't pass it on
                this.BB.Logger.warn(`HookListener "${item.key}" in hook "${eventName}" did not return a result.`, item);
            }
            else {
                value = result;
            }
        }
        return value;
    }
    /**
     * Typeguard to check a given object is an input value
     * @param value
     */
    isInputValue(value) {
        return typeof value === 'function';
    }
    /**
     * Typeguard to check a given object is a input item
     * @param item
     */
    isInputItem(item) {
        return !lodash_isnil_1.default(item.value) && !lodash_isnil_1.default(item.event);
    }
    /**
     * 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({ priority: exports.DEFAULT_HOOK_PRIORITY }, partial));
    }
}
exports.HookRegistry = HookRegistry;
