<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'

interface MentionItem {
  id: string
  name: string
  type?: string
  avatar?: string
}

const props = defineProps<{
  modelValue?: string
  placeholder?: string
}>()
const emit = defineEmits<{
  'update:modelValue': [value: string]
  'blur': []
}>()

// DOM 引用
const wrapperRef = ref<HTMLDivElement>()
const editorRef = ref<HTMLDivElement>()

// @提及下拉状态
const showMentionList = ref(false)
const activeIndex = ref(0)
const mentionPosition = ref({ top: 0, left: 0 })
const mentionKeyword = ref('')

// @提及数据源（写在组件内）
const mentionData = ref<MentionItem[]>([
  { id: '1', name: '张三', type: '用户', avatar: '' },
  { id: '2', name: '李四', type: '用户', avatar: '' },
  { id: '3', name: '王五', type: '用户', avatar: '' },
  { id: '4', name: '产品组', type: '群组', avatar: '' },
  { id: '5', name: '设计组', type: '群组', avatar: '' },
])

// 过滤后的列表（关键词为空时返回全部）
const filteredMentionList = computed(() => {
  const kw = mentionKeyword.value.trim().toLowerCase()
  if (!kw) return mentionData.value
  return mentionData.value.filter(
    (item) =>
      item.name.toLowerCase().includes(kw) ||
      (item.type ?? '').toLowerCase().includes(kw),
  )
})

// 关键词变化时重置选中项，避免越界
watch(mentionKeyword, () => {
  activeIndex.value = 0
})

// 从当前光标向前查找最近的 @，要求 @ 与光标之间无空白字符
// 返回需要被替换的范围（@ 到 光标）与关键词
const getActiveMentionRange = (): { range: Range; keyword: string } | null => {
  const sel = window.getSelection()
  if (!sel || sel.rangeCount === 0) return null
  const focusNode = sel.focusNode
  if (!focusNode || focusNode.nodeType !== Node.TEXT_NODE) return null
  const text = focusNode.textContent ?? ''
  const offset = sel.focusOffset
  for (let i = offset - 1; i >= 0; i--) {
    const ch = text[i]
    if (ch === '@') {
      const keyword = text.slice(i + 1, offset)
      const range = document.createRange()
      range.setStart(focusNode, i)
      range.setEnd(focusNode, offset)
      return { range, keyword }
    }
    // 遇到空白字符说明 @ 已失效，停止向前查找
    if (/\s/.test(ch)) return null
  }
  return null
}

// 计算光标相对编辑器 wrapper 的位置，用于定位下拉列表
const updateMentionPosition = () => {
  const wrapper = wrapperRef.value
  const editor = editorRef.value
  if (!wrapper || !editor) return
  const sel = window.getSelection()
  if (!sel || sel.rangeCount === 0) return
  const range = sel.getRangeAt(0).cloneRange()
  range.collapse(true)
  const rect = range.getBoundingClientRect()
  const wrapperRect = wrapper.getBoundingClientRect()
  mentionPosition.value = {
    top: rect.bottom - wrapperRect.top + 4,
    left: rect.left - wrapperRect.left,
  }
}

// 检测当前是否处于 @ 激活态
const detectMention = () => {
  const active = getActiveMentionRange()
  if (active) {
    mentionKeyword.value = active.keyword
    showMentionList.value = true
    updateMentionPosition()
  } else {
    showMentionList.value = false
    mentionKeyword.value = ''
  }
}

// 输出 HTML 给父组件（v-model）
const emitChange = () => {
  if (!editorRef.value) return
  emit('update:modelValue', editorRef.value.innerHTML)
}

// 清理空内容（浏览器清空后常残留 <br>，导致 :empty 失效）
const cleanIfEmpty = () => {
  const editor = editorRef.value
  if (!editor) return
  if ((editor.innerText ?? '').trim() === '') {
    editor.innerHTML = ''
  }
}

const onInput = () => {
  cleanIfEmpty()
  detectMention()
  emitChange()
}

const onKeydown = (event: KeyboardEvent) => {
  if (!showMentionList.value) return
  const list = filteredMentionList.value
  switch (event.key) {
    case 'ArrowDown':
      event.preventDefault()
      if (list.length === 0) return
      activeIndex.value = (activeIndex.value + 1) % list.length
      scrollActiveIntoView()
      break
    case 'ArrowUp':
      event.preventDefault()
      if (list.length === 0) return
      activeIndex.value =
        activeIndex.value === 0 ? list.length - 1 : activeIndex.value - 1
      scrollActiveIntoView()
      break
    case 'Enter':
    case 'Tab':
      event.preventDefault()
      if (list.length > 0) {
        selectMention(list[activeIndex.value])
      } else {
        showMentionList.value = false
      }
      break
    case 'Escape':
      event.preventDefault()
      showMentionList.value = false
      break
  }
}

// 让键盘选中的项滚动进可视区
const scrollActiveIntoView = () => {
  nextTick(() => {
    const el = listRef.value?.querySelector<HTMLElement>(`.mention-item.active`)
    el?.scrollIntoView({ block: 'nearest' })
  })
}
const listRef = ref<HTMLDivElement>()

// 选择提及项：用 chip 替换 @..光标 范围
const selectMention = (item: MentionItem) => {
  const editor = editorRef.value
  const sel = window.getSelection()
  if (!editor || !sel) return
  const active = getActiveMentionRange()
  if (!active) return

  // 删除 @ 到 光标 之间的内容
  active.range.deleteContents()

  // 构造 chip（不可分割单元）+ 尾随空格
  const chip = document.createElement('span')
  chip.className = 'mention-chip'
  chip.contentEditable = 'false'
  chip.dataset.id = item.id
  if (item.type) chip.dataset.type = item.type
  chip.textContent = `@${item.name}`

  const space = document.createTextNode('\u00A0')

  const frag = document.createDocumentFragment()
  frag.appendChild(chip)
  frag.appendChild(space)
  active.range.insertNode(frag)

  // 把光标移到空格之后
  const newRange = document.createRange()
  newRange.setStartAfter(space)
  newRange.collapse(true)
  sel.removeAllRanges()
  sel.addRange(newRange)

  showMentionList.value = false
  mentionKeyword.value = ''
  activeIndex.value = 0
  editor.focus()
  emitChange()
}

// 失焦延迟隐藏（让 mousedown 选择先触发）
const onBlur = () => {
  emit('blur')
  setTimeout(() => {
    showMentionList.value = false
  }, 150)
}

// 点击外部关闭（用 ref，支持多实例）
const onClickOutside = (event: MouseEvent) => {
  const wrapper = wrapperRef.value
  if (wrapper && !wrapper.contains(event.target as Node)) {
    showMentionList.value = false
  }
}

// v-model 同步：外部传入与当前 innerHTML 不同才设置，避免回环
watch(
  () => props.modelValue,
  (val) => {
    if (!editorRef.value) return
    if (val !== editorRef.value.innerHTML) {
      editorRef.value.innerHTML = val ?? ''
    }
  },
)

onMounted(() => {
  if (editorRef.value && props.modelValue) {
    editorRef.value.innerHTML = props.modelValue
  }
  document.addEventListener('click', onClickOutside)
})

onUnmounted(() => {
  document.removeEventListener('click', onClickOutside)
})

defineExpose({
  getHTML: () => editorRef.value?.innerHTML ?? '',
  getText: () => editorRef.value?.innerText ?? '',
  setHTML: (html: string) => {
    if (editorRef.value) {
      editorRef.value.innerHTML = html
      emitChange()
    }
  },
  setText: (text: string) => {
    if (editorRef.value) {
      editorRef.value.innerText = text
      emitChange()
    }
  },
})
</script>

<template>
  <div ref="wrapperRef" class="editor-wrapper">
    <div
      ref="editorRef"
      contenteditable="true"
      class="editor-content"
      :data-placeholder="placeholder ?? '描述你想生成的内容，可输入 @ 快速调用参考素材'"
      @input="onInput"
      @keydown="onKeydown"
      @blur="onBlur"
    ></div>

    <!-- @提及下拉列表（absolute 相对 wrapper，避免被 Vue Flow transform 影响） -->
    <div
      v-if="showMentionList"
      ref="listRef"
      class="mention-list"
      :style="{
        top: mentionPosition.top + 'px',
        left: mentionPosition.left + 'px',
      }"
    >
      <div
        v-for="(item, index) in filteredMentionList"
        :key="item.id"
        class="mention-item"
        :class="{ active: index === activeIndex }"
        @mousedown.prevent="selectMention(item)"
        @mouseenter="activeIndex = index"
      >
        <img v-if="item.avatar" :src="item.avatar" class="mention-avatar" />
        <span class="mention-name">{{ item.name }}</span>
        <span v-if="item.type" class="mention-type">{{ item.type }}</span>
      </div>
      <div v-if="filteredMentionList.length === 0" class="mention-empty">
        暂无匹配结果
      </div>
    </div>
  </div>
</template>

<style scoped>
.editor-wrapper {
  position: relative;
}

.editor-content {
  outline: none;
  color: #fff;
  line-height: 1.6;
  min-height: 140px;
  width: 600px;
  height: 140px;
  overflow-y: auto;
  padding: 10px;
  background: #111;
  border-radius: 4px;
  white-space: pre-wrap;
  word-break: break-word;
}

/* 空内容时显示 placeholder */
.editor-content:empty::before {
  content: attr(data-placeholder);
  color: #666;
  pointer-events: none;
}

/* @mention chip：不可分割的高亮标签 */
.mention-chip {
  display: inline-block;
  background: rgba(64, 158, 255, 0.18);
  color: #409eff;
  border: 1px solid rgba(64, 158, 255, 0.4);
  border-radius: 4px;
  padding: 0 4px;
  margin: 0 2px;
  font-size: 13px;
  line-height: 1.4;
  user-select: none;
  cursor: default;
}

/* @提及列表 */
.mention-list {
  position: absolute;
  background: #2d2d2d;
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  min-width: 180px;
  max-width: 280px;
  max-height: 200px;
  overflow-y: auto;
  z-index: 1000;
  padding: 4px 0;
  border: 1px solid #444;
}

.mention-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  cursor: pointer;
  transition: background 0.2s;
  color: #e0e0e0;
}

.mention-item:hover,
.mention-item.active {
  background: #4a4a4a;
}

.mention-avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #555;
  flex-shrink: 0;
}

.mention-name {
  flex: 1;
  font-size: 13px;
}

.mention-type {
  font-size: 11px;
  color: #888;
  background: #3a3a3a;
  padding: 1px 8px;
  border-radius: 10px;
}

.mention-empty {
  padding: 12px;
  color: #666;
  text-align: center;
  font-size: 13px;
}

.mention-list::-webkit-scrollbar {
  width: 4px;
}
.mention-list::-webkit-scrollbar-track {
  background: transparent;
}
.mention-list::-webkit-scrollbar-thumb {
  background: #555;
  border-radius: 2px;
}
</style>
