text: "use strict";
  
  const BannedUser = require('./BannedUser');
  const Store = require('../utils/Store');
  const UnavailableGuild = require('./UnavailableGuild');
  const TextChannel = require('./TextChannel');
  const VoiceChannel = require('./VoiceChannel');
  const CategoryChannel = require('./CategoryChannel');
  const User = require('./User');
  const Member = require('./Member');
  const Role = require('./Role');
  const { ENDPOINTS } = require('../utils/Constants').HTTP;
  
  /**
   * @extends UnavailableGuild Represents an available guild on Discord
   * @prop {GuildChannel?} afkChannel Represents the afk Channel
   * @prop {Snowflake?} afkChannelID The id of the afk channel
   * @prop {Boolean} available Whether the guild is available or not
   * @prop {Store<GuildChannel>} channel A Store of the Channels in the Guild
   * @prop {Number} createdTimestamp The timestamp in ms when the guild was created
   * @prop {Number} defaultMessageNotificaions The default message notification for the Guild
   * @prop {Store<Emoji>} emojis A Store of Guild Emojis
   * @prop {Array} features An array of guild features
   * @prop {String?} icon The icon hash of the guild
   * @prop {String?} iconURL The url of the guild's icon
   * @prop {Snowflake} id The id of the guild
   * @prop {Boolean} large Whether the guild is large or not, depends on `Client.largeThreshold`
   * @prop {Number} memberCount The guilds's member count
   * @prop {Store<Member>} members The amount of Members that the guild is in
   * @prop {String} name The name of the guild
   * @prop {Member?} owner The member object of the guild owner
   * @prop {Member?} ownerID The id of the guild owner
   * @prop {Array} presences An array of member presence
   * @prop {String} region The region of the guild
   * @prop {Store<Role>} roles A Store of Roles ( Where Roles are cached )
   * @prop {Object?} systemChannel The system channel of the guild
   * @prop {Snowflake?} systemChannelID The id of the system channel
   */
  
  class Guild extends UnavailableGuild {
    constructor(client, data) {
      super(client, data);
      Object.defineProperty(this, 'client', { value: client });
      Object.defineProperty(this, '_shard', { value: data.shard });
  
      this.afkChannelID = data.afk_channel_id;
      this.afkTimeout = data.afk_timeout;
      this.applicationID = data.application_id;
      this.channels = new Store();
      this.createdTimestamp = new Date(data.joined_at).getTime();
      this.defaultMessageNotificaions = data.default_message_notifications;
      this.emojis = new Store();
      this.explicitContentFilter = data.explicit_content_filter;
      this.features = data.features;
      this.icon = data.icon || null;
      this.iconURL = data.icon ? `https://cdn.discordapp.com/icons/${data.id}/${this.icon}.png` : null;
      this.large = data.large;
      this.lazy = data.lazy;
      this.memberCount = data.member_count;
      this.members = new Store();
      this.mfaLevel = data.mfa_level;
      this.name = data.name;
      this.ownerID = data.owner_id || null;
      this.presences = data.presences;
      this.region = data.region;
      this.roles = new Store();
      this.splash = data.splash;
      this.systemChannelID = data.system_channel_id;
      this.verificationLevel = data.verification_level;
      this.voiceStates = new Store();
      
      Object.defineProperty(this, '_roleStatus', { value: false, writable: true });
  
      for (var i = 0; i < data.channels.length; i++) {
        data.channels[i].guild = this;
        switch(data.channels[i].type) {
          case 0: {
            this.channels.set(data.channels[i].id, new TextChannel(this.client, data.channels[i]));
            this.client.channels.set(data.channels[i].id, new TextChannel(this.client, data.channels[i]));
            break;
          }
  
          case 2: {
            this.channels.set(data.channels[i].id, new VoiceChannel(this.client, data.channels[i]));
            this.client.channels.set(data.channels[i].id, new VoiceChannel(this.client, data.channels[i]));
            break;
          }
  
          case 4: {
            this.channels.set(data.channels[i].id, new CategoryChannel(this.client, data.channels[i]));
            this.client.channels.set(data.channels[i].id, new CategoryChannel(this.client, data.channels[i]));
            break;
          }
  
          default: {
            this.client.emit('debug', 'Invalid Guild Channel type Received: ' + data.channels[i].type);
            break;
          }
        };
      };
  
      for (var i = 0; i < data.roles.length; i++) {
        this.roles.set(data.roles[i].id, new Role(this.client, data.roles[i]));
      };
  
      for (var i = 0; i < data.members.length; i++) {
        this.client.users.set(data.members[i].user.id, new User(this.client, data.members[i].user));
        data.members[i].guild = this;
        this.members.set(data.members[i].user.id, new Member(this.client, data.members[i]));
      };
  
      for (var i = 0; i < this.presences.length; i++) {
        this.client._presences.set(this.presences[i].user.id, { status: this.presences[i].status, game: this.presences[i].game });
      };
  
      for (var i = 0; i < data.voice_states.length; i++) {
        this.voiceStates.set(data.voice_states[i].user_id, data.voice_states[i]);
      };
    }
  
    get afkChannel() {
      return this.afkChannelID ? this.channels.get(this.afkChannelID) : null;
    }
  
    get bans() {
      return (async () => {
        return await this.client.rest.request("GET", ENDPOINTS.GUILD_BANS(this.id)).data;
      })();
    }
  
    get bot() {
      return this.members.get(this.client.user.id);
    }
  
    get owner() {
      return this.ownerID ? this.members.get(this.ownerID) : null;
    }
  
    get shard() {
      return this._shard ? this._shard : this.client.shards.filter(shard => shard.guilds.has(this.id))[0];
    }
  
    get systemChannel() {
      return this.systemChannelID ? this.channels.get(this.systemChannelID) : null;
    }
  
    /**
     * Bans a member from the guild
     * @param {Snowflake} user The id of the member to ban
     * @param {Object} [options] Options for the guild ban
     * @param {Number} [options.days=0] Number of days to delete messages for
     * @param {String} [options.reason=''] Reasom for the ban
     * @returns {Promise<User>}
     */
  
    ban(user, options = { days: 0, reason: '' }) {
      return this.client.rest.request("PUT", `${ENDPOINTS.GUILD_BAN(this.id, user)}?delete-message-days=${options.days}&reason=${options.reason}`)
      .then(() => {
        return this.client.getUser(user);
      });
    }
  
    /**
     * Begins a prune operation. Returns an object with one 'pruned' key indicating the number of members that would be removed in a prune operation.
     * @param {Number} days Number of days to count prune for (1 or more)
     * @returns {Promise<Object>}
     */
  
    beginPrune(days) {
      return this.client.rest.request("POST", ENDPOINTS.GUILD_PRUNE(this.id), {
        data: {
          days
        }
      }).then(res => {
        return res.data;
      });
    }
  
    /**
     * Deletes a guild
     * @returns {Promise<Guild>}
     */
  
    delete() {
      return this.client.rest.request("DELETE", ENDPOINTS.GUILD(this.id))
      .then(() => {
        return this;
      });
    }
  
    /**
     * Deletes a role from the guild
     * @param {Snowflake} role The id of the role to delete
     * @returns {Promise<Role>}
     */
  
    deleteRole(role) {
      let Role = this.roles.get(role);
  
      return this.client.rest.request("DELETE", ENDPOINTS.GUILD_ROLE(this.id, role))
      .then(() => {
        return Role;
      });
    }
  
    /**
     * Gets information about the banned user id
     * @param {Snowflake} user The id of the banned user
     * @returns {Promise<BannedUser>}
     */
  
    getBan(user) {
      return this.client.rest.request("GET", ENDPOINTS.GUILD_BAN(this.guild, user))
      .then(res => {
        return new BannedUser(this.client, res.data);
      });
    }
  
    /**
     * Returns an array of banned users
     * @returns {Promise<Array<BannedUsers>>}
     */
  
    getBans() {
      return this.client.rest.request("GET", ENDPOINTS.GUILD_BANS(this.guild))
      .then(res => {
        return res.data.map(data => {
          return new BannedUser(this.client, data);
        });
      });
    }
  
    /**
     * Kicks a member from the guild
     * @param {Snowflake} user The id of the member to kick
     * @param {String} [reason] The reason for the kick
     * @returns {Promise<User>}
     */
  
    kick(user, reason) {
      return this.client.rest.request("DELETE", `${ENDPOINTS.GUILD_MEMBER(this.id, user)}?reason=${reason}`)
      .then(() => {
        return this.client.getUser(user);
      });
    }
  
    /**
     * Fetch all the guild channels using the REST API and sends an array of channels that were fetched from the cache
     * @returns {Promise<TextChannel|VoiceChannel|CategoryChannel>}
     */
  
    getChannels() {
      return this.client.rest.request("GET", ENDPOINTS.GUILD_CHANNELS(this.id))
      .then(res => {
        return res.data.map(channel => {
          return this.channels.get(channel.id);
        });
      });
    }
  
    /**
     * Returns an array of guild invites
     * @returns {Promise<Array<Invite>>}
     */
  
    getInvites() {
      return this.client.rest.request("GET", ENDPOINTS.GUILD_INVITES(this.id))
      .then(res => {
        return res.data;
      });
    }
  
    /**
     * Fetches a guild member from the cache, if not present will use the REST API and set it inside the cache
     * @param {Snowflake} user The id of the member
     * @returns {Promise<Member>}
     */
  
    getMember(user) {
      return this.client.rest.request("GET", ENDPOINTS.GUILD_MEMBER(this.id, user))
      .then(res => {
        if (!this.members.has(res.data.user.id)) {
          return this.members.set(res.data.user.id, new Member(this.client, res.data));
        } else {
          return this.members.get(res.data.user.id);
        }
      });
    }
  
    /**
     * Gives a member a role
     * @param {Snowflake} user The id of the member to give the role
     * @param {Snowflake} role The role to give to the member
     * @returns {Promise<Member>}
     */
  
    giveRole(user, role) {
      return this.client.rest.request("PUT", ENDPOINTS.GUILD_MEMBER_ROLE(this.id, user, role))
      .then(() => {
        return this.members.get(user);
      });
    }
  
    /**
     * Similiar to `Client#leaveGuild()`, makes the bot leave the current guild
     * @returns {Promise<Guild>}
     */
  
    async leave() {
      await this.client.leaveGuild(this.id);
      
      return Promise.resolve(this);
    }
  
    /**
     * Creates a Guild Channel
     * Guild Channel types:
     * * `text` - Text Channel
     * * `voice` - Voice Channel
     * * `category` - Category Channel
     * @param {String} name The name of the channel
     * @param {String} [type='text'] The type of the channel
     * @returns {Promise<TextChannel|VoiceChannel|CategoryChannel>}
     */
  
    makeChannel(name, type = 'text') {
      let types = ['text', 'voice', 'category'];
  
      if (typeof type !== 'string' || typeof type === 'string' && !types.includes(type.toLowerCase()))
        return this.client.emit('error', new Error('Invalid Channel type!'));
  
      type = type.toLowerCase();
  
      if (type === 'text') {
        type = 0;
      } else if (type === 'voice') {
        type = 2;
      } else if (type === 'category') {
        type = 4;
      }
  
      return this.client.rest.request("POST", ENDPOINTS.GUILD_CHANNELS(this.id), {
        data: {
          name,
          type
        }
      }).then(res => {
        if (this.channels.has(res.data.id)) {
          return this.channels.get(res.data.id);
        } else {
          let guild = this;
          res.data.guild = guild;
  
          switch (res.data.type) {
            case 0:
              guild.channels.set(res.data.id, new TextChannel(this.client, res.data));
              this.client.channels.set(res.data.id, new TextChannel(this.client, res.data));
              break;
  
            case 1:
            this.client.channels.set(res.data.id, new DMChannel(this.client, res.data));
              break;
  
            case 2:
              guild.channels.set(res.data.id, new VoiceChannel(this.client, res.data));
              this.client.channels.set(res.data.id, new VoiceChannel(this.client, res.data));
              break;
  
            case 4:
              guild.channels.set(res.data.id, new CategoryChannel(this.client, res.data));
              this.client.channels.set(res.data.id, new CategoryChannel(this.client, res.data));
              break;
          };
  
          return this.client.channels.get(res.data.id);
        };
      });
    }
  
    /**
     * Creates a guild role
     * @param {String} name The name of the role
     * @param {Boolean} [isHoisted=false] Whether the role should be seperate from the `@everyone` role
     * @param {Boolean} [isMentionable=false] Whther the role should be mentionable
     * @returns {Promise<Role>}
     */
  
    makeRole(name, isHoisted = false, isMentionable = false) {
      return this.client.rest.request("POST", ENDPOINTS.GUILD_ROLES(this.id), {
        data: {
          name,
          hoist: isHoisted,
          mentionable: isMentionable
        }
      }).then(res => {
        return this.roles.get(res.data.id);
      });
    }
  
    /**
     * Edits a channel's position
     * @param {Snowflake} channel The id of the channel to set the position of
     * @param {Number} position The new position of the channel
     * @returns {Promise<Channel>}
     */
  
    modifyChannelPosition(channel, position) {
      return this.client.rest.request("PATCH", ENDPOINTS.GUILD_CHANNEL(this.id, channel), {
        data: {
          id: channel,
          position
        }
      }).then(() => {
        return this.channels.get(channel);
      });
    }
  
    /**
     * Edits a guild role
     * @param {Snowflake} role The id of the role to edit
     * @param {Object} [options] The options for the role
     * @param {String} [options.name] The new name of the role
     * @param {Number} [options.color] The color of the role in RGB Form. e.g `(1, 1, 1)`
     * @param {Boolean} [options.isHoisted=false] Whether the role should be displayed separately in the sidebar
     * @param {Boolean} [options.isMentionable=false] Whether the role is mentionable
     * @returns {Promise<Role>}
     */
  
    modifyRole(role, options = {}) {
      return this.client.rest.request("PATCH", ENDPOINTS.GUILD_ROLE(this.id, role), {
        data: {
          name: options.name,
          color: options.color,
          hoist: options.isHoisted || false,
          mentionable: options.isMentionable || false
        }
      }).then(() => {
        return this.roles.get(role);
      });
    }
  
    /**
     * Returns an object with one 'pruned' key indicating the number of members that would be removed in a prune operation.
     * @param {Number} days Number of days to count prune for (1 or more)
     * @returns {Promise<Object>}
     */
  
    pruneCount(days) {
      return this.client.rest.request("GET", ENDPOINTS.GUILD_PRUNE(this.id), {
        data: {
          days
        }
      }).then(res => {
        return res.data;
      });
    }
  
    /**
     * Removes a role from a member
     * @param {Snowflake} user The id of the user to remove the role from
     * @param {Snowflake} role The id of the role to remove
     * @returns {Promise<Member>}
     */
  
    removeRole(user, role) {
      return this.client.rest.request("DELETE", ENDPOINTS.GUILD_MEMBER_ROLE(this.id, user, role))
      .then(() => {
        return this.members.get(user);
      });
    }
  
    /**
     * Edits a channel position
     * @param {Snowflake} channel The id of the guild channel
     * @param {Number} position The new position of the channel
     * @returns {Promise<TextChannel|VoiceChannel|GuildChannel>}
     */
  
    setChannelPosition(channel, position) {
      return this.client.rest.request("PATCH", ENDPOINTS.GUILD_CHANNELS(this.id), {
        data: {
          id: channel,
          position
        }
      }).then(() => {
        return this.channels.get(channel);
      });
    }
  
    /**
     * Sets the nickname of the client
     * @param {String} nick The new nickname for the bot
     * @returns {Promise<Member>}
     */
  
    setOwnNick(nick) {
      return this.client.rest.request("PATCH", ENDPOINTS.GUILD_MEMBER_NICK(this.id, '@me'), {
        data: {
          nick
        }
      }).then(() => {
        return this.bot;
      });
    }
  
    /**
     * Softbans a member from the guild
     * @param {Snowflake} user The id of the member to softban
     * @param {String} reason The reason for the softban
     * @returns {Promise<User>}
     */
  
    async softban(user, reason) {
      this.ban(user, { days: 7, reason });
      this.unban(user, reason);
  
      return await this.client.getUser(user);
    }
  
    /**
     * Unbans a member from the guild
     * @param {Snowflake} user The id of the member to unban
     * @param {String} reason The reason for the unban
     * @returns {Promise<User>}
     */
  
    unban(user, reason) {
      return this.client.rest.request("DELETE", `${ENDPOINTS.GUILD_BAN(this.id, user)}?reason=${reason}`)
      .then(() => {
        return this.client.getUser(user);
      });
    }
  };
  
  module.exports = Guild;