text: 'use strict';
  
  const CachedManager = require('./CachedManager');
  const { TypeError } = require('../errors');
  const Message = require('../structures/Message');
  const MessagePayload = require('../structures/MessagePayload');
  const Collection = require('../util/Collection');
  
  /**
   * Manages API methods for Messages and holds their cache.
   * @extends {CachedManager}
   */
  class MessageManager extends CachedManager {
    constructor(channel, iterable) {
      super(channel.client, Message, iterable);
  
      /**
       * The channel that the messages belong to
       * @type {TextBasedChannel}
       */
      this.channel = channel;
    }
  
    /**
     * The cache of Messages
     * @type {Collection<Snowflake, Message>}
     * @name MessageManager#cache
     */
  
    _add(data, cache) {
      return super._add(data, cache, { extras: [this.channel] });
    }
  
    /**
     * The parameters to pass in when requesting previous messages from a channel. `around`, `before` and
     * `after` are mutually exclusive. All the parameters are optional.
     * @typedef {Object} ChannelLogsQueryOptions
     * @property {number} [limit=50] Number of messages to acquire
     * @property {Snowflake} [before] The message's id to get the messages that were posted before it
     * @property {Snowflake} [after] The message's id to get the messages that were posted after it
     * @property {Snowflake} [around] The message's id to get the messages that were posted around it
     */
  
    /**
     * Gets a message, or messages, from this channel.
     * <info>The returned Collection does not contain reaction users of the messages if they were not cached.
     * Those need to be fetched separately in such a case.</info>
     * @param {Snowflake|ChannelLogsQueryOptions} [message] The id of the message to fetch, or query parameters.
     * @param {BaseFetchOptions} [options] Additional options for this fetch
     * @returns {Promise<Message>|Promise<Collection<Snowflake, Message>>}
     * @example
     * // Get message
     * channel.messages.fetch('99539446449315840')
     *   .then(message => console.log(message.content))
     *   .catch(console.error);
     * @example
     * // Get messages
     * channel.messages.fetch({ limit: 10 })
     *   .then(messages => console.log(`Received ${messages.size} messages`))
     *   .catch(console.error);
     * @example
     * // Get messages and filter by user id
     * channel.messages.fetch()
     *   .then(messages => console.log(`${messages.filter(m => m.author.id === '84484653687267328').size} messages`))
     *   .catch(console.error);
     */
    fetch(message, { cache = true, force = false } = {}) {
      return typeof message === 'string' ? this._fetchId(message, cache, force) : this._fetchMany(message, cache);
    }
  
    /**
     * Fetches the pinned messages of this channel and returns a collection of them.
     * <info>The returned Collection does not contain any reaction data of the messages.
     * Those need to be fetched separately.</info>
     * @param {boolean} [cache=true] Whether to cache the message(s)
     * @returns {Promise<Collection<Snowflake, Message>>}
     * @example
     * // Get pinned messages
     * channel.messages.fetchPinned()
     *   .then(messages => console.log(`Received ${messages.size} messages`))
     *   .catch(console.error);
     */
    fetchPinned(cache = true) {
      return this.client.api.channels[this.channel.id].pins.get().then(data => {
        const messages = new Collection();
        for (const message of data) messages.set(message.id, this._add(message, cache));
        return messages;
      });
    }
  
    /**
     * Data that can be resolved to a Message object. This can be:
     * * A Message
     * * A Snowflake
     * @typedef {Message|Snowflake} MessageResolvable
     */
  
    /**
     * Resolves a {@link MessageResolvable} to a {@link Message} object.
     * @method resolve
     * @memberof MessageManager
     * @instance
     * @param {MessageResolvable} message The message resolvable to resolve
     * @returns {?Message}
     */
  
    /**
     * Resolves a {@link MessageResolvable} to a {@link Message} id.
     * @method resolveId
     * @memberof MessageManager
     * @instance
     * @param {MessageResolvable} message The message resolvable to resolve
     * @returns {?Snowflake}
     */
  
    /**
     * Edits a message, even if it's not cached.
     * @param {MessageResolvable} message The message to edit
     * @param {MessageEditOptions|MessagePayload} options The options to edit the message
     * @returns {Promise<Message>}
     */
    async edit(message, options) {
      const messageId = this.resolveId(message);
      if (!messageId) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
  
      const { data, files } = await (options instanceof MessagePayload
        ? options
        : MessagePayload.create(message instanceof Message ? message : this, options)
      )
        .resolveData()
        .resolveFiles();
      const d = await this.client.api.channels[this.channel.id].messages[messageId].patch({ data, files });
  
      const existing = this.cache.get(messageId);
      if (existing) {
        const clone = existing._clone();
        clone._patch(d);
        return clone;
      }
      return this._add(d);
    }
  
    /**
     * Publishes a message in an announcement channel to all channels following it, even if it's not cached.
     * @param {MessageResolvable} message The message to publish
     * @returns {Promise<Message>}
     */
    async crosspost(message) {
      message = this.resolveId(message);
      if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
  
      const data = await this.client.api.channels(this.channel.id).messages(message).crosspost.post();
      return this.cache.get(data.id) ?? this._add(data);
    }
  
    /**
     * Pins a message to the channel's pinned messages, even if it's not cached.
     * @param {MessageResolvable} message The message to pin
     * @returns {Promise<void>}
     */
    async pin(message) {
      message = this.resolveId(message);
      if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
  
      await this.client.api.channels(this.channel.id).pins(message).put();
    }
  
    /**
     * Unins a message from the channel's pinned messages, even if it's not cached.
     * @param {MessageResolvable} message The message to unpin
     * @returns {Promise<void>}
     */
    async unpin(message) {
      message = this.resolveId(message);
      if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
  
      await this.client.api.channels(this.channel.id).pins(message).delete();
    }
  
    /**
     * Adds a reaction to a message, even if it's not cached.
     * @param {MessageResolvable} message The messag to react to
     * @param {EmojiIdentifierResolvable} emoji The emoji to react with
     * @returns {Promise<void>}
     */
    async react(message, emoji) {
      message = this.resolveId(message);
      if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
  
      emoji = this.client.emojis.resolveIdentifier(emoji);
      if (!emoji) throw new TypeError('EMOJI_TYPE', 'emoji', 'EmojiIdentifierResolvable');
  
      // eslint-disable-next-line newline-per-chained-call
      await this.client.api.channels(this.channel.id).messages(message).reactions(emoji, '@me').put();
    }
  
    /**
     * Deletes a message, even if it's not cached.
     * @param {MessageResolvable} message The message to delete
     * @returns {Promise<void>}
     */
    async delete(message) {
      message = this.resolveId(message);
      if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
  
      await this.client.api.channels(this.channel.id).messages(message).delete();
    }
  
    async _fetchId(messageId, cache, force) {
      if (!force) {
        const existing = this.cache.get(messageId);
        if (existing && !existing.partial) return existing;
      }
  
      const data = await this.client.api.channels[this.channel.id].messages[messageId].get();
      return this._add(data, cache);
    }
  
    async _fetchMany(options = {}, cache) {
      const data = await this.client.api.channels[this.channel.id].messages.get({ query: options });
      const messages = new Collection();
      for (const message of data) messages.set(message.id, this._add(message, cache));
      return messages;
    }
  }
  
  module.exports = MessageManager;
  