<template>
  <div class="custom-at-box">
    <div class="custom-textarea-box">
      <div
        ref="customInput"
        :class="[
          'custom-textarea custom-scroll',
          { 'show-word-limit': showWordLimit && maxlength },
          { 'custom-textarea-disabled': disabled },
        ]"
        :contenteditable="!disabled"
        :placeholder="placeholder"
        @input="onInput($event)"
        @keydown="onKeyDownInput($event)"
        @paste="onPaste($event)"
        @copy="onCopy($event)"
        @click="showList(false)"
      />
    </div>
    <div :key="`customInput${taskPanelIsInFullScreen}`">
      <el-popover
        ref="popoverRef"
        v-model="showPopover"
        trigger="click"
        class="custom-select-box"
        :append-to-body="taskPanelPopoverAppendToBody"
        :style="{ top: popoverOffset + 'px' }"
        @hide="hidePoppver"
      >
        <div
          ref="customSelectContent"
          class="custom-select-content custom-scroll"
        >
          <div
            v-for="(item, index) in dataList"
         
            :key="index"
            :class="[
              'custom-select-item',
              { hoverItem: selectedIndex === index },
            ]"
            @click="handleClickOperatorItem(item)"
          >
            <div class="custom-select-item-content">
              {{ item }}
            </div>
          </div>
        </div>
      </el-popover>
    </div>
  </div>
</template>
<script>
import Vue from 'vue'

export default {
  props: {
    // 输入框placeholder
    placeholder: {
      type: String,
      default: '请输入...'
    },
    // 是否显示输入字数统计
    showWordLimit: {
      type: Boolean,
      default: true
    },
    // 是否禁用
    disabled: {
      type: Boolean,
      default: false
    },
    // 最大输入长度
    maxlength: {
      type: [Number, String],
      default: '3000'
    },
    // 输入框高度
    height: {
      type: String,
      default: '100px'
    },
    setRefresh: {
      type: Object,
      default: () => {}
    },
    dataList: {
      type: Array,
      default: () => []
    },
    // 输入框输入的内容
    value: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      // 已输入内容的长度
      inputValueLen: 0,
      top: '',
      left: '',
      message: '',
      startOffset: 0,
      // @搜索人dom
      searSpan: null,

      // 筛选人数据加载状态
      searchOperatorLoad: false,
      // @插入位置
      selectionIndex: 0,
      // 当前编辑的dom
      dom: null,
      // 当前编辑dom的index
      domIndex: 0,
      // 当前编辑dom的childNodes的index
      childDomIndex: 0,
      // 编辑前dom内容
      beforeDomVal: '',
      // 筛选人选择框
      showPopover: false,
      // 筛选人选择框偏移量
      popoverOffset: 0,
      listInput: false,
      listInputValue: '',
      // 防抖
      timer: null,
      // 保存弹窗加载状态
      addDataLoad: false,

      // 鼠标选择人的索引
      selectedIndex: 0
    }
  },
  computed: {
    // 计算属性，用于同步父组件的数据
    model: {
      get() {
        return this.value
      },
      set(newValue) {
        this.$emit('input', newValue)
        if (this.$refs.customInput) {
          this.$emit('inputText', this.$refs.customInput.textContent)
        }
        const nodeList = this.$refs.customInput.childNodes
        const list = []
        nodeList.forEach((e) => {
          if (e.childNodes) {
            e.childNodes.forEach(i => {
              if (i.className === 'active-text') {
                list.push({
                  jobNumber: i.getAttribute('data-id'),
                  name: i.textContent.replace(/{/g, '').replace(/\s/g, '')
                })
              }
            })
          }
          if (e.className === 'active-text') {
            list.push({
              jobNumber: e.getAttribute('data-id'),
              name: e.textContent.replace(/{/g, '').replace(/\s/g, '')
            })
          }
        })
        this.$emit('changeChosen', list)
      }
    },
    taskPanelIsInFullScreen() {
      return this.$store.getters.taskPanelIsInFullScreen
    },
    taskPanelPopoverAppendToBody() {
      return this.$store.getters.taskPanelPopoverAppendToBody
    }
  },
  mounted() {
    this.setNativeInputValue()
  },
  methods: {
    // 设置输入框的值
    setNativeInputValue() {
      if (this.$refs.customInput) {
        if (this.value === this.$refs.customInput.innerHTML) return
        this.$refs.customInput.innerHTML = this.value
        this.inputValueLen = this.$refs.customInput.innerText.length
      }
    },
    // 筛选人弹窗数据选择
    handleClickOperatorItem(item) {
      this.addData(JSON.parse(JSON.stringify(item)))
      this.$refs.customSelectContent.scrollTop = 0
      this.selectedIndex = 0
      this.showPopover = false
      this.listInput = false
      this.listInputValue = ''
    },
    // 艾特人弹窗关闭
    hidePoppver() {
      this.$refs.customSelectContent.scrollTop = 0
      this.selectedIndex = 0
      this.showPopover = false
      this.listInput = false
      this.listInputValue = ''
    },
    // 创建艾特需要插入的元素
    createAtDom(item) {
      // 先判断剩余输入长度是否能够完整插入元素
      const dom = document.createElement('span')

      dom.classList.add('active-text')
      // 这里的contenteditable属性设置为false，删除时可以整块删除
      dom.setAttribute('contenteditable', 'false')
      // 将id存储在dom元素的标签上，便于后续数据处理
      dom.setAttribute('data-id', item)

      dom.innerHTML = `{${item}}&nbsp;`

      return dom
    },
    // 插入元素
    addData(item) {
      const spanElement = this.createAtDom(item)

      const maxlength = Number(this.maxlength) || 3000
      // 因为插入后需要删除之前输入的@，所以判断长度时需要减去这个1
      if (maxlength - this.inputValueLen < spanElement.innerText.length - 1) {
        this.$message('剩余字数不足')
        return
      }
      this.$refs.customInput.focus()

      // 获取当前光标位置的范围
      const selection = window.getSelection()
      const range = selection.getRangeAt(0)

      // 找到要插入的节点
      const nodes = Array.from(this.$refs.customInput.childNodes)
      let insertNode = ''
      // 是否是子元素
      let domIsCustomInputChild = true
      if (nodes[this.domIndex].nodeType === Node.TEXT_NODE) {
        insertNode = nodes[this.domIndex]
      } else {
        const childNodeList = nodes[this.domIndex].childNodes
        insertNode = childNodeList[this.childDomIndex]
        domIsCustomInputChild = false
      }

      // 如果前一个节点是空的文本节点，@用户无法删除
      // 添加判断条件：如果前一个节点是空的文本节点，则插入一个空的<span>节点
      const html = insertNode.textContent
      // 左边的节点
      const textLeft = document.createTextNode(
        html.substring(0, this.selectionIndex - 1) + ''
      )
      const emptySpan = document.createElement('span')

      // 如果找到了要插入的节点，则在其前面插入新节点
      if (insertNode) {
        if (!textLeft.textContent) {
          if (domIsCustomInputChild) {
            this.$refs.customInput.insertBefore(emptySpan, insertNode)
          } else {
            nodes[this.domIndex].insertBefore(emptySpan, insertNode)
          }
        }
        insertNode.parentNode.insertBefore(spanElement, insertNode.nextSibling)
        // 删除多余的@以及搜索条件
        const textContent = insertNode.textContent.slice(
          0,
          -(1 + this.listInputValue.length)
        )
        if (!textContent && insertNode.nodeName === '#text') {
          insertNode.remove()
        } else {
          insertNode.textContent = textContent
        }
      } else {
        // 如果未找到要插入的节点，则将新节点直接追加到末尾
        this.$refs.customInput.appendChild(spanElement)
      }

      // 将光标移动到 span 元素之后
      const nextNode = spanElement.nextSibling
      range.setStart(
        nextNode || spanElement.parentNode,
        nextNode ? 0 : spanElement.parentNode.childNodes.length
      )
      range.setEnd(
        nextNode || spanElement.parentNode,
        nextNode ? 0 : spanElement.parentNode.childNodes.length
      )
      selection.removeAllRanges()
      selection.addRange(range)

      this.model = this.$refs.customInput.innerHTML
      this.inputValueLen = this.$refs.customInput.innerText.length
      this.showList(false)
    },
    // 检查是否发生了全选操作
    isSelectAll() {
      const selection = window.getSelection()
      return selection.toString() === this.$refs.customInput.innerText
    },
    // 获取输入框是否选中文字
    isSelect() {
      try {
        const selection = window.getSelection()
        return selection.toString().length
      } catch (error) {
        return 0
      }
    },
    // 输入事件
    onKeyDownInput(event) {
      // 获取当前输入框的长度
      const currentLength = this.$refs.customInput.innerText.length
      // 获取最大输入长度限制
      const maxLength = Number(this.maxlength) || 3000

      // 如果按下的键是非控制键并且当前长度已经达到了最大长度限制
      if (currentLength >= maxLength) {
        // 获取按键的 keyCode
        var keyCode = event.keyCode || event.which

        // 检查是否按下了 Ctrl 键
        var ctrlKey = event.ctrlKey || event.metaKey // metaKey 用于 macOS 上的 Command 键

        // 允许的按键：Backspace（8）、Delete（46）、方向键和
        var allowedKeys = [8, 46, 37, 38, 39, 40]

        // 允许的按键 Ctrl+A、Ctrl+C、Ctrl+V
        const allowedCtrlKey = [65, 67, 86]

        // 检查按键是否在允许列表中并且没有执行选中操作
        if (!allowedKeys.includes(keyCode) && !this.isSelect()) {
          if ((allowedCtrlKey.includes(keyCode) && ctrlKey)) {
            return
          }
          // 阻止默认行为
          event.preventDefault()
          return false
        }
      }

      if (this.showPopover) {
        const listElement = this.$refs.customSelectContent
        const itemHeight = listElement.children[0].clientHeight
        if (event.key === 'ArrowDown') {
          // 防止光标移动
          event.preventDefault()
          // 移动选中索引
          if (this.selectedIndex === this.dataList.length - 1) {
            this.selectedIndex = 0 // 跳转到第一项
            listElement.scrollTop = 0 // 滚动到列表顶部
          } else {
            this.selectedIndex++
            const itemBottom = (this.selectedIndex + 1) * itemHeight
            const scrollBottom = listElement.scrollTop + listElement.clientHeight
            if (itemBottom > scrollBottom) {
              listElement.scrollTop += itemHeight
            }
          }
        } else if (event.key === 'ArrowUp') {
          event.preventDefault()
          if (this.selectedIndex === 0) {
            this.selectedIndex = this.dataList.length - 1 // 跳转到最后一项
            listElement.scrollTop = listElement.scrollHeight // 滚动到列表底部
          } else {
            this.selectedIndex--
            const itemTop = this.selectedIndex * itemHeight
            if (itemTop < listElement.scrollTop) {
              listElement.scrollTop -= itemHeight
            }
          }
        } else if (event.key === 'Enter') {
          event.preventDefault()
          if (!this.searchOperatorLoad) {
            this.handleClickOperatorItem(
              this.dataList[this.selectedIndex]
            )
          }
        }
      } else if (event.key === 'Backspace' && this.isSelectAll()) {
        // 如果执行了全选操作并删除，清空输入框内容
        this.$refs.customInput.innerText = ''
        this.model = this.$refs.customInput.innerHTML
        this.inputValueLen = 0
      }
    },
    // 监听输入事件
    onInput(e) {
      this.inputValueLen = this.$refs.customInput.innerText.length
      if (
        ['<div><br></div>', '<br>', '<span></span><br>'].includes(
          this.$refs.customInput.innerHTML
        )
      ) {
        this.$refs.customInput.innerHTML = ''
        this.inputValueLen = 0
      } else if (e.data === '{') {
        // 保存焦点位置
        this.saveIndex()
        this.showList()
        this.listInput = true
      } else if (this.showPopover) {
        const { diffChars } = Vue.internal
        const diffResult = diffChars(
          this.beforeDomVal,
          this.dom.textContent
        )
        let result = ''
        // 遍历差异信息数组
        for (let i = 0; i < diffResult.length; i++) {
          const change = diffResult[i]

          // 如果当前差异是添加或修改类型，则将其添加到结果字符串中
          if (change.added) {
            result += change.value
          } else if (change.removed && change.value === '{') {
            this.showList(false)
            this.listInputValue = ''
          }
        }
        if (this.timer) {
          clearTimeout(this.timer)
        }
        this.listInputValue = result
        this.timer = setTimeout(() => {
          this.remoteMethod()
        }, 300)
      }
      this.model = this.$refs.customInput.innerHTML
    },
    onPaste(event) {
      event.preventDefault()
      // 获取剪贴板中的 HTML 和文本内容
      const html = (event.clipboardData || window.clipboardData).getData(
        'text/html'
      )
      const text = (event.clipboardData || window.clipboardData).getData(
        'text/plain'
      )

      // 设置最大输入限制
      const maxLength = Number(this.maxlength) || 3000

      // 此时加个条件  看鼠标选中的文本长度，剩余可输入长度加上选中文本长度
      const selection1 = window.getSelection()
      const range1 = selection1.getRangeAt(0)
      const clonedSelection = range1.cloneContents()
      let selectTextLen = 0
      if (clonedSelection.textContent && clonedSelection.textContent.length) {
        selectTextLen = clonedSelection.textContent.length
      }

      // 剩余可输入长度
      const remainingLength = maxLength - this.inputValueLen + selectTextLen

      // 过滤掉不可见字符
      const cleanText = text.replace(/\s/g, '')

      // 创建一个临时 div 用于处理粘贴的 HTML 内容
      const tempDiv = document.createElement('div')
      tempDiv.innerHTML = html

      // 过滤掉不需要的内容，例如注释和换行符
      const fragment = document.createDocumentFragment()
      let totalLength = 0

      if (cleanText) {
        if (remainingLength >= cleanText.length) {
          fragment.appendChild(document.createTextNode(cleanText))
        } else {
          const truncatedText = cleanText.substr(0, remainingLength)
          fragment.appendChild(document.createTextNode(truncatedText))
        }
      } else {
        Array.from(tempDiv.childNodes).forEach((node) => {
          const regex = /<span class="active-text" contenteditable="false" data-id="(\d+)">{([^<]+)<\/span>/g
          // 过滤注释和空白节点
          if (
            node.nodeType !== 8 &&
              !(node.nodeType === 3 && !/\S/.test(node.textContent))
          ) {
            const childText = node.textContent || ''
            const childLength = childText.length
            const childHtml = node.outerHTML || node.innerHTML
            // 如果剩余空间足够，插入节点
            if ((regex.exec(childHtml) !== null) && totalLength + childLength <= remainingLength) {
              fragment.appendChild(node.cloneNode(true))
              totalLength += childLength
            } else if (remainingLength - totalLength > 0) {
              // 如果还有剩余长度，不插入节点，插入文本内容
              const lastNodeLength = remainingLength - totalLength
              const truncatedText = childText.substr(0, lastNodeLength)
              fragment.appendChild(document.createTextNode(truncatedText))
              totalLength += truncatedText.length
            } else {
              // 如果添加当前节点的内容会超出剩余可插入长度，则结束循环
              return
            }
          }
        })
      }

      // 插入处理后的内容到光标位置
      const selection = window.getSelection()
      const range = selection.getRangeAt(0)
      range.deleteContents()
      range.insertNode(fragment)

      // 更新输入框内容和长度
      this.model = this.$refs.customInput.innerHTML
      this.inputValueLen = this.$refs.customInput.innerText.length

      // 设置光标位置为插入内容的后面一位
      const newRange = document.createRange()
      newRange.setStart(range.endContainer, range.endOffset)
      newRange.collapse(true)
      selection.removeAllRanges()
      selection.addRange(newRange)
    },
    // 修改默认复制事件
    onCopy(e) {
      e.preventDefault()
      const selection = window.getSelection()
      const range = selection.getRangeAt(0)
      const clonedSelection = range.cloneContents()

      // 检查复制的内容是否包含符合条件的元素
      const hasActiveText =
          clonedSelection.querySelector(
            '.active-text[contenteditable="false"][data-id]'
          ) !== null

      const clipboardData = e.clipboardData || window.clipboardData
      if (hasActiveText) {
        const div = document.createElement('div')
        div.appendChild(clonedSelection)
        const selectedHtml = div.innerHTML
        clipboardData.setData('text/html', selectedHtml)
      } else {
        clipboardData.setData('text/plain', clonedSelection.textContent || '')
      }
    },
    // 保存焦点位置
    async saveIndex() {
      const selection = getSelection()
      this.selectionIndex = selection.anchorOffset
      const nodeList = this.$refs.customInput.childNodes
      const range = selection.getRangeAt(0)

      // 保存当前编辑的dom节点
      for (const [index, value] of nodeList.entries()) {
        // 这里第二个参数要配置成true，没配置有其他的一些小bug
        // (range.startContainer.contains(value) && range.endContainer.contains(value))  是为了处理兼容性问题
        if (
          selection.containsNode(value, true) ||
            (range.startContainer.contains(value) &&
              range.endContainer.contains(value))
        ) {
          if (value.nodeType === Node.TEXT_NODE) {
            this.dom = value
            this.beforeDomVal = value.textContent
            this.domIndex = index
            const selection = window.getSelection()
            const range = selection.getRangeAt(0)
            this.startOffset = range.startOffset - 1
          } else {
            const childNodeList = value.childNodes
            for (const [childIndex, childValue] of childNodeList.entries()) {
              if (selection.containsNode(childValue, true)) {
                this.dom = value
                this.beforeDomVal = value.textContent
                this.domIndex = index
                this.childDomIndex = childIndex
                const selection = window.getSelection()
                const range = selection.getRangeAt(0)
                this.startOffset = range.startOffset - 1
              }
            }
          }
        }
      }
    },
    // 筛选人弹窗
    showList(bool = true) {
      this.showPopover = bool
      if (bool) {
        const offset =
            this.getCursorDistanceFromDivBottom(this.$refs.customInput) || -1
        if (offset < 0) {
          this.popoverOffset = 0
        } else {
          this.popoverOffset = -(offset - 1)
        }
      }
      if (!bool) {
        this.listInputValue = ''
        this.remoteMethod()
      }
    },
    // 获取光标位置
    getCursorDistanceFromDivBottom(editableDiv) {
      // 获取选区
      const selection = window.getSelection()
      // 获取选区的范围
      const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null

      if (range) {
        // 创建一个临时元素来标记范围的结束位置
        const markerElement = document.createElement('span')
        // 插入临时标记元素
        range.insertNode(markerElement)
        markerElement.appendChild(document.createTextNode('\u200B')) // 零宽空格

        // 获取标记元素的位置信息
        const markerOffsetTop = markerElement.offsetTop
        const markerHeight = markerElement.offsetHeight

        // 计算光标距离div底部的距离
        const cursorDistanceFromBottom =
            editableDiv.offsetHeight - (markerOffsetTop + markerHeight)

        // 滚动条距顶部的高度
        const scrollTop = editableDiv.scrollTop || 0
        // 移除临时标记元素
        markerElement.parentNode.removeChild(markerElement)

        // 返回光标距离底部的距离
        return cursorDistanceFromBottom + scrollTop
      }

      // 如果没有选区，则返回-1或者其他错误值
      return -1
    },
    // 搜索筛选人
    async remoteMethod() {
      const query = this.listInputValue
      this.searchOperatorLoad = true

      this.searchOperatorLoad = false
    },
    handleNameShift(item) {
      const name = item.realname || ''
      if (!name) return '--'
      if (name.length > 1) {
        return name.slice(0, 1)
      } else {
        return name
      }
    },
    // 按钮div点击 聚焦textarea
    handleBtnBoxClick() {
      this.$refs.customInput.focus()
    },
    getInnerText() {
      const customInput = this.$refs.customInput
      if (!customInput) return
      return customInput.innerText
    },
    getJobId() {
      const nodeList = this.$refs.customInput.childNodes
      const list = []
      nodeList.forEach((e) => {
        if (e.className === 'active-text') {
          list.push(e.getAttribute('data-id'))
        }
      })
      return list
    },
    clearInput() {
      this.$refs.customInput.innerText = ''
      this.$refs.customInput.innerHTML = ''
      this.inputValueLen = 0
      this.$emit('input', '')
      this.$emit('inputText', '')
      this.$emit('changeChosen', [])
    }
  }
}
</script>
  <style lang="scss" scoped>
  .custom-textarea-btn {
    position: absolute;
    bottom: 1px;
    right: 4px;
    left: 4px;
    text-align: right;
    // background: #fff;
    padding-bottom: 3px;
    .el-button {
      font-size: 12px;
      padding: 4px 10px;
    }
  }
  .custom-textarea-box {
    position: relative;
  }
  .custom-at-limit {
    position: absolute;
    right: 12px;
    bottom: 4px;
    font-size: 12px;
    color: #999;
    line-height: 12px;
  }
  ::v-deep.custom-textarea {
    white-space: pre-wrap;
    height: calc(100vh - 500px);
    border: 1px solid #dcdfe6;
    border-radius: 4px;
    background-color: #ffffff;
    padding: 5px 15px;
    color: #606266;
    overflow-y: auto;
    line-height: 20px;
    font-size: 14px;
    transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
    position: relative;
    word-break: break-all;
    &.show-word-limit {
      padding-bottom: 16px;
    }
    &.custom-textarea-disabled {
      cursor: not-allowed;
      background-color: #f5f7fa;
      border-color: #e4e7ed;
      color: #c0c4cc;
    }
    &:focus {
      border-color: #f98600 !important;
    }
    &:empty::before {
      content: attr(placeholder);
      font-size: 14px;
      color: #c0c4cc;
    }
    .active-text {
      color: #909399;
      // padding: 2px 6px;
      // background: #f4f4f5;
      margin-right: 4px;
      // border-radius: 4px;
      // font-size: 12px;
    }
    // &:focus::before {
    //   content: "";
    // }
  }

  ::v-deep.custom-select-box {
    position: relative;

    .el-popover {
      padding: 0;
      top: 0;
      box-shadow: 0 4px 8px 0 rgba(89, 88, 88, 0.8);
    }
    .custom-select-content {
      width: 259px;
      padding: 8px;
      max-height: 260px;
      overflow-y: auto;
    }

    .custom-select-item {
      // font-size: 14px;
      // padding: 0 20px;
      // position: relative;
      // height: 34px;
      // line-height: 34px;
      // box-sizing: border-box;
      display: flex;
      padding: 8px 12px;
      border-bottom: 1px solid #ebebeb;
      align-items: center;
      color: #606266;
      cursor: pointer;
      &:last-child {
        border-bottom: none;
      }
      .avatar-box {
        flex-shrink: 0;
        .custom-select-item-avatar {
          width: 24px;
          height: 24px;
          background-color: #ffb803;
          border-radius: 50%;
          text-align: center;
          line-height: 24px;
          color: #ffffff;
        }
      }
      .custom-select-item-content {
        flex: 1;
        padding-left: 12px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }
      &:hover {
        background-color: #f5f7fa;
      }
      &.hoverItem {
        background-color: #dbdbdb;
      }
    }
    .custom-select-empty {
      padding: 10px 0;
      text-align: center;
      color: #999;
      font-size: 14px;

      &.load {
        display: flex;
        align-items: center;
        justify-content: center;
      }
    }
    .custom-scroll {
      overflow: auto;
      &::-webkit-scrollbar {
        width: 8px;
        height: 8px;
      }
      &::-webkit-scrollbar-thumb {
        border-radius: 8px;
        background-color: #b4b9bf;
      }
    }
  }
  </style>
