import Completion from '../../services/completion/completion.js'
import { take } from 'lodash'

/**
 * EmojiInput - augmented inputs for emoji and autocomplete support in inputs
 * without having to give up the comfort of <input/> and <textarea/> elements
 *
 * Intended usage is:
 * <EmojiInput v-model="something">
 *   <input v-model="something"/>
 * </EmojiInput>
 *
 * Works only with <input> and <textarea>. Intended to use with only one nested
 * input. It will find first input or textarea and work with that, multiple
 * nested children not tested. You HAVE TO duplicate v-model for both
 * <emoji-input> and <input>/<textarea> otherwise it will not work.
 *
 * Be prepared for CSS troubles though because it still wraps component in a div
 * while TRYING to make it look like nothing happened, but it could break stuff.
 */

const EmojiInput = {
  props: {
    suggest: {
      /**
       * suggest: function (input: String) => Suggestion[]
       *
       * Function that takes input string which takes string (textAtCaret)
       * and returns an array of Suggestions
       *
       * Suggestion is an object containing following properties:
       * displayText: string. Main display text, what actual suggestion
       *    represents (user's screen name/emoji shortcode)
       * replacement: string. Text that should replace the textAtCaret
       * detailText: string, optional. Subtitle text, providing additional info
       *    if present (user's nickname)
       * imageUrl: string, optional. Image to display alongside with suggestion,
       *    currently if no image is provided, replacement will be used (for
       *    unicode emojis)
       *
       * TODO: make it asynchronous when adding proper server-provided user
       * suggestions
       *
       * For commonly used suggestors (emoji, users, both) use suggestor.js
       */
      required: true,
      type: Function
    },
    value: {
      /**
       * Used for v-model
       */
      required: true,
      type: String
    }
  },
  data () {
    return {
      input: undefined,
      highlighted: 0,
      caret: 0,
      focused: false,
      blurTimeout: null
    }
  },
  computed: {
    suggestions () {
      const firstchar = this.textAtCaret.charAt(0)
      if (this.textAtCaret === firstchar) { return [] }
      const matchedSuggestions = this.suggest(this.textAtCaret)
      if (matchedSuggestions.length <= 0) {
        return []
      }
      return take(matchedSuggestions, 5)
        .map(({ imageUrl, ...rest }, index) => ({
          ...rest,
          // eslint-disable-next-line camelcase
          img: imageUrl || '',
          highlighted: index === this.highlighted
        }))
    },
    showPopup () {
      return this.focused && this.suggestions && this.suggestions.length > 0
    },
    textAtCaret () {
      return (this.wordAtCaret || {}).word || ''
    },
    wordAtCaret () {
      if (this.value && this.caret) {
        const word = Completion.wordAtPosition(this.value, this.caret - 1) || {}
        return word
      }
    }
  },
  mounted () {
    const slots = this.$slots.default
    if (!slots || slots.length === 0) return
    const input = slots.find(slot => ['input', 'textarea'].includes(slot.tag))
    if (!input) return
    this.input = input
    this.resize()
    input.elm.addEventListener('blur', this.onBlur)
    input.elm.addEventListener('focus', this.onFocus)
    input.elm.addEventListener('paste', this.onPaste)
    input.elm.addEventListener('keyup', this.onKeyUp)
    input.elm.addEventListener('keydown', this.onKeyDown)
    input.elm.addEventListener('transitionend', this.onTransition)
    input.elm.addEventListener('compositionupdate', this.onCompositionUpdate)
  },
  unmounted () {
    const { input } = this
    if (input) {
      input.elm.removeEventListener('blur', this.onBlur)
      input.elm.removeEventListener('focus', this.onFocus)
      input.elm.removeEventListener('paste', this.onPaste)
      input.elm.removeEventListener('keyup', this.onKeyUp)
      input.elm.removeEventListener('keydown', this.onKeyDown)
      input.elm.removeEventListener('transitionend', this.onTransition)
      input.elm.removeEventListener('compositionupdate', this.onCompositionUpdate)
    }
  },
  methods: {
    replace (replacement) {
      const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement)
      this.$emit('input', newValue)
      this.caret = 0
    },
    replaceText (e, suggestion) {
      const len = this.suggestions.length || 0
      if (this.textAtCaret.length === 1) { return }
      if (len > 0 || suggestion) {
        const chosenSuggestion = suggestion || this.suggestions[this.highlighted]
        const replacement = chosenSuggestion.replacement
        const newValue = Completion.replaceWord(this.value, this.wordAtCaret, replacement)
        this.$emit('input', newValue)
        this.highlighted = 0
        const position = this.wordAtCaret.start + replacement.length

        this.$nextTick(function () {
          // Re-focus inputbox after clicking suggestion
          this.input.elm.focus()
          // Set selection right after the replacement instead of the very end
          this.input.elm.setSelectionRange(position, position)
          this.caret = position
        })
        e.preventDefault()
      }
    },
    cycleBackward (e) {
      const len = this.suggestions.length || 0
      if (len > 0) {
        this.highlighted -= 1
        if (this.highlighted < 0) {
          this.highlighted = this.suggestions.length - 1
        }
        e.preventDefault()
      } else {
        this.highlighted = 0
      }
    },
    cycleForward (e) {
      const len = this.suggestions.length || 0
      if (len > 0) {
        this.highlighted += 1
        if (this.highlighted >= len) {
          this.highlighted = 0
        }
        e.preventDefault()
      } else {
        this.highlighted = 0
      }
    },
    onTransition (e) {
      this.resize()
    },
    onBlur (e) {
      // Clicking on any suggestion removes focus from autocomplete,
      // preventing click handler ever executing.
      this.blurTimeout = setTimeout(() => {
        this.focused = false
        this.setCaret(e)
        this.resize()
      }, 200)
    },
    onClick (e, suggestion) {
      this.replaceText(e, suggestion)
    },
    onFocus (e) {
      if (this.blurTimeout) {
        clearTimeout(this.blurTimeout)
        this.blurTimeout = null
      }

      this.focused = true
      this.setCaret(e)
      this.resize()
    },
    onKeyUp (e) {
      this.setCaret(e)
      this.resize()
    },
    onPaste (e) {
      this.setCaret(e)
      this.resize()
    },
    onKeyDown (e) {
      this.setCaret(e)
      this.resize()

      const { ctrlKey, shiftKey, key } = e
      if (key === 'Tab') {
        if (shiftKey) {
          this.cycleBackward(e)
        } else {
          this.cycleForward(e)
        }
      }
      if (key === 'ArrowUp') {
        this.cycleBackward(e)
      } else if (key === 'ArrowDown') {
        this.cycleForward(e)
      }
      if (key === 'Enter') {
        if (!ctrlKey) {
          this.replaceText(e)
        }
      }
    },
    onInput (e) {
      this.setCaret(e)
      this.$emit('input', e.target.value)
    },
    onCompositionUpdate (e) {
      this.setCaret(e)
      this.resize()
      this.$emit('input', e.target.value)
    },
    setCaret ({ target: { selectionStart } }) {
      this.caret = selectionStart
    },
    resize () {
      const { panel } = this.$refs
      if (!panel) return
      const { offsetHeight, offsetTop } = this.input.elm
      this.$refs.panel.style.top = (offsetTop + offsetHeight) + 'px'
    }
  }
}

export default EmojiInput
