const {EventBase, EventBusBase, EventListener} = require("./EventBase.js");
const {EventFilter} = require("./EventFilter");
const {checkType, randomInt, createHiddenProp} = require("./Utility");
const {Component} = require("./Component");

/**
 * An event for communicating actions
 *
 * @extends EventBase
 */
class ActionEvent extends EventBase {
    /**
     * Creates a new ActionEvent
     *
     * @param {string} sourceName - The name of the source for this event
     * @param {string} sourceType - The type of the source for this event
     */
    constructor(sourceName, sourceType) {
        super();
        checkType("ActionEvent.constructor", "name", sourceName, "string");
        checkType("ActionEvent.constructor", "type", sourceType, "string");
        createHiddenProp(this, "_sourceName", sourceName, true);
        createHiddenProp(this, "_sourceType", sourceType, true);
    }

    // eslint-disable-next-line jsdoc/require-jsdoc
    get sourceName() {
        return this._sourceName || "initializing";
    }

    // eslint-disable-next-line jsdoc/require-jsdoc
    get sourceType() {
        return this._sourceType || "initializing";
    }

    // eslint-disable-next-line jsdoc/require-jsdoc
    get allowedEventTypes() {
        return new Set(["register", "waiting", "action"]);
    }

    // eslint-disable-next-line jsdoc/require-jsdoc
    get eventBus() {
        return actionEventBus;
    }
}

const actionEventBus = new EventBusBase(ActionEvent);

/**
 * Selects the action to perform
 *
 * @extends Component
 */
class ActionSelection extends Component {
    /**
     * Creates a new ActionSelection component
     */
    constructor() {
        super("action-selection", "action", ActionEvent);

        // handle a sychronoous event loop
        this._filter = new EventFilter("allow", {eventType: "waiting", all: true});
        new EventListener(actionEventBus, this._filter, () => {
            this.performAction();
        });
    }

    /**
     * Select and perform an action from the Action.actionList
     */
    performAction() {
        let al = Action.getActionList();
        let potentialActions = Array.from(al.values());

        // TODO: just performing random actions for now
        let selected = randomInt(0, potentialActions.length - 1);
        let ret = potentialActions[selected]();

        this.sendEvent("action", ret);
    }
}

new ActionSelection();
let actionList = new Map();

/**
 * Add and remove potential actions that may be selected
 */
class Action {
    /**
     * Adds a new action that can be performed
     *
     * @param {string}   name - The name of the action
     * @param {Function} fn   - The function to be performed
     */
    static addAction(name, fn) {
        checkType("addAction", "name", name, "string");
        checkType("addAction", "fn", fn, "function");

        if (actionList.has(name)) {
            throw new Error(`addAction '${name}' already exists`);
        }

        actionList.set(name, fn);
    }

    /**
     * Returns a list of all registered actions
     *
     * @returns {Map} List of actions that have been registered through {@link Action.addAction}
     */
    static getActionList() {
        return actionList;
    }

    /**
     * Removes all registered actions. Mostly used for testing.
     */
    static clearActions() {
        actionList.clear();
    }

    /** the action event bus, for communicating between action {@link Component|Components} */
    static get eventBus() {
        return actionEventBus;
    }
}

module.exports = {
    Action,
    ActionEvent,
    ActionSelection,
};