text: 'use strict';
  
  /* eslint-disable import/order */
  const MessageCollector = require('../MessageCollector');
  const APIMessage = require('../APIMessage');
  const Snowflake = require('../../util/Snowflake');
  const Collection = require('../../util/Collection');
  const { RangeError, TypeError } = require('../../errors');
  
  /**
   * Interface for classes that have text-channel-like features.
   * @interface
   */
  class TextBasedChannel {
    constructor() {
      /**
       * A manager of the messages sent to this channel
       * @type {MessageManager}
       */
      this.messages = new MessageManager(this);
  
      /**
       * The ID of the last message in the channel, if one was sent
       * @type {?Snowflake}
       */
      this.lastMessageID = null;
  
      /**
       * The timestamp when the last pinned message was pinned, if there was one
       * @type {?number}
       */
      this.lastPinTimestamp = null;
    }
  
    /**
     * The Message object of the last message in the channel, if one was sent
     * @type {?Message}
     * @readonly
     */
    get lastMessage() {
      return this.messages.cache.get(this.lastMessageID) || null;
    }
  
    /**
     * The date when the last pinned message was pinned, if there was one
     * @type {?Date}
     * @readonly
     */
    get lastPinAt() {
      return this.lastPinTimestamp ? new Date(this.lastPinTimestamp) : null;
    }
  
    /**
     * Options provided when sending or editing a message.
     * @typedef {Object} MessageOptions
     * @property {boolean} [tts=false] Whether or not the message should be spoken aloud
     * @property {string} [nonce=''] The nonce for the message
     * @property {string} [content=''] The content for the message
     * @property {MessageEmbed|Object} [embed] An embed for the message
     * (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details)
     * @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
     * @property {DisableMentionType} [disableMentions=this.client.options.disableMentions] Whether or not all mentions or
     * everyone/here mentions should be sanitized to prevent unexpected mentions
     * @property {FileOptions[]|BufferResolvable[]} [files] Files to send with the message
     * @property {string|boolean} [code] Language for optional codeblock formatting to apply
     * @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if
     * it exceeds the character limit. If an object is provided, these are the options for splitting the message
     * @property {UserResolvable} [reply] User to reply to (prefixes the message with a mention, except in DMs)
     */
  
    /**
     * Options provided to control parsing of mentions by Discord
     * @typedef {Object} MessageMentionOptions
     * @property {MessageMentionTypes[]} [parse] Types of mentions to be parsed
     * @property {Snowflake[]} [users] Snowflakes of Users to be parsed as mentions
     * @property {Snowflake[]} [roles] Snowflakes of Roles to be parsed as mentions
     */
  
    /**
     * Types of mentions to enable in MessageMentionOptions.
     * - `roles`
     * - `users`
     * - `everyone`
     * @typedef {string} MessageMentionTypes
     */
  
    /**
     * The type of mentions to disable.
     * - `none`
     * - `all`
     * - `everyone`
     * @typedef {string} DisableMentionType
     */
  
    /**
     * @typedef {Object} FileOptions
     * @property {BufferResolvable} attachment File to attach
     * @property {string} [name='file.jpg'] Filename of the attachment
     */
  
    /**
     * Options for splitting a message.
     * @typedef {Object} SplitOptions
     * @property {number} [maxLength=2000] Maximum character length per message piece
     * @property {string} [char='\n'] Character to split the message with
     * @property {string} [prepend=''] Text to prepend to every piece except the first
     * @property {string} [append=''] Text to append to every piece except the last
     */
  
    /**
     * Sends a message to this channel.
     * @param {StringResolvable|APIMessage} [content=''] The content to send
     * @param {MessageOptions|MessageAdditions} [options={}] The options to provide
     * @returns {Promise<Message|Message[]>}
     * @example
     * // Send a basic message
     * channel.send('hello!')
     *   .then(message => console.log(`Sent message: ${message.content}`))
     *   .catch(console.error);
     * @example
     * // Send a remote file
     * channel.send({
     *   files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048']
     * })
     *   .then(console.log)
     *   .catch(console.error);
     * @example
     * // Send a local file
     * channel.send({
     *   files: [{
     *     attachment: 'entire/path/to/file.jpg',
     *     name: 'file.jpg'
     *   }]
     * })
     *   .then(console.log)
     *   .catch(console.error);
     * @example
     * // Send an embed with a local image inside
     * channel.send('This is an embed', {
     *   embed: {
     *     thumbnail: {
     *          url: 'attachment://file.jpg'
     *       }
     *    },
     *    files: [{
     *       attachment: 'entire/path/to/file.jpg',
     *       name: 'file.jpg'
     *    }]
     * })
     *   .then(console.log)
     *   .catch(console.error);
     */
    async send(content, options) {
      const User = require('../User');
      const GuildMember = require('../GuildMember');
  
      if (this instanceof User || this instanceof GuildMember) {
        return this.createDM().then(dm => dm.send(content, options));
      }
  
      let apiMessage;
  
      if (content instanceof APIMessage) {
        apiMessage = content.resolveData();
      } else {
        apiMessage = APIMessage.create(this, content, options).resolveData();
        if (Array.isArray(apiMessage.data.content)) {
          return Promise.all(apiMessage.split().map(this.send.bind(this)));
        }
      }
  
      const { data, files } = await apiMessage.resolveFiles();
      return this.client.api.channels[this.id].messages
        .post({ data, files })
        .then(d => this.client.actions.MessageCreate.handle(d).message);
    }
  
    /**
     * Starts a typing indicator in the channel.
     * @param {number} [count=1] The number of times startTyping should be considered to have been called
     * @returns {Promise} Resolves once the bot stops typing gracefully, or rejects when an error occurs
     * @example
     * // Start typing in a channel, or increase the typing count by one
     * channel.startTyping();
     * @example
     * // Start typing in a channel with a typing count of five, or set it to five
     * channel.startTyping(5);
     */
    startTyping(count) {
      if (typeof count !== 'undefined' && count < 1) throw new RangeError('TYPING_COUNT');
      if (this.client.user._typing.has(this.id)) {
        const entry = this.client.user._typing.get(this.id);
        entry.count = count || entry.count + 1;
        return entry.promise;
      }
  
      const entry = {};
      entry.promise = new Promise((resolve, reject) => {
        const endpoint = this.client.api.channels[this.id].typing;
        Object.assign(entry, {
          count: count || 1,
          interval: this.client.setInterval(() => {
            endpoint.post().catch(error => {
              this.client.clearInterval(entry.interval);
              this.client.user._typing.delete(this.id);
              reject(error);
            });
          }, 9000),
          resolve,
        });
        endpoint.post().catch(error => {
          this.client.clearInterval(entry.interval);
          this.client.user._typing.delete(this.id);
          reject(error);
        });
        this.client.user._typing.set(this.id, entry);
      });
      return entry.promise;
    }
  
    /**
     * Stops the typing indicator in the channel.
     * The indicator will only stop if this is called as many times as startTyping().
     * <info>It can take a few seconds for the client user to stop typing.</info>
     * @param {boolean} [force=false] Whether or not to reset the call count and force the indicator to stop
     * @example
     * // Reduce the typing count by one and stop typing if it reached 0
     * channel.stopTyping();
     * @example
     * // Force typing to fully stop regardless of typing count
     * channel.stopTyping(true);
     */
    stopTyping(force = false) {
      if (this.client.user._typing.has(this.id)) {
        const entry = this.client.user._typing.get(this.id);
        entry.count--;
        if (entry.count <= 0 || force) {
          this.client.clearInterval(entry.interval);
          this.client.user._typing.delete(this.id);
          entry.resolve();
        }
      }
    }
  
    /**
     * Whether or not the typing indicator is being shown in the channel
     * @type {boolean}
     * @readonly
     */
    get typing() {
      return this.client.user._typing.has(this.id);
    }
  
    /**
     * Number of times `startTyping` has been called
     * @type {number}
     * @readonly
     */
    get typingCount() {
      if (this.client.user._typing.has(this.id)) return this.client.user._typing.get(this.id).count;
      return 0;
    }
  
    /**
     * Creates a Message Collector.
     * @param {CollectorFilter} filter The filter to create the collector with
     * @param {MessageCollectorOptions} [options={}] The options to pass to the collector
     * @returns {MessageCollector}
     * @example
     * // Create a message collector
     * const filter = m => m.content.includes('discord');
     * const collector = channel.createMessageCollector(filter, { time: 15000 });
     * collector.on('collect', m => console.log(`Collected ${m.content}`));
     * collector.on('end', collected => console.log(`Collected ${collected.size} items`));
     */
    createMessageCollector(filter, options = {}) {
      return new MessageCollector(this, filter, options);
    }
  
    /**
     * An object containing the same properties as CollectorOptions, but a few more:
     * @typedef {MessageCollectorOptions} AwaitMessagesOptions
     * @property {string[]} [errors] Stop/end reasons that cause the promise to reject
     */
  
    /**
     * Similar to createMessageCollector but in promise form.
     * Resolves with a collection of messages that pass the specified filter.
     * @param {CollectorFilter} filter The filter function to use
     * @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector
     * @returns {Promise<Collection<Snowflake, Message>>}
     * @example
     * // Await !vote messages
     * const filter = m => m.content.startsWith('!vote');
     * // Errors: ['time'] treats ending because of the time limit as an error
     * channel.awaitMessages(filter, { max: 4, time: 60000, errors: ['time'] })
     *   .then(collected => console.log(collected.size))
     *   .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`));
     */
    awaitMessages(filter, options = {}) {
      return new Promise((resolve, reject) => {
        const collector = this.createMessageCollector(filter, options);
        collector.once('end', (collection, reason) => {
          if (options.errors && options.errors.includes(reason)) {
            reject(collection);
          } else {
            resolve(collection);
          }
        });
      });
    }
  
    /**
     * Bulk deletes given messages that are newer than two weeks.
     * @param {Collection<Snowflake, Message>|MessageResolvable[]|number} messages
     * Messages or number of messages to delete
     * @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically
     * @returns {Promise<Collection<Snowflake, Message>>} Deleted messages
     * @example
     * // Bulk delete messages
     * channel.bulkDelete(5)
     *   .then(messages => console.log(`Bulk deleted ${messages.size} messages`))
     *   .catch(console.error);
     */
    async bulkDelete(messages, filterOld = false) {
      if (Array.isArray(messages) || messages instanceof Collection) {
        let messageIDs = messages instanceof Collection ? messages.keyArray() : messages.map(m => m.id || m);
        if (filterOld) {
          messageIDs = messageIDs.filter(id => Date.now() - Snowflake.deconstruct(id).date.getTime() < 1209600000);
        }
        if (messageIDs.length === 0) return new Collection();
        if (messageIDs.length === 1) {
          await this.client.api.channels(this.id).messages(messageIDs[0]).delete();
          const message = this.client.actions.MessageDelete.getMessage(
            {
              message_id: messageIDs[0],
            },
            this,
          );
          return message ? new Collection([[message.id, message]]) : new Collection();
        }
        await this.client.api.channels[this.id].messages['bulk-delete'].post({ data: { messages: messageIDs } });
        return messageIDs.reduce(
          (col, id) =>
            col.set(
              id,
              this.client.actions.MessageDeleteBulk.getMessage(
                {
                  message_id: id,
                },
                this,
              ),
            ),
          new Collection(),
        );
      }
      if (!isNaN(messages)) {
        const msgs = await this.messages.fetch({ limit: messages });
        return this.bulkDelete(msgs, filterOld);
      }
      throw new TypeError('MESSAGE_BULK_DELETE_TYPE');
    }
  
    static applyToClass(structure, full = false, ignore = []) {
      const props = ['send'];
      if (full) {
        props.push(
          'lastMessage',
          'lastPinAt',
          'bulkDelete',
          'startTyping',
          'stopTyping',
          'typing',
          'typingCount',
          'createMessageCollector',
          'awaitMessages',
        );
      }
      for (const prop of props) {
        if (ignore.includes(prop)) continue;
        Object.defineProperty(
          structure.prototype,
          prop,
          Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop),
        );
      }
    }
  }
  
  module.exports = TextBasedChannel;
  
  // Fixes Circular
  const MessageManager = require('../../managers/MessageManager');
  