<template>
  <div ref="editor" class="notranslate">
    <div v-show="showSuggestions" ref="suggestions">
      <template v-if="(filteredSuggestions || []).length">
        <div
          v-for="(item, index) in filteredSuggestions.slice(0, 10)"
          :key="index"
          :class="[$style.suggestion, navigatedSuggestionIndex === index && $style.selected]"
          @click="selectSuggestion(item)"
        >
          <template v-if="suggestionType === 'emoji'">
            <template v-if="item.native">{{ item.native }}</template>
            <img v-else :src="item.imageUrl" :class="$style.customEmoji" />
            <span class="m-left-xs">{{ item.colons }}</span>
          </template>
        </div>
      </template>
      <div v-else :class="[$style.suggestion, $style.empty]">{{ noSuggestionsMessage }}</div>
    </div>

    <editor-content dir="auto" :editor="editor" :class="$style.editor" />

    <div :class="$style.footer">
      <tippy ref="emojis" trigger="click" placement="top-end" interactive :arrow="false" theme="light">
        <template #trigger>
          <button :class="[$style.button, emojiPickerVisible() && $style.active]" type="button">
            <emoji-icon class="icon-s" />
          </button>
        </template>
        <picker
          native
          emoji-tooltip
          auto-focus
          color="#475DE5"
          title="Pick your favorite"
          emoji="starklaus"
          :data="emojiIndex"
          :class="$style.emojiPicker"
          @select="selectEmoji"
        />
      </tippy>
    </div>
  </div>
</template>

<script>
import tippy from 'tippy.js'
import { TippyComponent } from 'vue-tippy'
import { isEqual, debounce } from 'lodash-es'
import { Picker } from 'emoji-mart-vue-fast'
import { Editor, EditorContent } from '@tiptap/vue-2'
import { Extension } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import 'emoji-mart-vue-fast/css/emoji-mart.css'

import { EmojiSearch, Klausmoji, insertHTML, getEmojis } from './utils/editorPlugins'
import EmojiIcon from './assets/smile.svg'

export default {
  name: 'CommentEditor',
  components: {
    EditorContent,
    Picker,
    Tippy: TippyComponent,
    EmojiIcon,
  },
  props: {
    value: String,
  },
  data() {
    return {
      filteredSuggestions: [],
      suggestionType: '',
      suggestionQuery: null,
      suggestionRange: null,
      suggestionPopup: null,
      navigatedSuggestionIndex: 0,
      insertSuggestion: () => undefined,
      val: this.value || '',
      suggestions: [],
      emojiIndex: getEmojis(),
      editor: new Editor({
        extensions: [
          Klausmoji.configure({ HTMLAttributes: { class: 'klausmoji', style: 'vertical-align: text-bottom;' } }),
          EmojiSearch.configure({
            mentionClass: 'emoji',
            suggestion: {
              char: ':',
              items: ({ query: q }) => {
                if (!q) {
                  this.destroySuggestionPopup()
                  return []
                }
                return this.emojiIndex.search(q)
              },
              render: () => ({
                onStart: (args) => {
                  this.suggestionType = 'emoji'
                  this.onStartSuggestions(args)
                },
                onUpdate: this.onUpdateSuggestions,
                onExit: async () => {
                  await this.$nextTick()
                  this.resetMentions()
                },
                onKeyDown: this.onKeyDownHandler,
              }),
            },
          }),
        ],
        content: this.value || '',
        onUpdate: () => {
          this.setValues()
        },
      }),
    }
  },
  computed: {
    showSuggestions() {
      if (this.suggestionType !== 'emoji') return false

      return !!this.suggestionQuery || (this.filteredSuggestions || []).length
    },
    noSuggestionsMessage() {
      return this.$t('conversations.sidebar.no_items_found')
    },
  },
  beforeDestroy() {
    this.editor.destroy()
    this.suggestions = []
  },
  methods: {
    focus() {
      this.editor.commands.focus()
    },
    setValues() {
      const html = this.editor.getHTML()
      this.val = html === '<p></p>' ? '' : html
      this.suggestions = this.getSuggestions(this.editor.getJSON())
    },
    getSuggestions(obj) {
      const array = Array.isArray(obj) ? obj : [obj]
      return array
        .filter(({ type }) => type !== 'codeBlock') // Disable suggestions inside code block
        .filter(({ marks }) => !(marks && marks.some(({ type }) => type === 'code'))) // Disable suggestions inside code
        .reduce((suggestion, value) => {
          if (value.content) {
            suggestion = suggestion.concat(this.getSuggestions(value.content))
          }

          return suggestion
        }, [])
    },
    // navigate to the previous item
    // if it's the first item, navigate to the last one
    upSuggestionHandler() {
      this.navigatedSuggestionIndex =
        (this.navigatedSuggestionIndex + this.filteredSuggestions.length - 1) % this.filteredSuggestions.length
    },
    // navigate to the next item
    // if it's the last item, navigate to the first one
    downSuggestionHandler() {
      this.navigatedSuggestionIndex = (this.navigatedSuggestionIndex + 1) % this.filteredSuggestions.length
    },
    enterSuggestionHandler() {
      const item = this.filteredSuggestions[this.navigatedSuggestionIndex]
      if (item) this.selectSuggestion(item)
    },
    onKeyDownHandler({ event }) {
      // pressing up arrow
      if (event.key === 'ArrowUp') {
        this.upSuggestionHandler()
        return true
      }
      // pressing down arrow
      if (event.key === 'ArrowDown') {
        this.downSuggestionHandler()
        return true
      }
      // pressing enter
      if (event.key === 'Enter') {
        this.enterSuggestionHandler()
        tippy.hideAll()
        return true
      }
      if (event.key === 'Space') {
        // Check if there's a new tag leading up to the cursor
        this.enterSuggestionHandler()
        tippy.hideAll()
        return true
      }
      if (event.key === 'Escape') {
        tippy.hideAll()
        this.resetMentions()
        return true
      }
      return false
    },
    // we have to replace our suggestion text with a mention
    // so it's important to pass also the position of your suggestion text
    async selectSuggestion(item) {
      // TODO: This can be replaced with this.editor.commands.mention()
      // That way we don't need to store insertSuggestion in the suggestion
      // start handler
      if (this.suggestionType === 'emoji' && item.custom) {
        this.selectEmoji(item)
        this.destroySuggestionPopup()
      } else if (typeof item !== 'string' && 'name' in item && !item.name) {
        this.destroySuggestionPopup()
        return this.focus()
      }

      const label = item.custom ? '' : item.native || item.name || item.replace('#', '')
      this.insertSuggestion({
        id: item.id || null,
        label,
      })
      this.focus()
    },
    renderSuggestionPopup() {
      if (!this.showSuggestions) return
      if (this.suggestionPopup) {
        this.suggestionPopup.popperInstance.update()
        return
      }

      this.suggestionPopup = tippy(this.$el, {
        content: this.$refs.suggestions,
        trigger: 'mouseenter',
        interactive: true,
        theme: 'light left-align',
        placement: 'top-start',
        allowHTML: true,
        inertia: true,
        duration: [400, 200],
        maxWidth: 400,
        showOnInit: true,
        sticky: true,
        arrow: false,
        animateFill: false,
      })
    },
    destroySuggestionPopup() {
      if (this.suggestionPopup && 'destroy' in this.suggestionPopup) this.suggestionPopup.destroy()
      this.suggestionPopup = null
    },
    onStartSuggestions({ items, query, range, command }) {
      this.suggestionQuery = query
      this.filteredSuggestions = items
      this.suggestionRange = range
      this.renderSuggestionPopup()
      this.insertSuggestion = command
    },
    onUpdateSuggestions({ items, query, range, command }) {
      this.suggestionQuery = query
      this.filteredSuggestions = items
      this.suggestionRange = range
      this.navigatedSuggestionIndex = 0
      this.renderSuggestionPopup()
      this.insertSuggestion = command
    },
    resetMentions() {
      this.suggestionType = ''
      this.suggestionQuery = null
      this.filteredSuggestions = []
      this.suggestionRange = null
      this.navigatedSuggestionIndex = 0
      this.destroySuggestionPopup()
    },
    emojiPickerVisible() {
      return this.$refs.emojis?.tip.state.isVisible
    },
    selectEmoji(emj) {
      if (emj.custom) this.editor.commands.setKlausmoji({ src: emj.imageUrl })
      // TODO: Insert whitespace after native emoji
      else insertHTML(this.editor, `${emj.native}&nbsp;`)
      this.$refs.emojis.tip.hide()
      setTimeout(() => this.editor.commands.focus('end'), 0)
    },
  },
}
</script>
