<template>
  <div class="editor" v-click-outside="hideMenuInstance">
    <div 
      class="prosemirror-editor"
      ref="editorViewRef"
    ></div>
  
    <div class="menu" ref="menuRef">
      <button :class="{ 'active': attr?.bold }" @click="execCommand('bold')"><IconTextBold /></button>
      <button :class="{ 'active': attr?.em }" @click="execCommand('em')"><IconTextItalic /></button>
      <button :class="{ 'active': attr?.underline }" @click="execCommand('underline')"><IconTextUnderline /></button>
      <button :class="{ 'active': attr?.strikethrough }" @click="execCommand('strikethrough')"><IconStrikethrough /></button>
      <Popover trigger="click" style="width: 30%;">
        <template #content>
          <ColorPicker :modelValue="attr?.color" @update:modelValue="value => execCommand('color', value)" />
        </template>
        <button><IconText /></button>
      </Popover>
      <Popover trigger="click" style="width: 30%;">
        <template #content>
          <ColorPicker :modelValue="attr?.backcolor" @update:modelValue="value => execCommand('backcolor', value)" />
        </template>
        <button><IconHighLight /></button>
      </Popover>
      <button :class="{ 'active': attr?.bulletList }" @click="execCommand('bulletList')"><IconList /></button>
      <button :class="{ 'active': attr?.orderedList }" @click="execCommand('orderedList')"><IconOrderedList /></button>
      <button @click="execCommand('clear')"><IconFormat /></button>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from 'vue'
import { debounce } from 'lodash'
import { useMainStore } from '@/store'
import type { EditorView } from 'prosemirror-view'
import { initProsemirrorEditor, createDocument } from '@/utils/prosemirror'
import { addMark, autoSelectAll, getTextAttrs, type TextAttrs } from '@/utils/prosemirror/utils'
import { toggleList } from '@/utils/prosemirror/commands/toggleList'
import tippy, { type Instance } from 'tippy.js'

import ColorPicker from '@/components/ColorPicker/index.vue'
import Popover from '@/components/Popover.vue'
import { toggleMark } from 'prosemirror-commands'

const props = defineProps<{
  value: string
}>()

const emit = defineEmits<{
  (event: 'update', payload: string): void
}>()

const mainStore = useMainStore()

const editorViewRef = ref<HTMLElement>()
let editorView: EditorView

const attr = ref<TextAttrs>()

const menuInstance = ref<Instance>()
const menuRef = ref<HTMLElement>()

const hideMenuInstance = () => {
  if (menuInstance.value) menuInstance.value.hide()
}

const handleInput = debounce(function() {
  emit('update', editorView.dom.innerHTML)
}, 300, { trailing: true })

const handleFocus = () => {
  mainStore.setDisableHotkeysState(true)
}

const handleBlur = () => {
  mainStore.setDisableHotkeysState(false)
}

const updateTextContent = () => {
  if (!editorView) return
  const { doc, tr } = editorView.state
  editorView.dispatch(tr.replaceRangeWith(0, doc.content.size, createDocument(props.value)))
}

defineExpose({ updateTextContent })

const handleMouseup = () => {
  const selection = window.getSelection()

  if (
    !selection ||
    !selection.anchorNode ||
    !selection.focusNode ||
    selection.isCollapsed ||
    selection.type === 'Caret' ||
    selection.type === 'None'
  ) return

  const range = selection.getRangeAt(0)

  if (menuInstance.value) {
    attr.value = getTextAttrs(editorView)

    const { x, y, left, top } = range.getBoundingClientRect()

    menuInstance.value.setProps({
      getReferenceClientRect: () => ({
        x, y, left, top,
        height: 0,
        width: 0,
        right: left,
        bottom: top,
      } as DOMRect),
    })
    menuInstance.value.show()
  }
}

const execCommand = (command: string, value?: string) => {
  if (command === 'color' && value) {
    const mark = editorView.state.schema.marks.forecolor.create({ color: value })
    autoSelectAll(editorView)
    addMark(editorView, mark)
  }
  else if (command === 'backcolor' && value) {
    const mark = editorView.state.schema.marks.backcolor.create({ backcolor: value })
    autoSelectAll(editorView)
    addMark(editorView, mark)
  }
  else if (command === 'bold') {
    autoSelectAll(editorView)
    toggleMark(editorView.state.schema.marks.strong)(editorView.state, editorView.dispatch)
  }
  else if (command === 'em') {
    autoSelectAll(editorView)
    toggleMark(editorView.state.schema.marks.em)(editorView.state, editorView.dispatch)
  }
  else if (command === 'underline') {
    autoSelectAll(editorView)
    toggleMark(editorView.state.schema.marks.underline)(editorView.state, editorView.dispatch)
  }
  else if (command === 'strikethrough') {
    autoSelectAll(editorView)
    toggleMark(editorView.state.schema.marks.strikethrough)(editorView.state, editorView.dispatch)
  }
  else if (command === 'bulletList') {
    const { bullet_list: bulletList, list_item: listItem } = editorView.state.schema.nodes
    toggleList(bulletList, listItem)(editorView.state, editorView.dispatch)
  }
  else if (command === 'orderedList') {
    const { ordered_list: orderedList, list_item: listItem } = editorView.state.schema.nodes
    toggleList(orderedList, listItem)(editorView.state, editorView.dispatch)
  }
  else if (command === 'clear') {
    autoSelectAll(editorView)
    const { $from, $to } = editorView.state.selection
    editorView.dispatch(editorView.state.tr.removeMark($from.pos, $to.pos))
  }

  editorView.focus()
  handleInput()
  attr.value = getTextAttrs(editorView)
}

onMounted(() => {
  editorView = initProsemirrorEditor((editorViewRef.value as Element), props.value, {
    handleDOMEvents: {
      focus: handleFocus,
      blur: handleBlur,
      mouseup: handleMouseup,
      mousedown: () => {
        window.getSelection()?.removeAllRanges()
        hideMenuInstance()
      },
      keydown: hideMenuInstance,
      input: handleInput,
    },
  }, {
    placeholder: '点击输入演讲者备注',
  })

  menuInstance.value = tippy(editorViewRef.value!, {
    duration: 0,
    content: menuRef.value!,
    interactive: true,
    trigger: 'manual',
    placement: 'top-start',
    hideOnClick: 'toggle',
    offset: [0, 6],
  })
})

onUnmounted(() => {
  editorView && editorView.destroy()
})
</script>

<style lang="scss" scoped>
.editor {
  height: 100%;
  overflow: auto;
}
.prosemirror-editor {
  height: 100%;
  cursor: text;

  ::v-deep(.ProseMirror) {
    height: 100%;
    font-size: 12px;
    overflow: auto;
    padding: 8px;
    line-height: 1.5;

    & > p[data-placeholder]::before {
      content: attr(data-placeholder);
      pointer-events: none;
      position: absolute;
      font-size: 12px;
      color: rgba(#666, 0.5);
    }
  }
}
.menu {
  display: flex;
  background-color: #fff;
  padding: 6px 4px;
  border-radius: $borderRadius;
  box-shadow: 0 0 20px 0 rgba(0, 0, 0, .15);

  button {
    outline: 0;
    border: 0;
    background-color: #fff;
    padding: 3px;
    border-radius: $borderRadius;
    font-size: 16px;
    margin: 0 3px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;

    &:hover, &.active {
      background-color: $themeColor;
      color: #fff;
    }
  }
}
</style>
