text: const Collection = require('../util/Collection');
  const Snowflake = require('../util/Snowflake');
  const Webhook = require('./Webhook');
  const Invite = require('./Invite');
  
  /**
   * The target type of an entry, e.g. `GUILD`. Here are the available types:
   * * GUILD
   * * CHANNEL
   * * USER
   * * ROLE
   * * INVITE
   * * WEBHOOK
   * * EMOJI
   * * MESSAGE
   * @typedef {string} AuditLogTargetType
   */
  
  /**
   * Key mirror of all available audit log targets.
   * @name GuildAuditLogs.Targets
   * @type {AuditLogTargetType}
   */
  const Targets = {
    ALL: 'ALL',
    GUILD: 'GUILD',
    CHANNEL: 'CHANNEL',
    USER: 'USER',
    ROLE: 'ROLE',
    INVITE: 'INVITE',
    WEBHOOK: 'WEBHOOK',
    EMOJI: 'EMOJI',
    MESSAGE: 'MESSAGE',
  };
  
  /**
   * The action of an entry. Here are the available actions:
   * * ALL: null
   * * GUILD_UPDATE: 1
   * * CHANNEL_CREATE: 10
   * * CHANNEL_UPDATE: 11
   * * CHANNEL_DELETE: 12
   * * CHANNEL_OVERWRITE_CREATE: 13
   * * CHANNEL_OVERWRITE_UPDATE: 14
   * * CHANNEL_OVERWRITE_DELETE: 15
   * * MEMBER_KICK: 20
   * * MEMBER_PRUNE: 21
   * * MEMBER_BAN_ADD: 22
   * * MEMBER_BAN_REMOVE: 23
   * * MEMBER_UPDATE: 24
   * * MEMBER_ROLE_UPDATE: 25
   * * ROLE_CREATE: 30
   * * ROLE_UPDATE: 31
   * * ROLE_DELETE: 32
   * * INVITE_CREATE: 40
   * * INVITE_UPDATE: 41
   * * INVITE_DELETE: 42
   * * WEBHOOK_CREATE: 50
   * * WEBHOOK_UPDATE: 51
   * * WEBHOOK_DELETE: 52
   * * EMOJI_CREATE: 60
   * * EMOJI_UPDATE: 61
   * * EMOJI_DELETE: 62
   * * MESSAGE_DELETE: 72
   * @typedef {?number|string} AuditLogAction
   */
  
  /**
   * All available actions keyed under their names to their numeric values.
   * @name GuildAuditLogs.Actions
   * @type {AuditLogAction}
   */
  const Actions = {
    ALL: null,
    GUILD_UPDATE: 1,
    CHANNEL_CREATE: 10,
    CHANNEL_UPDATE: 11,
    CHANNEL_DELETE: 12,
    CHANNEL_OVERWRITE_CREATE: 13,
    CHANNEL_OVERWRITE_UPDATE: 14,
    CHANNEL_OVERWRITE_DELETE: 15,
    MEMBER_KICK: 20,
    MEMBER_PRUNE: 21,
    MEMBER_BAN_ADD: 22,
    MEMBER_BAN_REMOVE: 23,
    MEMBER_UPDATE: 24,
    MEMBER_ROLE_UPDATE: 25,
    ROLE_CREATE: 30,
    ROLE_UPDATE: 31,
    ROLE_DELETE: 32,
    INVITE_CREATE: 40,
    INVITE_UPDATE: 41,
    INVITE_DELETE: 42,
    WEBHOOK_CREATE: 50,
    WEBHOOK_UPDATE: 51,
    WEBHOOK_DELETE: 52,
    EMOJI_CREATE: 60,
    EMOJI_UPDATE: 61,
    EMOJI_DELETE: 62,
    MESSAGE_DELETE: 72,
  };
  
  
  /**
   * Audit logs entries are held in this class.
   */
  class GuildAuditLogs {
    constructor(guild, data) {
      if (data.users) for (const user of data.users) guild.client.dataManager.newUser(user);
  
      /**
       * Cached webhooks
       * @type {Collection<Snowflake, Webhook>}
       * @private
       */
      this.webhooks = new Collection();
      if (data.webhooks) {
        for (const hook of data.webhooks) {
          this.webhooks.set(hook.id, new Webhook(guild.client, hook));
        }
      }
  
      /**
       * The entries for this guild's audit logs
       * @type {Collection<Snowflake, GuildAuditLogsEntry>}
       */
      this.entries = new Collection();
      for (const item of data.audit_log_entries) {
        const entry = new GuildAuditLogsEntry(this, guild, item);
        this.entries.set(entry.id, entry);
      }
    }
  
    /**
     * Handles possible promises for entry targets.
     * @returns {Promise<GuildAuditLogs>}
     */
    static build(...args) {
      const logs = new GuildAuditLogs(...args);
      return Promise.all(logs.entries.map(e => e.target)).then(() => logs);
    }
  
    /**
     * The target of an entry. It can be one of:
     * * A guild
     * * A user
     * * A role
     * * An emoji
     * * An invite
     * * A webhook
     * * An object where the keys represent either the new value or the old value
     * @typedef {?Object|Guild|User|Role|Emoji|Invite|Webhook} AuditLogEntryTarget
     */
  
    /**
     * Find target type from entry action.
     * @param {number} target The action target
     * @returns {?string}
     */
    static targetType(target) {
      if (target < 10) return Targets.GUILD;
      if (target < 20) return Targets.CHANNEL;
      if (target < 30) return Targets.USER;
      if (target < 40) return Targets.ROLE;
      if (target < 50) return Targets.INVITE;
      if (target < 60) return Targets.WEBHOOK;
      if (target < 70) return Targets.EMOJI;
      if (target < 80) return Targets.MESSAGE;
      return null;
    }
  
    /**
     * The action type of an entry, e.g. `CREATE`. Here are the available types:
     * * CREATE
     * * DELETE
     * * UPDATE
     * * ALL
     * @typedef {string} AuditLogActionType
     */
  
  
    /**
     * Finds the action type from the entry action.
     * @param {AuditLogAction} action The action target
     * @returns {AuditLogActionType}
     */
    static actionType(action) {
      if ([
        Actions.CHANNEL_CREATE,
        Actions.CHANNEL_OVERWRITE_CREATE,
        Actions.MEMBER_BAN_REMOVE,
        Actions.ROLE_CREATE,
        Actions.INVITE_CREATE,
        Actions.WEBHOOK_CREATE,
        Actions.EMOJI_CREATE,
      ].includes(action)) return 'CREATE';
  
      if ([
        Actions.CHANNEL_DELETE,
        Actions.CHANNEL_OVERWRITE_DELETE,
        Actions.MEMBER_KICK,
        Actions.MEMBER_PRUNE,
        Actions.MEMBER_BAN_ADD,
        Actions.ROLE_DELETE,
        Actions.INVITE_DELETE,
        Actions.WEBHOOK_DELETE,
        Actions.EMOJI_DELETE,
        Actions.MESSAGE_DELETE,
      ].includes(action)) return 'DELETE';
  
      if ([
        Actions.GUILD_UPDATE,
        Actions.CHANNEL_UPDATE,
        Actions.CHANNEL_OVERWRITE_UPDATE,
        Actions.MEMBER_UPDATE,
        Actions.MEMBER_ROLE_UPDATE,
        Actions.ROLE_UPDATE,
        Actions.INVITE_UPDATE,
        Actions.WEBHOOK_UPDATE,
        Actions.EMOJI_UPDATE,
      ].includes(action)) return 'UPDATE';
  
      return 'ALL';
    }
  }
  
  /**
   * Audit logs entry.
   */
  class GuildAuditLogsEntry {
    constructor(logs, guild, data) {
      const targetType = GuildAuditLogs.targetType(data.action_type);
      /**
       * The target type of this entry
       * @type {AuditLogTargetType}
       */
      this.targetType = targetType;
  
      /**
       * The action type of this entry
       * @type {AuditLogActionType}
       */
      this.actionType = GuildAuditLogs.actionType(data.action_type);
  
      /**
       * Specific action type of this entry in its string representation
       * @type {AuditLogAction}
       */
      this.action = Object.keys(Actions).find(k => Actions[k] === data.action_type);
  
      /**
       * The reason of this entry
       * @type {?string}
       */
      this.reason = data.reason || null;
  
      /**
       * The user that executed this entry
       * @type {User}
       */
      this.executor = guild.client.users.get(data.user_id);
  
      /**
       * An entry in the audit log representing a specific change.
       * @typedef {object} AuditLogChange
       * @property {string} key The property that was changed, e.g. `nick` for nickname changes
       * @property {*} [old] The old value of the change, e.g. for nicknames, the old nickname
       * @property {*} [new] The new value of the change, e.g. for nicknames, the new nickname
       */
  
      /**
       * Specific property changes
       * @type {AuditLogChange[]}
       */
      this.changes = data.changes ? data.changes.map(c => ({ key: c.key, old: c.old_value, new: c.new_value })) : null;
  
      /**
       * The ID of this entry
       * @type {Snowflake}
       */
      this.id = data.id;
  
      /**
       * Any extra data from the entry
       * @type {?Object|Role|GuildMember}
       */
      this.extra = null;
      if (data.options) {
        if (data.action_type === Actions.MEMBER_PRUNE) {
          this.extra = {
            removed: data.options.members_removed,
            days: data.options.delete_member_days,
          };
        } else if (data.action_type === Actions.MESSAGE_DELETE) {
          this.extra = {
            count: data.options.count,
            channel: guild.channels.get(data.options.channel_id),
          };
        } else {
          switch (data.options.type) {
            case 'member':
              this.extra = guild.members.get(data.options.id);
              if (!this.extra) this.extra = { id: data.options.id };
              break;
            case 'role':
              this.extra = guild.roles.get(data.options.id);
              if (!this.extra) this.extra = { id: data.options.id, name: data.options.role_name };
              break;
            default:
              break;
          }
        }
      }
  
      if ([Targets.USER, Targets.GUILD].includes(targetType)) {
        /**
         * The target of this entry
         * @type {AuditLogEntryTarget}
         */
        this.target = guild.client[`${targetType.toLowerCase()}s`].get(data.target_id);
      } else if (targetType === Targets.WEBHOOK) {
        this.target = logs.webhooks.get(data.target_id) ||
          new Webhook(guild.client,
            this.changes.reduce((o, c) => {
              o[c.key] = c.new || c.old;
              return o;
            }, {
              id: data.target_id,
              guild_id: guild.id,
            }));
      } else if (targetType === Targets.INVITE) {
        const changes = this.changes.reduce((o, c) => {
          o[c.key] = c.new || c.old;
          return o;
        }, {
          id: data.target_id,
          guild,
        });
        changes.channel = { id: changes.channel_id };
        this.target = new Invite(guild.client, changes);
      } else if (targetType === Targets.MESSAGE) {
        this.target = guild.client.users.get(data.target_id);
      } else {
        this.target = guild[`${targetType.toLowerCase()}s`].get(data.target_id);
      }
    }
  
    /**
     * The timestamp this entry was created at
     * @type {number}
     * @readonly
     */
    get createdTimestamp() {
      return Snowflake.deconstruct(this.id).timestamp;
    }
  
    /**
     * The time this entry was created
     * @type {Date}
     * @readonly
     */
    get createdAt() {
      return new Date(this.createdTimestamp);
    }
  }
  
  GuildAuditLogs.Actions = Actions;
  GuildAuditLogs.Targets = Targets;
  GuildAuditLogs.Entry = GuildAuditLogsEntry;
  
  module.exports = GuildAuditLogs;
  