Source: Command.js

const CommandRequirements = require('./requirements')
/** Class representing a command. */
class Command {
	/**
   * Create a command.
   * @class
   * @param {string|Array} name - The name of the command. If passed as an
   *     array, the first item of the array is used as the name and the rest of
   *     the items are set as aliases.
   * @param {object} options - Options of command
   * @param {string} [options.category='Default'] - Category from this command
   * @param {string} [options.help=''] - Description of command
   * @param {string} [options.args=''] - The command arguments
   * @param {boolean} [options.hide=false] - Hide command from default help command
   * @param {boolean} [options.enable=true] - Enable/Disable the command
   * @param {string} [options.childOf=undefined] - Parent command name
   * @param {Array} [options.requirements=[]] - Requirements are mapped in client.addCommand
   * @param {object} [options.hooks = {}] - Command hooks.
   * @param {Array<function>} [options.hooks.pre=[]] - Hook pre run command.
   * @param {Array<function>} [options.hooks.executed=[]] - Hook after run command.
   * @param {Array<function>} [options.hooks.error=[]] - Hook fired when there an error with execution command.
   * @param {object} [options.custom={}] - Define custom methods or properties to copy to command.
   * @param {Command~run} run - The function to be called when the command is executed.
   * @param {string | function | EmbedMessageObject } response - Response to command. Ignore run function.
   * @param {string | function | EmbedMessageObject } responseDM - DM response to command. Ignore run function.
   */
	constructor(name, options = {}, run) {
		if (Array.isArray(name)) {
			/** @prop {string} - Name of Command */
			[this.name] = name.splice(0, 1)
			/** @prop {string[]} - Command aliases */
			this.aliases = name
		} else if(typeof(name) === "string") {
			this.name = name
			this.aliases = []
		} else if(typeof(name) === "object") {
			options = name
			if (Array.isArray(options.name)) {
				[this.name] = options.name.splice(0, 1)
				this.aliases = options.name
			} else if(typeof(options.name) === "string") {
				this.name = options.name
				this.aliases = []
			}
		}
		if (!this.name) throw new Error('Name is required')
		/** @prop {Command~run} - Run function of command */
		this.run = run || options.run || async function(){}
		/** @prop {string} - Description of command */
		this.help = options.help || ''
		/** @prop {string | EmbedMessageObject | function } - Response of command. If it exists, ignore run function. If function (msg, args, client, commad) */
		this.response = options.response || ''
		/** @prop {string | EmbedMessageObject | function } - Response of command with a direct message. If it exists, ignore run function. If function (msg, args, client, commad) */
		this.responseDM = options.responseDM || ''
		/** @prop {Command | undefined} - Parent Command */
		this.parent = undefined
		/** @prop {Command[]} - Subcommands of Command. */
		this.childs = []
		/** @prop {array} - Command Requirements */
		this.requirements = Array.isArray(options.requirements) ? options.requirements : [] // These requirements are mapped in client.addCommand
		/** @prop {object} - Command Hooks */
		this.hooks = {
			prereq: [], // Fired before check reqiurements
			prerun: [], // Fired before run command
			executed: [], // Fired after command is run
			error: [] // Fired when there is an error running pre/executed hooks and response/run methods
		}
		if(options.hooks && typeof(options.hooks) === 'object'){
			Object.keys(this.hooks).forEach(key => {
				const hook = this.hooks[key]
				if(typeof(hook) !== 'function') throw new TypeError(`${hook} is not a function on ${this.name}`)
				this.addHook(key, hook)
			})
		}
		/** @prop {string|undefined} - Name of uppercomand */
		this.childOf = options.childOf
		/** @prop {string} - Command category. It should exist if not, will be 'Default' */
		this.category = options.category || 'Default'
		/** @prop {string} - Arguments for a command */
		this.args = options.args || ''
		/** @prop {boolean} - Hide to default help command */
		this.hide = options.hide !== undefined ? options.hide : false // Hide command from help command
		/** @prop {boolean} - Enable/Disable the command */
		this.enable = options.enable !== undefined ? options.enable : true // Enable or disable command
		/** @prop {Client} - Client instance */
		this.client = undefined
		/** @prop {object | undefined} - Custom props */
		this.custom = undefined
	}

	/**
    * @callback Command~run
    * A function to be called when a command is executed. Accepts information
    * about the message that triggered the command as arguments.
    * @param {Eris.Message} msg - The Eris message object that triggered the command.
    *     For more information, see the Eris documentation:
    *     {@link https://abal.moe/Eris/docs/Message}
    * @param {args} args - An array of arguments passed to the command,
    *     obtained by removing the command name and prefix from the message, then
    *     splitting on spaces. To get the raw text that was passed to the
    *     command, use `args.join(' ')`.
	* @param {Client} client client instance that recieved the message triggering the
    *     command.
    * @param {Command} command - The name or alias used to call the command in
    *     the message. Will be one of the values of `this.names`.
    */

	/**
	* All names that can be used to invoke the command - its primary name in
	* addition to its aliases.
	* @return {Array<string>}
	*/
	get names() {
		return [this.name, ...this.aliases]
	}

	/** Add a requirement
	* @param {object} requirement - Requirement to add. Inject a method to remove it.
	*/
	addRequirement(requirement) {
		requirement.remove = () => this.removeRequirement(requirement)
		this.requirements.push(requirement)
		if(requirement.init){
			requirement.init(this.client, this, requirement)
		}
	}

	/** Remove a requirement
	* @param {object} requirement - Requirement to remove.
	*/
	removeRequirement(requirement) {
		this.requirements = this.requirements.filter(req => req !== requirement)
	}

	/**
	 * Command hook
	 * @callback CommandHook 
	 * @param {Eris.Message} msg - Eris message 
	 * @param {args} args - Arguments
	 * @param {Client} client - Eris message 
	 * @param {Command} command - Command
	 */

	/** Add a hook
	* @param {string} hookname - Hook name.
	* @param {CommandHook} hook - Hook to add.Inject a method to remove it.
	*/
	addHook(hookname, hook){
		if(!this.hooks[hookname]){throw new Error(`Add command hook error: ${hookname} not defined on ${this.name} command`)}
		hook.remove = () => this.removeHook(hookname, hook)
		this.hooks[hookname].push(hook)
	}

	/** Remove a hook
	* @param {string} hookname - Hook name.
	* @param {CommandHook} hook - Hook to remove.
	*/
	removeHook(hookname, hook){
		if(!this.hooks[hookname]){throw new Error(`Add command hook error: ${hookname} not defined on ${this.name} command`)}
		this.hooks[hookname] = this.hooks[hookname].filter(h => h !== hook)
	}

	runHook(hookname, ...args){
		this.hooks[hookname].forEach(hook => hook(...args))
	}

	/** Throw a command error */
	error(message) {
		throw new Error(message)
	}
}

/**
 * @typedef CommandRequirementObject
 * @prop {string} type - Type requirement
 * @prop {function} condition - If returns false, do first of response/resposneDM/run
 * @prop {string|function|undefined} response - Reply with this response
 * @prop {string|function|undefined} responseDM - Reply with this response by direct message
 * @prop {function|undefined} run - Run this custom function
 * @prop {function|undefined} init - Run when requirement is added to command
 */

 /**
 * @callback CommandRequirementFunction
 * @param {object} config - Config requirement
 * @param {Command} config.command - Command
 * @param {Client} config.command - Client
 * @returns {CommandRequirementObject|Array<CommandRequirementObject>}
 */

module.exports = Command