<script lang="ts" setup>
// 引入Quill编辑器的样式文件
import '@vueup/vue-quill/dist/vue-quill.snow.css'
// 引入图片上传插件的样式
import 'quill-image-uploader/dist/quill.imageUploader.min.css'
// 引入自定义的提及功能样式
import '@/assets/css/editor-mention.less'
// 引入Vue核心功能
import { reactive, watch, ref, markRaw, computed, onMounted, onUnmounted } from 'vue'
// 引入Naive UI的弹出框组件
import { NPopover } from 'naive-ui'
// 引入图标组件
import {
  Voice as IconVoice,    // 语音图标
  SourceCode,            // 代码图标
  Local,                 // 地理位置图标
  SmilingFace,           // 表情图标
  Pic,                   // 图片图标
  FolderUpload,          // 文件上传图标
  Ranking,               // 排名图标（用于投票）
  History                // 历史记录图标
} from '@icon-park/vue-next'
// 引入Quill编辑器及其核心实例
import { QuillEditor, Quill } from '@vueup/vue-quill'
// 引入图片上传插件
import ImageUploader from 'quill-image-uploader'
// 引入自定义表情符号格式
import EmojiBlot from './formats/emoji'
// 引入自定义引用格式
import QuoteBlot from './formats/quote'
// 引入提及功能
import 'quill-mention'
// 引入状态管理
import { useDialogueStore, useEditorDraftStore } from '@/store'
// 引入编辑器工具函数
import { deltaToMessage, deltaToString, isEmptyDelta } from './util'
// 引入获取图片信息的工具函数
import { getImageInfo } from '@/utils/functions'
// 引入编辑器常量定义
import { EditorConst } from '@/constant/event-bus'
// 引入事件调用工具
import { emitCall } from '@/utils/common'
// 引入默认头像常量
import { defAvatar } from '@/constant/default'
// 引入编辑器各子组件
import MeEditorVote from './MeEditorVote.vue'            // 投票组件
import MeEditorEmoticon from './MeEditorEmoticon.vue'    // 表情组件
import MeEditorCode from './MeEditorCode.vue'            // 代码编辑组件
import MeEditorRecorder from './MeEditorRecorder.vue'    // 录音组件
// 引入上传API
import { ServeUploadImage } from '@/api/upload'
import { uploadImg } from '@/api/upload'
// 引入事件总线钩子
import { useEventBus } from '@/hooks'
// 注册Quill编辑器的自定义格式
Quill.register('formats/emoji', EmojiBlot)       // 注册表情格式
Quill.register('formats/quote', QuoteBlot)       // 注册引用格式
Quill.register('modules/imageUploader', ImageUploader)  // 注册图片上传模块

// 定义组件的事件
const emit = defineEmits(['editor-event'])
// 获取对话状态管理
const dialogueStore = useDialogueStore()
// 获取编辑器草稿状态管理
const editorDraftStore = useEditorDraftStore()
// 定义组件props
const props = defineProps({
  vote: {
    type: Boolean,
    default: false  // 是否显示投票功能
  },
  members: {
    default: () => []  // 聊天成员列表，用于@功能
  }
})

// 编辑器引用
const editor = ref()

// 获取Quill编辑器实例
const getQuill = () => {
  return editor.value?.getQuill()
}

// 获取当前编辑器光标位置
const getQuillSelectionIndex = () => {
  let quill = getQuill()

  return (quill.getSelection() || {}).index || quill.getLength()
}

// 计算当前对话索引名称（标识当前聊天）
const indexName = computed(() => dialogueStore.index_name)
// 控制是否显示编辑器的投票界面
const isShowEditorVote = ref(false)
// 控制是否显示编辑器的代码界面
const isShowEditorCode = ref(false)
// 控制是否显示录音界面
const isShowEditorRecorder = ref(false)
// 图片文件上传DOM引用
const fileImageRef = ref()
// 文件上传DOM引用
const uploadFileRef = ref()
// 表情面板引用
const emoticonRef = ref()

// 编辑器配置选项
const editorOption = {
  debug: false,
  modules: {
    toolbar: false,  // 禁用默认工具栏
    clipboard: {
      // 粘贴处理，去除粘贴时的自带样式
      matchers: [[Node.ELEMENT_NODE, onClipboardMatcher]]
    },

    keyboard: {
      bindings: {
        enter: {
          key: 13,
          handler: onSendMessage  // 按Enter键发送消息
        }
      }
    },

    // 图片上传配置
    imageUploader: {
      upload: onEditorUpload
    },

    // @功能配置
    mention: {
      allowedChars: /^[\u4e00-\u9fa5]*$/,  // 允许中文字符
      mentionDenotationChars: ['@'],       // @符号触发
      positioningStrategy: 'fixed',        // 定位策略
      // 渲染@项目的函数
      renderItem: (data: any) => {
        const el = document.createElement('div')
        el.className = 'ed-member-item'
        el.innerHTML = `<img src="${data.avatar}" class="avator"/>`
        el.innerHTML += `<span class="nickname">${data.nickname}</span>`
        return el
      },
      // 数据源函数，过滤匹配的用户
      source: function (searchTerm: string, renderList: any) {
        console.log("source")
        if (!props.members.length) {
          return renderList([])
        }
        let list = [
          ...props.members
        ] as any
if((dialogueStore.groupInfo as any).is_manager){
  list.unshift({ id: 0, nickname: '所有人', avatar: defAvatar, value: '所有人' })
}
        const items = list.filter(
          (item: any) => item.nickname.toLowerCase().indexOf(searchTerm) !== -1
        )

        renderList(items)
      },
      mentionContainerClass: 'ql-mention-list-container me-scrollbar me-scrollbar-thumb'
    }
  },
  placeholder: '按Enter发送 / Shift+Enter 换行',
  theme: 'snow'  // 使用snow主题
}

// 底部工具栏配置
const navs = reactive([
  {
    title: '图片',
    icon: markRaw(Pic),
    show: true,
    click: () => {
      fileImageRef.value.click()  // 触发图片上传
    }
  },
  {
    title: '文件',
    icon: markRaw(FolderUpload),
    show: true,
    click: () => {
      uploadFileRef.value.click()  // 触发文件上传
    }
  },
  // 以下功能已被注释掉，但保留代码
  // {
  //   title: '代码',
  //   icon: markRaw(SourceCode),
  //   show: true,
  //   click: () => {
  //     isShowEditorCode.value = true
  //   }
  // },
  // {
  //   title: '语音消息',
  //   icon: markRaw(IconVoice),
  //   show: true,
  //   click: () => {
  //     isShowEditorRecorder.value = true
  //   }
  // },
  // {
  //   title: '地理位置',
  //   icon: markRaw(Local),
  //   show: true,
  //   click: () => {}
  // },
  // {
  //   title: '群投票',
  //   icon: markRaw(Ranking),
  //   show: computed(() => props.vote),
  //   click: () => {
  //     isShowEditorVote.value = true
  //   }
  // },
  // {
  //   title: '历史记录',
  //   icon: markRaw(History),
  //   show: true,
  //   click: () => {
  //     emit('editor-event', emitCall('history_event'))
  //   }
  // }
])

/**
 * 上传图片函数
 * @param file 文件对象
 * @returns Promise，成功时返回图片URL
 */
function onUploadImage(file: File) {
  return new Promise((resolve) => {
    let image = new Image()
    image.src = URL.createObjectURL(file)
    image.onload = () => {
      const form = new FormData()
      form.append('file', file)
      form.append("source", "fonchain-chat");  // 图片来源标识
      // 添加图片尺寸信息作为URL参数
      form.append("urlParam", `width=${image.width}&height=${image.height}`);

      // 调用上传API
      uploadImg(form).then(({ code, data, message }) => {
        if (code == 0) {
          resolve(data.ori_url)  // 返回原始图片URL
        } else {
          resolve('')
          window['$message'].error(message)  // 显示错误信息
        }
      })
    }
  })
}

/**
 * 编辑器上传处理函数
 * @param file 要上传的文件
 * @returns Promise
 */
function onEditorUpload(file: File) {
  async function fn(file: File, resolve: Function, reject: Function) {
    if (file.type.indexOf('image/') === 0) {
      // 如果是图片，使用图片上传处理
      return resolve(await onUploadImage(file))
    }

    reject()

    // 非图片文件的处理
    if (file.type.indexOf('video/') === 0) {
      // 视频文件
      let fn = emitCall('video_event', file, () => {})
      emit('editor-event', fn)
    } else {
      // 其他文件
      let fn = emitCall('file_event', file, () => {})
      emit('editor-event', fn)
    }
  }

  return new Promise((resolve, reject) => {
    fn(file, resolve, reject)
  })
}

/**
 * 投票事件处理
 * @param data 投票数据
 */
function onVoteEvent(data: any) {
  const msg = emitCall('vote_event', data, (ok: boolean) => {
    if (ok) {
      isShowEditorVote.value = false  // 成功后关闭投票界面
    }
  })

  emit('editor-event', msg)
}

/**
 * 表情事件处理
 * @param data 表情数据
 */
function onEmoticonEvent(data: any) {
  emoticonRef.value.setShow(false)  // 关闭表情面板

  if (data.type == 1) {
    // 插入文本表情
    const quill = getQuill()
    let index = getQuillSelectionIndex()

    // 删除编辑器中多余的换行符
    if (index == 1 && quill.getLength() == 1 && quill.getText(0, 1) == '\n') {
      quill.deleteText(0, 1)
      index = 0
    }

    if (data.img) {
      // 插入图片表情
      quill.insertEmbed(index, 'emoji', {
        alt: data.value,
        src: data.img,
        width: '24px',
        height: '24px'
      })
    } else {
      // 插入文本表情
      quill.insertText(index, data.value)
    }

    // 设置光标位置
    quill.setSelection(index + 1, 0, 'user')
  } else {
    // 发送整个表情包
    let fn = emitCall('emoticon_event', data.value, () => {})
    emit('editor-event', fn)
  }
}

/**
 * 代码事件处理
 * @param data 代码数据
 */
function onCodeEvent(data: any) {
  const msg = emitCall('code_event', data, (ok: boolean) => {
    isShowEditorCode.value = false  // 成功后关闭代码界面
  })

  emit('editor-event', msg)
}

/**
 * 文件上传处理
 * @param e 上传事件对象
 */
async function onUploadFile(e: any) {
  let file = e.target.files[0]

  e.target.value = null  // 清空input，允许再次选择相同文件

  console.log("文件类型"+file.type)
  if (file.type.indexOf('image/') === 0) {
    console.log("进入图片")
    // 处理图片文件 - 立即显示临时消息，然后上传
    let fn = emitCall('image_event', file, () => {})
    emit('editor-event', fn)

    return
  }

  if (file.type.indexOf('video/') === 0) {
    console.log("进入视频")
    // 处理视频文件
    let fn = emitCall('video_event', file, () => {})
    emit('editor-event', fn)
  } else {
    console.log("进入其他")
    // 处理其他类型文件
    let fn = emitCall('file_event', file, () => {})
    emit('editor-event', fn)
  }
}

/**
 * 录音事件处理
 * @param file 录音文件
 */
function onRecorderEvent(file: any) {
  emit('editor-event', emitCall('file_event', file))
  isShowEditorRecorder.value = false  // 关闭录音界面
}

/**
 * 粘贴内容处理，移除粘贴内容中的样式
 * @param node DOM节点
 * @param Delta Quill Delta对象
 * @returns 处理后的Delta
 */
function onClipboardMatcher(node: any, Delta) {
  const ops: any[] = []

  Delta.ops.forEach((op) => {
    // 处理粘贴内容
    if (op.insert && typeof op.insert === 'string') {
      ops.push({
        insert: op.insert, // 文字内容
        attributes: {} // 移除所有样式
      })
    } else {
      ops.push(op)
    }
  })

  Delta.ops = ops
  return Delta
}

/**
 * 发送消息处理
 * 根据编辑器内容类型发送不同类型的消息
 */
function onSendMessage() {
  var delta = getQuill().getContents()
  let data = deltaToMessage(delta)  // 转换Delta为消息格式
  if (data.items.length === 0||!data.items[0].content.trim()) {
    return  // 没有内容不发送
  }

  switch (data.msgType) {
    case 1: // 文字消息
      if (data.items[0].content.length > 1024) {
        return window['$message'].info('发送内容超长，请分条发送')
      }

      // 发送文本消息
      emit(
        'editor-event',
        emitCall('text_event', data, (ok: any) => {
          ok && getQuill().setContents([], Quill.sources.USER)  // 成功发送后清空编辑器
        })
      )
      break
    case 3: // 图片消息
      // 发送图片消息
      emit(
        'editor-event',
        emitCall(
          'image_event',
          { ...getImageInfo(data.items[0].content), url: data.items[0].content, size: 10000 },
          (ok: any) => {
            ok && getQuill().setContents([])  // 成功发送后清空编辑器
          }
        )
      )
      break
    case 12: // 图文混合消息
      // 发送混合消息
      emit(
        'editor-event',
        emitCall('mixed_event', data, (ok: any) => {
          ok && getQuill().setContents([])  // 成功发送后清空编辑器
        })
      )
      break
  }
}

/**
 * 编辑器内容改变时的处理
 * 保存草稿并触发输入事件
 */
function onEditorChange() {
  let delta = getQuill().getContents()
  let text = deltaToString(delta)  // 将Delta转为纯文本

  if (!isEmptyDelta(delta)) {
    // 保存草稿到store
    editorDraftStore.items[indexName.value || ''] = JSON.stringify({
      text: text,
      ops: delta.ops
    })
  } else {
    // 编辑器为空时删除对应草稿
    delete editorDraftStore.items[indexName.value || '']
  }

  // 触发输入事件
  emit('editor-event', emitCall('input_event', text))
}

/**
 * 加载编辑器草稿内容
 * 当切换聊天对象时，加载对应的草稿
 */
function loadEditorDraftText() {
  if (!editor.value) return

  // 延迟处理，确保DOM已渲染
  setTimeout(() => {
    hideMentionDom()  // 隐藏@菜单

    const quill = getQuill()

    if (!quill) return

    // 从缓存中加载编辑器草稿
    let draft = editorDraftStore.items[indexName.value || '']
    if (draft) {
      quill.setContents(JSON.parse(draft)?.ops || [])
    } else {
      quill.setContents([])  // 没有草稿则清空编辑器
    }

    // 设置光标位置到末尾
    const index = getQuillSelectionIndex()
    quill.setSelection(index, 0, 'user')
  }, 0)
}

/**
 * 处理@成员事件
 * @param data @成员数据
 */
function onSubscribeMention(data: any) {
  const mention = getQuill().getModule('mention')
  // 插入@项
  mention.insertItem({ id: data?.id, denotationChar: '@', value: data.value }, true)
}

/**
 * 处理引用事件
 * @param data 引用数据
 */
function onSubscribeQuote(data: any) {
  // 检查是否已有引用内容
  const delta = getQuill().getContents()
  if (delta.ops?.some((item: any) => item.insert.quote)) {
    return  // 已有引用则不再添加
  }

  const quill = getQuill()
  const index = getQuillSelectionIndex()

  // 在编辑器开头插入引用
  quill.insertEmbed(0, 'quote', data)
  quill.setSelection(index + 1, 0, 'user')  // 设置光标到引用后
}

/**
 * 隐藏@成员DOM元素
 */
function hideMentionDom() {
  let el = document.querySelector('.ql-mention-list-container')
  if (el) {
    document.querySelector('body')?.removeChild(el)
  }
}

/**
 * 处理编辑消息事件
 * @param data 消息数据
 */
function onSubscribeEdit(data: any) {
  const quill = getQuill()
  if (!quill) return
  
  // 清空当前编辑器内容
  quill.setContents([])
  
  // 插入要编辑的文本内容
  quill.setText(data.content)
  
  // 设置光标位置到末尾
  const index = quill.getLength() - 1
  quill.setSelection(index > 0 ? index : 0, 0, 'user')
}

// 监听聊天索引变化，切换聊天时加载对应草稿
watch(indexName, loadEditorDraftText, { immediate: true })

// 组件挂载时初始化
onMounted(() => {
  loadEditorDraftText()
})

// 组件卸载时清理
onUnmounted(() => {
  hideMentionDom()
})

// 订阅编辑器相关事件总线事件
useEventBus([
  { name: EditorConst.Mention, event: onSubscribeMention },  // @成员事件
  { name: EditorConst.Quote, event: onSubscribeQuote },       // 引用事件
  { name: EditorConst.Edit, event: onSubscribeEdit }          // 编辑消息事件
])
</script>

<template>
  <!-- 编辑器容器 -->
  <section class="el-container editor">
    <section class="el-container is-vertical">
      <!-- 工具栏区域 -->
      <header class="el-header toolbar bdr-t">
        <div class="tools">
          <!-- 表情选择器弹出框 -->
          <n-popover
            placement="top-start"
            trigger="click"
            raw
            :show-arrow="false"
            :width="300"
            ref="emoticonRef"
            style="width: 500px; height: 250px; border-radius: 10px; overflow: hidden"
          >
            <template #trigger>
              <div class="item pointer">
                <n-icon size="18" class="icon" :component="SmilingFace" />
                <p class="tip-title">表情符号</p>
              </div>
            </template>

            <MeEditorEmoticon @on-select="onEmoticonEvent" />
          </n-popover>

          <!-- 工具栏其他功能按钮 -->
          <div
            class="item pointer"
            v-for="nav in navs"
            :key="nav.title"
            v-show="nav.show"
            @click="nav.click"
          >
            <n-icon size="18" class="icon" :component="nav.icon" />
            <p class="tip-title">{{ nav.title }}</p>
          </div>
        </div>
      </header>

      <!-- 编辑器主体区域 -->
      <main class="el-main height100">
        <QuillEditor
          ref="editor"
          id="editor"
          :options="editorOption"
          @editorChange="onEditorChange"
          style="height: 100%; border: none"
        />
      </main>
    </section>
  </section>

  <!-- 隐藏的文件上传表单 -->
  <form enctype="multipart/form-data" style="display: none">
    <input type="file" ref="fileImageRef" accept="image/*" @change="onUploadFile" />
    <input type="file" ref="uploadFileRef" @change="onUploadFile" />
  </form>

  <!-- 条件渲染的功能组件 -->
  <MeEditorVote v-if="isShowEditorVote" @close="isShowEditorVote = false" @submit="onVoteEvent" />

  <MeEditorCode
    v-if="isShowEditorCode"
    @on-submit="onCodeEvent"
    @close="isShowEditorCode = false"
  />

  <MeEditorRecorder
    v-if="isShowEditorRecorder"
    @on-submit="onRecorderEvent"
    @close="isShowEditorRecorder = false"
  />
</template>

<style lang="less" scoped>
.editor {
  --tip-bg-color: rgb(241 241 241 / 90%);  /* 提示背景颜色 */

  height: 100%;

  .toolbar {
    height: 38px;
    display: flex;

    .tools {
      height: 100%;
      flex: auto;
      display: flex;

      .item {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 35px;
        margin: 0 2px;
        position: relative;
        user-select: none;

        .tip-title {
          display: none;  /* 默认隐藏提示文字 */
          position: absolute;
          top: 40px;
          left: 0px;
          line-height: 26px;
          background-color: var(--tip-bg-color);
          color: var(--im-text-color);
          min-width: 20px;
          font-size: 12px;
          padding: 0 5px;
          border-radius: 2px;
          white-space: pre;
          user-select: none;
          z-index: 999999999999;
        }

        &:hover {
          .tip-title {
            display: block;  /* 悬停时显示提示文字 */
          }
        }
      }
    }
  }
}

/* 暗色模式样式调整 */
html[theme-mode='dark'] {
  .editor {
    --tip-bg-color: #48484d;
  }
}
</style>

<style lang="less">
/* 全局编辑器样式 */
#editor {
  overflow: hidden;
}

/* 编辑器主体区域样式 */
.ql-editor {
  padding: 8px;

  /* 滚动条样式 */
  &::-webkit-scrollbar {
    width: 3px;
    height: 3px;
    background-color: unset;
  }

  &::-webkit-scrollbar-thumb {
    border-radius: 3px;
    background-color: transparent;
  }

  /* 悬停时显示滚动条 */
  &:hover {
    &::-webkit-scrollbar-thumb {
      background-color: var(--im-scrollbar-thumb);
    }
  }
}

/* 编辑器占位符样式 */
.ql-editor.ql-blank::before {
  font-family:
    PingFang SC,
    Microsoft YaHei,
    'Alibaba PuHuiTi 2.0 45' !important;
  left: 8px;
}

/* 编辑器中图片样式 */
.ql-snow .ql-editor img {
  max-width: 100px;
  border-radius: 3px;
  background-color: #48484d;
  margin: 0px 2px;
}

/* 图片上传中样式 */
.image-uploading {
  display: flex;
  width: 100px;
  height: 100px;
  background: #f5f5f5;
  border-radius: 5px;

  img {
    filter: unset;
    display: none;
  }
}

/* 表情符号样式 */
.ed-emoji {
  background-color: unset !important;
}

/* 编辑器占位符样式 */
.ql-editor.ql-blank::before {
  font-style: unset;
  color: #b8b3b3;
}

/* 引用卡片样式 */
.quote-card-content {
  display: flex;
  background-color: #f6f6f6;
  flex-direction: column;
  padding: 8px;
  margin-bottom: 5px;
  cursor: pointer;
  user-select: none;

  .quote-card-title {
    height: 22px;
    line-height: 22px;
    font-size: 12px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: flex;
    justify-content: space-between;

    .quote-card-remove {
      margin-right: 15px;
      font-size: 18px;
    }
  }

  .quote-card-meta {
    margin-top: 4px;
    font-size: 12px;
    line-height: 20px;
    color: #999;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* 暗色模式下的样式调整 */
html[theme-mode='dark'] {
  .ql-editor.ql-blank::before {
    color: #57575a;
  }

  .quote-card-content {
    background-color: var(--im-message-bg-color);
  }
}
</style>
