text: 'use strict';
  
  const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
  const BaseManager = require('./BaseManager');
  const { TypeError } = require('../errors');
  const ApplicationCommand = require('../structures/ApplicationCommand');
  const Collection = require('../util/Collection');
  
  /**
   * Manages API methods for application commands and stores their cache.
   * @extends {BaseManager}
   */
  class ApplicationCommandManager extends BaseManager {
    constructor(client, iterable) {
      super(client, iterable, ApplicationCommand);
  
      /**
       * The manager for permissions of arbitrary commands on arbitrary guilds
       * @type {ApplicationCommandPermissionsManager}
       */
      this.permissions = new ApplicationCommandPermissionsManager(this);
    }
  
    /**
     * The cache of this manager
     * @type {Collection<Snowflake, ApplicationCommand>}
     * @name ApplicationCommandManager#cache
     */
  
    add(data, cache, guildID) {
      return super.add(data, cache, { extras: [this.guild, guildID] });
    }
  
    /**
     * The APIRouter path to the commands
     * @param {Snowflake} [options.id] ID of the application command
     * @param {Snowflake} [options.guildID] ID of the guild to use in the path,
     * ignored when using a {@link GuildApplicationCommandManager}
     * @returns {Object}
     * @private
     */
    commandPath({ id, guildID } = {}) {
      let path = this.client.api.applications(this.client.application.id);
      if (this.guild || guildID) path = path.guilds(this.guild?.id ?? guildID);
      return id ? path.commands(id) : path.commands;
    }
  
    /**
     * Data that resolves to give an ApplicationCommand object. This can be:
     * * An ApplicationCommand object
     * * A Snowflake
     * @typedef {ApplicationCommand|Snowflake} ApplicationCommandResolvable
     */
  
    /**
     * Options used to fetch data from discord
     * @typedef {Object} BaseFetchOptions
     * @property {boolean} [cache=true] Whether to cache the fetched data if it wasn't already
     * @property {boolean} [force=false] Whether to skip the cache check and request the API
     */
  
    /**
     * Options used to fetch Application Commands from discord
     * @typedef {BaseFetchOptions} FetchApplicationCommandOptions
     * @property {Snowflake} [guildID] ID of the guild to fetch commands for, for when the guild is not cached
     */
  
    /**
     * Obtains one or multiple application commands from Discord, or the cache if it's already available.
     * @param {Snowflake} [id] ID of the application command
     * @param {FetchApplicationCommandOptions} [options] Additional options for this fetch
     * @returns {Promise<ApplicationCommand|Collection<Snowflake, ApplicationCommand>>}
     * @example
     * // Fetch a single command
     * client.application.commands.fetch('123456789012345678')
     *   .then(command => console.log(`Fetched command ${command.name}`))
     *   .catch(console.error);
     * @example
     * // Fetch all commands
     * guild.commands.fetch()
     *   .then(commands => console.log(`Fetched ${commands.size} commands`))
     *   .catch(console.error);
     */
    async fetch(id, { guildID, cache = true, force = false } = {}) {
      if (typeof id === 'object') {
        ({ guildID, cache = true, force = false } = id);
        id = undefined;
      }
      if (id) {
        if (!force) {
          const existing = this.cache.get(id);
          if (existing) return existing;
        }
        const command = await this.commandPath({ id, guildID }).get();
        return this.add(command, cache);
      }
  
      const data = await this.commandPath({ guildID }).get();
      return data.reduce((coll, command) => coll.set(command.id, this.add(command, cache, guildID)), new Collection());
    }
  
    /**
     * Creates an application command.
     * @param {ApplicationCommandData} command The command
     * @param {Snowflake} [guildID] ID of the guild to create this command in,
     * ignored when using a {@link GuildApplicationCommandManager}
     * @returns {Promise<ApplicationCommand>}
     * @example
     * // Create a new command
     * client.application.commands.create({
     *   name: 'test',
     *   description: 'A test command',
     * })
     *   .then(console.log)
     *   .catch(console.error);
     */
    async create(command, guildID) {
      const data = await this.commandPath({ guildID }).post({
        data: this.constructor.transformCommand(command),
      });
      return this.add(data, undefined, guildID);
    }
  
    /**
     * Sets all the commands for this application or guild.
     * @param {ApplicationCommandData[]} commands The commands
     * @param {Snowflake} [guildID] ID of the guild to create the commands in,
     * ignored when using a {@link GuildApplicationCommandManager}
     * @returns {Promise<Collection<Snowflake, ApplicationCommand>>}
     * @example
     * // Set all commands to just this one
     * client.application.commands.set([
     *   {
     *     name: 'test',
     *     description: 'A test command',
     *   },
     * ])
     *   .then(console.log)
     *   .catch(console.error);
     * @example
     * // Remove all commands
     * guild.commands.set([])
     *   .then(console.log)
     *   .catch(console.error);
     */
    async set(commands, guildID) {
      const data = await this.commandPath({ guildID }).put({
        data: commands.map(c => this.constructor.transformCommand(c)),
      });
      return data.reduce(
        (coll, command) => coll.set(command.id, this.add(command, undefined, guildID)),
        new Collection(),
      );
    }
  
    /**
     * Edits an application command.
     * @param {ApplicationCommandResolvable} command The command to edit
     * @param {ApplicationCommandData} data The data to update the command with
     * @param {Snowflake} [guildID] ID of the guild where the command registered,
     * ignored when using a {@link GuildApplicationCommandManager}
     * @returns {Promise<ApplicationCommand>}
     * @example
     * // Edit an existing command
     * client.application.commands.edit('123456789012345678', {
     *   description: 'New description',
     * })
     *   .then(console.log)
     *   .catch(console.error);
     */
    async edit(command, data, guildID) {
      const id = this.resolveID(command);
      if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
  
      const patched = await this.commandPath({ id, guildID }).patch({ data: this.constructor.transformCommand(data) });
      return this.add(patched, undefined, guildID);
    }
  
    /**
     * Deletes an application command.
     * @param {ApplicationCommandResolvable} command The command to delete
     * @param {Snowflake} [guildID] ID of the guild where the command is registered,
     * ignored when using a {@link GuildApplicationCommandManager}
     * @returns {Promise<?ApplicationCommand>}
     * @example
     * // Delete a command
     * guild.commands.delete('123456789012345678')
     *   .then(console.log)
     *   .catch(console.error);
     */
    async delete(command, guildID) {
      const id = this.resolveID(command);
      if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
  
      await this.commandPath({ id, guildID }).delete();
  
      const cached = this.cache.get(id);
      this.cache.delete(id);
      return cached ?? null;
    }
  
    /**
     * Transforms an {@link ApplicationCommandData} object into something that can be used with the API.
     * @param {ApplicationCommandData} command The command to transform
     * @returns {APIApplicationCommand}
     * @private
     */
    static transformCommand(command) {
      return {
        name: command.name,
        description: command.description,
        options: command.options?.map(o => ApplicationCommand.transformOption(o)),
        default_permission: command.defaultPermission,
      };
    }
  }
  
  module.exports = ApplicationCommandManager;
  