<script lang="tsx">
import {
  type VNode,
  cloneVNode,
  inject,
  nextTick,
  ref,
  render,
} from 'vue'
import getCaretCoordinates from 'textarea-caret'
import MentionList from '../MentionList/index.vue'
import type { AtUserInfoDto } from '@/api/article/types'
import { MENTION_USER_FN_KEY, IS_REAL_KEY_COMMENT } from '@/constants/symbolKey'

type SlotModelAccessor = {
  getValue: () => string
  setValue: (v: string) => void
}

const extractModelAccessor = (vnode: VNode): SlotModelAccessor | null => {
  const props = vnode.props
  if (!props) return null

  const updater = props['onUpdate:modelValue'] as ((v: string) => void) | undefined
  if (!updater) return null

  const dirs = (vnode as any).dirs as { dir: any; value: any }[] | undefined
  const modelDir = dirs?.find((d) => d.dir?.mounted && d.dir?.beforeUpdate)

  return {
    getValue: () => (modelDir?.value as string) ?? (props.value as string) ?? '',
    setValue: (v: string) => updater(v),
  }
}

export default {
  name: 'MentionBox',
  setup(_, { slots }) {
   const isReal = inject(IS_REAL_KEY_COMMENT)!


    const renderBoxRef = useTemplateRef<HTMLDivElement>('renderBoxRef')



    let cursorPos = 0
    let accessor: SlotModelAccessor | null = null
    const mentionUsers = ref<Array<{ userId: string; name: string }>>([])
    const mousePosition = ref({ x: 0, y: 0 })

    const escapeRegExp = (str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
    const existsMentionToken = (text: string, name: string) => {
      const pattern = new RegExp(`(^|\\s)@${escapeRegExp(name)}(?=\\s|$)`)
      return pattern.test(text)
    }
    const syncMentionUsersWithText = (text: string) => {
      mentionUsers.value = mentionUsers.value.filter((u) => existsMentionToken(text, u.name))
    }

    const updateMentionPanelPosition = (textarea: HTMLTextAreaElement, caretIndex: number) => {
      const { left, top } = getCaretCoordinates(textarea, caretIndex)
      mousePosition.value = { x: left - 18, y: top + 30 }
    }

    /** 光标是否在「可弹出选人」的 @ 片段末尾（@ 后可为空或正在输入关键词） */
    const shouldOpenMentionAtCursor = (beforeCursor: string) => {
      if (!/@([^\s@]*)$/.test(beforeCursor)) return false
      const atIndex = beforeCursor.lastIndexOf('@')
      if (atIndex < 0) return false
      if (atIndex === 0) return true
      const prev = beforeCursor.charAt(atIndex - 1)
      if (prev === '@') return false
      if (prev && /\s/.test(prev)) return true
      // 非空白、非连续 @ 的前缀（如 12@、ca1@）也应能触发，旧版 (^|\\s)@ 会漏掉
      return true
    }

    const showMentionList = () => {
      render(
        <MentionList mousePosition={mousePosition.value} onMention={handleMention} isReal={unref(isReal)} />,
      renderBoxRef.value as HTMLElement,
      )
    }

    const handleInput = (e: Event) => {
      const textarea = e.target as HTMLTextAreaElement
      cursorPos = textarea.selectionStart
      syncMentionUsersWithText(textarea.value)

      const beforeCursor = textarea.value.slice(0, cursorPos)
      if (shouldOpenMentionAtCursor(beforeCursor)) {
        updateMentionPanelPosition(textarea, cursorPos)
        showMentionList()
      }
    }

    const findMentionRangeByIndex = (text: string, index: number) => {
      if (index < 0 || index >= text.length) return null

      for (const u of mentionUsers.value) {
        const token = `@${u.name}`
        let start = text.indexOf(token)
        while (start !== -1) {
          const end = start + token.length
          const prev = start > 0 ? text.charAt(start - 1) : ''
          const next = end < text.length ? text.charAt(end) : ''
          const boundaryOk = (!prev || /\s/.test(prev)) && (!next || /\s/.test(next))
          if (boundaryOk && index >= start && index < end) {
            return { start, end }
          }
          start = text.indexOf(token, start + token.length)
        }
      }
      return null
    }

    const handleKeydown = (e: KeyboardEvent) => {
      if (!accessor) return
      if (e.key !== 'Backspace' && e.key !== 'Delete') return

      const textarea = e.target as HTMLTextAreaElement
      const value = accessor.getValue()
      const caret = textarea.selectionStart
      const targetIndex = e.key === 'Backspace' ? caret - 1 : caret
      const mentionRange = findMentionRangeByIndex(value, targetIndex)
      if (!mentionRange) return

      e.preventDefault()
      const nextValue = value.slice(0, mentionRange.start) + value.slice(mentionRange.end)
      accessor.setValue(nextValue)
      syncMentionUsersWithText(nextValue)

      nextTick(() => {
        const nextCaret = mentionRange.start
        textarea.setSelectionRange(nextCaret, nextCaret)
        cursorPos = nextCaret
      })
    }

    const handleMention = (item: AtUserInfoDto) => {
      if (!accessor) return

      const value = accessor.getValue()
      const beforeAt = value.slice(0, cursorPos).replace(/@([^\s@]*)$/, '')
      const after = value.slice(cursorPos)
      const nextValue = `${beforeAt}@${item.name} ${after}`
      accessor.setValue(nextValue)
      if (!mentionUsers.value.some((u) => String(u.userId) === String(item.userId))) {
        mentionUsers.value.push({
          userId: String(item.userId),
          name: item.name,
        })
      }
      syncMentionUsersWithText(nextValue)
    }

    const mentionFnObj = inject(MENTION_USER_FN_KEY)
    if (mentionFnObj) {
      mentionFnObj.getMentionUserIds = () => mentionUsers.value.map((u) => u.userId)
      mentionFnObj.getMentionUsers = () => mentionUsers.value.map((u) => ({ ...u }))
    }


    return () => {
      const raw = slots.default?.()[0] as VNode
      if (!raw) return null

      accessor = extractModelAccessor(raw)
      const wrapped = cloneVNode(raw, { onInput: handleInput, onKeydown: handleKeydown })

      return <div ref='renderBoxRef' class="relative">{wrapped}</div>
    }
  },
}
</script>

<style scoped lang="scss"></style>
