text: const { once } = require('events')
  const { printCallbackDepreciation } = require('../promise_utils')
  
  module.exports = inject
  
  function inject (bot, options) {
    const CHAT_LENGTH_LIMIT = options.chatLengthLimit ?? (bot.supportFeature('lessCharsInChat') ? 100 : 256)
    const defaultChatPatterns = options.defaultChatPatterns ?? true
  
    const ChatMessage = require('prismarine-chat')(bot.version)
    // chat.pattern.type will emit an event for bot.on() of the same type, eg chatType = whisper will trigger bot.on('whisper')
    const _patterns = {}
    let _length = 0
    // deprecated
    bot.chatAddPattern = (patternValue, typeValue) => {
      return bot.addChatPattern(typeValue, patternValue, { deprecated: true })
    }
  
    bot.addChatPatternSet = (name, patterns, opts = {}) => {
      if (!patterns.every(p => p instanceof RegExp)) throw new Error('Pattern parameter should be of type RegExp')
      const { repeat = true, parse = false } = opts
      _patterns[_length++] = {
        name,
        patterns,
        position: 0,
        matches: [],
        messages: [],
        repeat,
        parse
      }
      return _length
    }
  
    bot.addChatPattern = (name, pattern, opts = {}) => {
      if (!(pattern instanceof RegExp)) throw new Error('Pattern parameter should be of type RegExp')
      const { repeat = true, deprecated = false, parse = false } = opts
      _patterns[_length++] = {
        name,
        patterns: [pattern],
        position: 0,
        matches: [],
        messages: [],
        deprecated,
        repeat,
        parse
      }
      return _length
    }
  
    bot.removeChatPattern = name => {
      if (typeof name === 'number') {
        _patterns[name] = undefined
      } else {
        const matchingPatterns = Object.entries(_patterns).filter(pattern => pattern[1]?.name === name)
        matchingPatterns.forEach(([indexString]) => {
          _patterns[+indexString] = undefined
        })
      }
    }
  
    function findMatchingPatterns (msg) {
      const found = []
      for (const [indexString, pattern] of Object.entries(_patterns)) {
        if (!pattern) continue
        const { position, patterns } = pattern
        if (patterns[position].test(msg)) {
          found.push(+indexString)
        }
      }
      return found
    }
  
    bot.on('messagestr', (msg, _, originalMsg) => {
      const foundPatterns = findMatchingPatterns(msg)
  
      for (const ix of foundPatterns) {
        _patterns[ix].matches.push(msg)
        _patterns[ix].messages.push(originalMsg)
        _patterns[ix].position++
  
        if (_patterns[ix].deprecated) {
          const [, ...matches] = _patterns[ix].matches[0].match(_patterns[ix].patterns[0])
          bot.emit(_patterns[ix].name, ...matches, _patterns[ix].messages[0].translate, ..._patterns[ix].messages)
          _patterns[ix].messages = [] // clear out old messages
        } else { // regular parsing
          if (_patterns[ix].patterns.length > _patterns[ix].matches.length) return // we have all the matches, so we can emit the done event
          if (_patterns[ix].parse) {
            const matches = _patterns[ix].patterns.map((pattern, i) => {
              const [, ...matches] = _patterns[ix].matches[i].match(pattern) // delete full message match
              return matches
            })
            bot.emit(`chat:${_patterns[ix].name}`, matches)
          } else {
            bot.emit(`chat:${_patterns[ix].name}`, _patterns[ix].matches)
          }
          // these are possibly null-ish if the user deletes them as soon as the event for the match is emitted
        }
        if (_patterns[ix]?.repeat) {
          _patterns[ix].position = 0
          _patterns[ix].matches = []
        } else {
          _patterns[ix] = undefined
        }
      }
    })
  
    addDefaultPatterns()
  
    bot._client.on('chat', (packet) => {
      const msg = ChatMessage.fromNotch(packet.message)
  
      const ChatPositions = {
        0: 'chat',
        1: 'system',
        2: 'game_info'
      }
  
      const chatPosition = ChatPositions[packet.position]
  
      bot.emit('message', msg, chatPosition)
      bot.emit('messagestr', msg.toString(), chatPosition, msg)
  
      // Position 2 is the action bar
      if (packet.position === 2) bot.emit('actionBar', msg)
    })
  
    function chatWithHeader (header, message) {
      if (typeof message === 'number') message = message.toString()
      if (typeof message !== 'string') {
        throw new Error('Incorrect type! Should be a string or number.')
      }
  
      const lengthLimit = CHAT_LENGTH_LIMIT - header.length
      message.split('\n').forEach((subMessage) => {
        if (!subMessage) return
        let i
        let smallMsg
        for (i = 0; i < subMessage.length; i += lengthLimit) {
          smallMsg = header + subMessage.substring(i, i + lengthLimit)
          bot._client.write('chat', { message: smallMsg })
        }
      })
    }
  
    async function tabComplete (text, assumeCommand = false, sendBlockInSight = true) {
      let position
  
      if (sendBlockInSight) {
        const block = bot.blockAtCursor()
  
        if (block) {
          position = block.position
        }
      }
  
      bot._client.write('tab_complete', {
        text,
        assumeCommand,
        lookedAtBlock: position
      })
  
      const [packet] = await once(bot._client, 'tab_complete')
      return packet.matches
    }
  
    bot.whisper = (username, message) => {
      chatWithHeader(`/tell ${username} `, message)
    }
    bot.chat = (message) => {
      chatWithHeader('', message)
    }
  
    bot.tabComplete = callbackify(tabComplete)
  
    function addDefaultPatterns () {
      if (!defaultChatPatterns) return
      const USERNAME_REGEX = '(?:\\(.+\\)|\\[.+\\]|.)*?(\\w+)'
      bot.addChatPattern('whisper', new RegExp(`^${USERNAME_REGEX} whispers(?: to you)?:? (.*)$`), { deprecated: true })
      bot.addChatPattern('whisper', new RegExp(`^\\[${USERNAME_REGEX} -> \\w+\\s?\\] (.*)$`), { deprecated: true })
      bot.addChatPattern('chat', new RegExp(`^${USERNAME_REGEX}\\s?[>:\\-»\\]\\)~]+\\s(.*)$`), { deprecated: true })
    }
  
    function awaitMessage (...args) {
      return new Promise((resolve, reject) => {
        const resolveMessages = args.flatMap(x => x)
        function messageListener (msg) {
          if (resolveMessages.some(x => x instanceof RegExp ? x.test(msg) : msg === x)) {
            resolve(msg)
            bot.off('messagestr', messageListener)
          }
        }
        bot.on('messagestr', messageListener)
      })
    }
    bot.awaitMessage = awaitMessage
  }
  
  function callbackify (f) { // specifically for this function because cb isn't the last parameter
    return function (...args) {
      const cb = args[1]
      args.splice(1, 1)
      return f(...args).then(r => {
        if (cb) {
          printCallbackDepreciation()
          cb(undefined, r)
        } return r
      }, err => {
        if (cb) {
          printCallbackDepreciation()
          cb(err)
        } else throw err
      })
    }
  }
  