import fsp from 'node:fs/promises'
import * as vscode from 'vscode'
import { addEventListener, createCompletionItem, createHover, createMarkdownString, createPosition, createRange, createSelect, getActiveText, getActiveTextEditor, getActiveTextEditorLanguageId, getConfiguration, getCurrentFileUrl, getLineText, getLocale, getPosition, getSelection, insertText, message, openExternalUrl, registerCommand, registerCompletionItemProvider, setConfiguration, setCopyText, updateText } from '@vscode-use/utils'
import { CreateWebview } from '@vscode-use/createwebview'
import { createFilter } from '@rollup/pluginutils'
import { detectSlots, findDynamicComponent, findRefs, getImportDeps, getReactRefsMap, parser, parserVine, registerCodeLensProviderFn, transformVue } from './utils'
import { UINames as UINamesMap } from './constants'
import type { Directives, PropsConfig, SubCompletionItem } from './ui/utils'
import { isVine, isVue, toCamel } from './ui/utils'
import { completionsCallbacks, deactivateUICache, eventCallbacks, findUI, getCacheMap, getCurrentPkgUiNames, getOptionsComponents, getUiCompletions, logger } from './ui-find'
import { getIsShowSlots, getUiDeps } from './ui-utils'
import { cacheFetch, localCacheUri } from './fetch'

const defaultExclude = getConfiguration('common-intellisense.exclude')
const filterId = createFilter(defaultExclude)
const filter = ['javascript', 'javascriptreact', 'typescript', 'typescriptreact', 'vue', 'svelte']
function isSkip() {
  const id = getActiveTextEditorLanguageId()
  return !id || !filter.includes(id)
}
// todo: 补充类型
// todo: 补充example
export async function activate(context: vscode.ExtensionContext) {
  // todo: createWebviewPanel
  // createWebviewPanel(context)
  logger.info('common-intellisense activate!')
  const isZh = getLocale().includes('zh')
  const LANS = ['javascriptreact', 'typescript', 'typescriptreact', 'vue', 'svelte', 'solid', 'swan', 'react', 'js', 'ts', 'tsx', 'jsx']

  if (!isSkip())
    findUI(context, detectSlots)

  const provider = new CreateWebview(context, {
    viewColumn: vscode.ViewColumn.Beside,
    scripts: ['main.js'],
  })

  context.subscriptions.push(registerCommand('common-intellisense.cleanCache', () => {
    fsp.rmdir(localCacheUri)
    cacheFetch.clear()
    findUI(context, detectSlots)
  }))
  context.subscriptions.push(registerCodeLensProviderFn())

  context.subscriptions.push(addEventListener('activeText-change', (editor?: vscode.TextEditor) => {
    if (!editor)
      return

    if (isSkip())
      return
    // 找到当前活动的编辑器
    const visibleEditors = vscode.window.visibleTextEditors
    const currentEditor = visibleEditors.find(e => e === editor)
    if (currentEditor)
      findUI(context, detectSlots)
  }))

  context.subscriptions.push(registerCommand('intellisense.copyDemo', (demo) => {
    setCopyText(demo)
    message.info('copy successfully')
  }))

  context.subscriptions.push(registerCommand('common-intellisense.pickUI', () => {
    const currentPkgUiNames = getCurrentPkgUiNames()
    if (currentPkgUiNames && currentPkgUiNames.length) {
      if (currentPkgUiNames.some(i => i.includes('bitsUi'))) {
        currentPkgUiNames.filter(i => i.startsWith('bitsUi')).map(i => i.replace('bitsUi', 'shadcnSvelte')).forEach((i) => {
          if (!currentPkgUiNames!.includes(i))
            currentPkgUiNames!.push(i)
        })
      }
      const currentSelect = getConfiguration('common-intellisense.ui') as (string[] | undefined)
      let options: ({ label: string, picked?: boolean })[] = []
      if (currentSelect) {
        options = currentPkgUiNames.map((label) => {
          if (currentSelect.includes(label)) {
            return {
              label,
              picked: true,
            }
          }
          else {
            return {
              label,
            }
          }
        })
      }
      createSelect(options, {
        canSelectMany: true,
        placeHolder: isZh ? '请指定你需要提示的 UI 库' : 'Please specify the UI library you need to prompt.',
        title: 'common intellisense',
      }).then((data: string[]) => {
        setConfiguration('common-intellisense.ui', data)
      })
    }
    else {
      message.error(isZh
        ? '当前项目中并没有安装 common intellisense 支持的 UI 库'
        : 'There is no UI library supported by common intelligence in the current project.')
    }
  }))

  context.subscriptions.push(addEventListener('config-change', (e) => {
    if (e.affectsConfiguration('common-intellisense.ui'))
      findUI(context, detectSlots)
  }))

  context.subscriptions.push(registerCommand('common-intellisense.import', (params, loc, _lineOffset) => {
    if (!params)
      return
    const { data, lib, prefix, dynamicLib, importWay } = params
    const name = data.name.split('.')[0]
    const fromName = data.from
    const from = fromName || dynamicLib ? dynamicLib.replace('${name}', name.toLowerCase()) : lib
    const code = getActiveText()!
    const uiComponents = getImportUiComponents(code)
    let deps = data.suggestions?.length === 1
      ? data.suggestions.map((i: any) => {
        if (i.includes('-'))
          return toCamel(i).slice(prefix.length)

        return i
      })
      : []

    if (uiComponents[lib])
      deps.push(...uiComponents[lib].components)
    else
      deps.push(name)

    deps = [...new Set(deps)]
    if (uiComponents[lib]) {
      if (deps.includes(name))
        return
      deps.push(name)

      const offsetStart = code.match(uiComponents[lib].match[0])!.index!
      const offsetEnd = offsetStart + uiComponents[lib].match[0].length
      const posStart = getPosition(offsetStart).position
      const posEnd = getPosition(offsetEnd).position

      const str = importWay === 'as default'
        ? `import * as ${deps.join(', ')} from '${from}'`
        : importWay === 'default'
          ? `import ${deps.join(', ')} from '${from}'`
          : `import { ${deps.join(', ')} } from '${from}'`
      updateText((edit) => {
        edit.replace(createRange(posStart, posEnd), str)
      })
    }
    else {
      // 顶部导入
      let str = importWay === 'as default'
        ? `import * as ${deps.join(', ')} from '${from}'`
        : importWay === 'default'
          ? `import ${deps.join(', ')} from '${from}'`
          : `import { ${deps.join(', ')} } from '${from}'`
      let pos: any = null
      if (isVue()) {
        if (loc) {
          if (getLineText(loc.start.line)?.trim()) {
            str += '\n'
          }
          pos = createPosition(loc.start.line, 0)
        }
        else {
          const match = code.match(/<script[^>]*>/)
          if (match) {
            const offset = match.index! + match[0].length
            pos = getPosition(offset)
            str = `\n${str}`
          }
          else {
            pos = createPosition(0, 0)
            str = `<script setup>\n${str}</script>`
          }
        }
      }
      else {
        const match = code.match(/<script[^>]*>/)
        if (match) {
          const offset = match.index! + match[0].length
          pos = getPosition(offset)
          str = `\n  ${str}`
        }
        else {
          str += '\n'
          pos = createPosition(0, 0)
        }
      }

      updateText((edit) => {
        edit.insert(pos, str)
      })
    }
  }))

  // 监听pkg变化
  if (getIsShowSlots()) {
    context.subscriptions.push(registerCommand('common-intellisense.slots', (child, name, offset) => {
      const UiCompletions = getUiCompletions()
      const activeText = getActiveText()
      if (!activeText)
        return
      if (!child && UiCompletions) {
        const uiDeps = getUiDeps(activeText)
        const optionsComponents = getOptionsComponents()
        const componentsPrefix = optionsComponents.prefix
        detectSlots(UiCompletions, uiDeps, componentsPrefix)
        return
      }
      if (!child.children)
        return

      let lastChild = child.children[child.children.findLastIndex((c: any) => c.type !== 2)]
      let slotName = `#${name}`
      if (child.range)
        slotName = `v-slot:${name}`

      if (lastChild) {
        if (isVine() && lastChild.codegenNode) {
          lastChild = lastChild.codegenNode
        }
        const pos = lastChild.loc.end
        const endColumn = Math.max(pos.column - 1, 0)
        if (isVine())
          insertText(`\n<template ${slotName}>$1</template>`, getPosition(pos.offset + offset).position)
        else
          insertText(`\n<template ${slotName}>$1</template>`, createPosition(pos.line - 1, endColumn))
        // updateText((edit) => {
        //   if (isVine())
        //     edit.insert(getPosition(pos.offset + offset).position, `\n${empty}<template ${slotName}></template>`)
        //   else
        //     edit.insert(createPosition(pos.line - 1, endColumn), `\n${empty}<template ${slotName}></template>`)
        // })
      }
      else {
        const empty = ' '.repeat(Math.max(child.loc.start.column - 1, 0))

        if (child.isSelfClosing) {
          if (isVine())
            insertText(`>\n${empty}  <template ${slotName}>$1</template>\n</${child.tag}>`, getPosition(child.loc.end.offset + offset - 3).position)
          else
            insertText(`>\n${empty}  <template ${slotName}>$1</template>\n</${child.tag}>`, createPosition(child.loc.end.line - 1, child.loc.end.column - 3))
          // updateText((edit) => {
          //   if (isVine())
          //     edit.replace(createRange(getPosition(child.loc.end.offset + offset - 3), getPosition(child.loc.end.offset + offset - 1)), `>\n${empty}  <template ${slotName}></template>\n${empty}</${child.tag}>`)
          //   else
          //     edit.replace(createRange([child.loc.end.line - 1, child.loc.end.column - 3], [child.loc.end.line - 1, child.loc.end.column - 1]), `>\n${empty}  <template ${slotName}></template>\n${empty}</${child.tag}>`)
          // })
        }
        else {
          const isNeedLineBlock = child.loc.start.line === child.loc.end.line
          const index = child.loc.start.offset + child.loc.source.indexOf(`</${child.tag}`) - (isNeedLineBlock ? 0 : (child.loc.end.column - `</${child.tag}>`.length - 1))
          const pos = getPosition(index)
          if (isVine())
            insertText(`${isNeedLineBlock ? '\n' : empty}  <template ${slotName}>$1</template>\n`, getPosition(index + offset).position)
          else
            insertText(`${isNeedLineBlock ? '\n' : empty}  <template ${slotName}>$1</template>\n`, createPosition(pos.line, pos.column))
          // updateText((edit) => {
          //   if (isVine())
          //     edit.insert(getPosition(pos.offset + offset).position, `${isNeedLineBlock ? '\n' : ''}${empty}  <template ${slotName}></template>\n${isNeedLineBlock ? empty : ''}`)
          //   else
          //     edit.insert(createPosition(pos), `${isNeedLineBlock ? '\n' : ''}${empty}  <template ${slotName}></template>\n${isNeedLineBlock ? empty : ''}`)
          // })
        }
      }
    }))

    context.subscriptions.push(addEventListener('text-change', ({ contentChanges, document }) => {
      if (contentChanges.length === 0 || document.languageId === 'Log')
        return
      const UiCompletions = getUiCompletions()
      const optionsComponents = getOptionsComponents()
      const componentsPrefix = optionsComponents.prefix
      if (isSkip())
        return
      const activeText = getActiveText()
      if (UiCompletions && activeText) {
        const uiDeps = getUiDeps(activeText)
        detectSlots(UiCompletions, uiDeps, componentsPrefix)
      }
    }))
  }

  context.subscriptions.push(registerCompletionItemProvider(filter, async (document, position) => {
    const optionsComponents = getOptionsComponents()
    const componentsPrefix = optionsComponents.prefix
    let UiCompletions = getUiCompletions()
    if (!UiCompletions)
      return
    const { lineText } = getSelection()!
    const p = position
    const activeTextEditor = getActiveTextEditor()
    if (!activeTextEditor)
      return

    if (isSkip())
      return

    const preText = lineText.slice(0, activeTextEditor.selection.active.character)
    let completionsCallback: SubCompletionItem[] | undefined
    const activeText = getEffectWord(preText)
    const result = parser(document.getText(), p)
    if (!result)
      return
    if (activeText === ':' && result.type === 'text')
      return

    const lan = getActiveTextEditorLanguageId()
    const isVue = (lan === 'vue' && result.template) || isVine()
    const code = getActiveText()
    if (!code)
      return
    const deps = isVue ? getImportDeps(code) : {}
    const uiDeps = getUiDeps(code)
    const { character } = position
    const isPreEmpty = lineText[character - 1] === ' '
    const isValue = result.isValue

    if (result.type === 'script' && Object.keys(result.refsMap || {}).length && !isPreEmpty) {
      if (lineText?.slice(-1)[0] === '.') {
        for (const key in result.refsMap) {
          const value = result.refsMap[key]
          if (isVue && (lineText.endsWith(`.$refs.${key}.`) || lineText.endsWith(`${key}.value.`)) && UiCompletions[value])
            return [...UiCompletions[value].methods, ...UiCompletions[value].exposed]
          else if (!isVue && lineText.endsWith(`${key}.current.`) && UiCompletions[value])
            return [...UiCompletions[value].methods, ...UiCompletions[value].exposed]
        }
      }
      if (isVue && lineText.slice(character, character + 6) !== '.value' && !/\.value\.?$/.test(lineText.slice(0, character)))
        return result.refs.map((refName: string) => createCompletionItem({ content: refName, snippet: `${refName}.value`, documentation: `${refName}.value`, preselect: true, sortText: 'a' }))

      if (!isVue && lineText.slice(character, character + 8) !== '.current' && !/\.current\.?$/.test(lineText.slice(0, character)))
        return result.refs.map((refName: string) => createCompletionItem({ content: refName, snippet: `${refName}.current`, documentation: `${refName}.current`, preselect: true, sortText: 'a' }))
    }

    if (result.parent && result.tag === 'template') {
      const parentTag = result.parent.tag || result.parent.name
      if (parentTag) {
        const name = toCamel(parentTag)
        const component = UiCompletions[name[0].toUpperCase() + name.slice(1)]
        const slots = component?.slots
        if (slots)
          return slots
      }
    }

    if (UiCompletions && result?.type === 'props') {
      const name = result.tag[0].toUpperCase() + result.tag.replace(/(-\w)/g, (match: string) => match[1].toUpperCase()).slice(1)
      if (result.propName === 'icon')
        return UiCompletions.icons

      const propName = result.propName
      const from = uiDeps?.[name]
      const cacheMap = getCacheMap()
      if (from && cacheMap.size > 2) {
        // 存在多个 UI 库
        const nameReg = new RegExp(`${toCamel(from)}\\d+$`)
        const keys = Array.from(cacheMap.keys())
        const targetKey = keys.find(k => nameReg.test(k))!
        const targetValue = cacheMap.get(targetKey)! as PropsConfig
        UiCompletions = targetValue
      }
      let target = await findDynamicComponent(name, deps, UiCompletions, componentsPrefix, from)
      const importUiSource = uiDeps?.[name]
      if (importUiSource && (!target || target.uiName !== importUiSource)) {
        for (const p of optionsComponents.prefix.filter(Boolean)) {
          const realName = p[0].toUpperCase() + p.slice(1) + name
          const newTarget = UiCompletions[realName]
          if (!newTarget)
            continue
          if (newTarget.uiName === importUiSource) {
            target = newTarget
            break
          }
        }
      }

      if (!target)
        return

      const { events, completions, uiName } = target
      const key = uiName + name
      if (!completionsCallbacks.has(key)) {
        const directives = optionsComponents.directivesMap[uiName]
        const directivesCompletions = directives
          ? directives.map((item: Directives[0]) => {
            const detail = isZh ? item.description_zh : item.description
            const content = `${item.name}  ${detail}`
            const documentation = createMarkdownString()
            if (item.documentation)
              documentation.appendMarkdown(item.documentation)
            else if (item.documentationType)
              documentation.appendCodeblock(item.documentationType, 'typescript')

            if (item.params?.length) {
              documentation.appendCodeblock('\n')
              item.params.forEach((i) => {
                documentation.appendMarkdown(`### 🌟 ${i.name}: \n`)
                documentation.appendMarkdown(`- ${isZh ? '类型' : 'type'}: ${i.type}\n`)
                documentation.appendMarkdown(`- ${isZh ? '描述' : 'description'}: ${isZh ? i.description_zh : i.description}\n`)
                documentation.appendMarkdown(`- ${isZh ? '默认值' : 'default'}: ${i.default}\n`)
              })
            }

            const snippet = item.params?.length
              ? `:${item.name}="${JSON.stringify(item.params.reduce((acc, i) => {
                const key = i.name
                const type = i.type.toLocaleLowerCase()
                const value = i.default || type === 'boolean' ? false : type === 'number' ? 0 : type === 'string' ? '' : ''
                acc[key] = value
                return acc
              }, {} as Record<string, any>), null, 2).replace(/"([^"]+)":/g, '$1:').replace(/"/g, '`')}"`
              : item.name

            return createCompletionItem({
              content,
              detail,
              sortText: 'a',
              type: vscode.CompletionItemKind.Enum,
              snippet,
              params: [uiName, item.name],
              preselect: true,
              documentation,
            })
          })
          : []
        const _events = events[0](isVue)
        eventCallbacks.set(key, _events)
        completionsCallbacks.set(key, [...completions[0](isVue), ...(isVue ? [] : _events), ...directivesCompletions])
      }

      if (!eventCallbacks.has(key))
        eventCallbacks.set(key, events[0](isVue))

      completionsCallback = completionsCallbacks.get(key)
      const hasProps = result.props
        ? result.props.map((item: any) => {
          if (item.name === 'on' && item.arg)
            return `${item.arg.content}`

          if (typeof item.name === 'object' && item.name.name !== 'on')
            return item.name.name

          if (item.name === 'model' && item?.loc?.source?.startsWith('v-model'))
            return item.loc.source.split('=')[0]

          if (item.name === 'bind')
            return item?.arg?.content

          if (item.name !== 'on')
            return item.name

          return false
        }).filter(Boolean)
        : []
      if (propName === 'on') {
        return (eventCallbacks.get(key) || []).filter((item: any) => !hasProps.find((prop: any) => item?.params?.[1] === prop))
      }
      else if (propName) {
        const r: any[] = []
        if (isValue) {
          (completionsCallback ?? []).filter((item: any) => hasProps.find((prop: any) => item?.params?.[1] === prop))
            .filter((item: any) => {
              const reg = propName === 'bind'
                ? new RegExp('^:')
                : new RegExp(`^:?${propName}`)
              return reg.test(item.label)
            }).forEach((item: any) => {
              item.propType?.split('/').forEach((p: string) => {
                r.push(createCompletionItem({
                  content: p.trim(),
                  snippet: p.trim().replace(/'`/g, ''),
                  documentation: item.documentation,
                  sortText: 'a',
                  detail: item.detail,
                  type: item.kind,
                }))
              })
            })
        }
        else {
          r.push(...(completionsCallback ?? []).filter((item: any) => !hasProps.find((prop: any) => item?.params?.[1] === prop))
            .map((item: any) => createCompletionItem(({
              content: item.content,
              snippet: item.snippet,
              documentation: item.documentation,
              detail: item.detail,
              sortText: 'a',
              preselect: true,
              type: item.kind,
            }))))
        }
        const events = isVue
          ? []
          : isValue
            ? []
            : (eventCallbacks.get(key) || []).filter((item: any) => !hasProps.find((prop: any) => item?.params?.[1] === prop))
        if (propName === 'o')
          return [...events, ...r]

        return [...r, ...events]
      }
      else if (hasProps.length) {
        return (completionsCallback ?? []).filter((item: any) => !hasProps.find((prop: any) => item.params?.[1] === prop))
      }
      else {
        return completionsCallback
      }
    }
    else if (!result.isInTemplate || isPreEmpty || !optionsComponents) {
      return
    }
    const prefix = lineText.trim().split(' ').slice(-1)[0]
    if (prefix.toLowerCase() === prefix ? optionsComponents.prefix.some((reg: string) => prefix.startsWith(reg) || reg.startsWith(prefix)) : true) {
      const parent = result.parent
      const data = optionsComponents.data.map(c => c()).flat()
      if (parent) {
        const parentTag = parent.tag || parent.name
        if (UiCompletions) {
          const suggestions = UiCompletions[toCamel(parentTag)[0].toUpperCase() + toCamel(parentTag).slice(1)]?.suggestions
          if (suggestions && suggestions.length) {
            data.forEach((child) => {
              const label = typeof child.label === 'string' ? child.label.split(' ')[0] : child.label.label.split(' ')[0]
              child.sortText = suggestions.includes(label) ? '1' : '2';
              (child as any).loc = result.loc
            })
          }
          else {
            data.forEach((child: any) => {
              child.sortText = '2'
              child.loc = result.loc
            })
          }
        }
      }

      return data
    }
  }, (item: SubCompletionItem) => {
    if (!item.command) {
      if (item.params?.isReact) {
        item.command = {
          title: 'common-intellisense-import',
          command: 'common-intellisense.import',
          arguments: [item.params, item.loc, (item.snippet || item.content).split('\n').length - 1],
        }
      }
      else {
        item.command = {
          title: 'common-intellisense.slots',
          command: 'common-intellisense.slots',
          arguments: [],
        }
      }
    }

    return item
  }, ['"', '\'', '-', ' ', '@', '.', ':']))

  context.subscriptions.push(registerCommand('intellisense.openDocument', (args) => {
    // 注册全局的 link 点击事件
    const url = args.link
    if (!url)
      return
    provider.create(`
      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Webview</title>
          <style>
            body{
              width:100%;
              height:100vh;
            }
          </style>
        </head>
        <body>
          <iframe src="${url}" width="100%" height="100%"></iframe>
        </body>
      </html>
      `, ({ data, type }) => {
      // callback 获取 js 层的 postMessage 数据
      if (type === 'copy') {
        setCopyText(data).then(() => {
          const isZh = getLocale().includes('zh')
          message.info(`${isZh ? '复制成功' : 'copy successfully'}!  ✅`)
        })
      }
    })
  }))

  context.subscriptions.push(registerCommand('intellisense.openDocumentExternal', (args) => {
    // 注册全局的 link 点击事件
    const url = args.link
    if (!url)
      return
    openExternalUrl(url)
  }))

  context.subscriptions.push(vscode.languages.registerHoverProvider(LANS, {
    async provideHover(document, position) {
      const optionsComponents = getOptionsComponents()
      const componentsPrefix = optionsComponents.prefix
      let UiCompletions = getUiCompletions()
      if (!optionsComponents || !UiCompletions)
        return

      const editor = getActiveTextEditor()
      if (!editor)
        return

      const currentFileUrl = getCurrentFileUrl()

      if (!currentFileUrl)
        return

      if (filterId(currentFileUrl))
        return

      const range = document.getWordRangeAtPosition(position)
      if (!range)
        return

      let word = document.getText(range)

      const lineText = getLineText(position.line)
      if (!lineText)
        return

      const code = document.getText()
      const uiDeps = getUiDeps(code)
      // word 修正
      if (lineText[range.end.character] === '.' || lineText[range.end.character] === '-') {
        let index = range.end.character
        while (!/[>\s/]/.test(lineText[index]) && index < lineText.length) {
          word += lineText[index]
          index++
        }
      }
      if (lineText[range.start.character - 1] === '.') {
        let index = range.start.character - 1
        while (!/[<\s/]/.test(lineText[index]) && index >= 0) {
          word = lineText[index] + word
          index--
        }
      }
      else if (lineText[range.start.character - 1] !== '<') {
        const result = parser(code, position as any)
        if (!result)
          return
        if (result.type === 'tag') {
          const data = optionsComponents.data.map(c => c()).flat()
          if (!data?.length || !word)
            return createHover('')
          const tag = toCamel(result.tag)[0].toUpperCase() + toCamel(result.tag).slice(1)
          const target = await findDynamicComponent(tag, {}, UiCompletions, componentsPrefix, uiDeps?.[tag])
          if (!target)
            return

          const tableDocument = target.tableDocument

          if (tableDocument)
            return createHover(tableDocument)
        }
        else if (!result.propName) {
          return
        }

        const propName = result.propName === 'bind' ? result.props[0].arg.content : result.propName
        if (['class', 'className', 'style', 'id'].includes(propName))
          return
        const tag = toCamel(result.tag)[0].toUpperCase() + toCamel(result.tag).slice(1)
        const r = UiCompletions[tag] || await findDynamicComponent(tag, {}, UiCompletions, componentsPrefix)
        if (!r)
          return
        const completions = result.isEvent ? r.events[0]?.() : r.completions[0]?.()
        if (!completions)
          return
        const detail = getHoverAttribute(completions, propName)
        if (!detail)
          return
        return createHover(`## Details \n\n${detail}`)
      }
      // todo: 优化这里的条件，在 react 中， 也可以减少更多的处理步骤
      if (isVue()) {
        const r = transformVue(code, position)
        if (r) {
          if (!r.template)
            return
          if (word.includes('.value.') && r.type === 'script' && r.refs.length) {
            const refsMap = findRefs(r.template, r.refs)
            const index = word.indexOf('.value.')
            const key = word.slice(0, index)
            const refName = refsMap[key]
            if (!refName)
              return

            if (lineText.slice(range.start.character, range.end.character) === 'value') {
              // hover .value.区域 提示所有方法
              const groupMd = createMarkdownString()
                ;[...UiCompletions[refName].methods, ...UiCompletions[refName].exposed].forEach((m, i) => {
                let content = typeof m.documentation === 'string' ? m.documentation : m.documentation?.value || ''
                if (i !== 0) {
                  content = content.replace(/##[^\]\n]*[\]\n]/, '')
                }
                groupMd.appendMarkdown(content)
                groupMd.appendMarkdown('\n')
              })

              return createHover(groupMd)
            }
            const targetKey = word.slice(index + '.value.'.length)
            // FIXME: label可能是对象,string | vscode.CompletionItemLabel
            const target = [...UiCompletions[refName].methods, ...UiCompletions[refName].exposed].find(item => item.label === targetKey)

            if (!target)
              return

            return target.hover
          }
          if (r.type === 'script')
            return
        }
      }
      else if (isVine()) {
        const r = parserVine(code, position)
        if (r) {
          if (word.includes('.value.') && r.type === 'script' && Object.keys(r.refsMap || {}).length) {
            const index = word.indexOf('.value.')
            const key = word.slice(0, index)
            const refName = r.refsMap[key]
            if (!refName)
              return
            if (lineText.slice(range.start.character, range.end.character) === 'value') {
              // hover .value.区域 提示所有方法
              const groupMd = createMarkdownString()
                ;[...UiCompletions[refName].methods, ...UiCompletions[refName].exposed].forEach((m: any, i: number) => {
                let content = m.documentation.value
                if (content && i !== 0) {
                  content = content.replace(/##[^\]\n]*[\]\n]/, '')
                }
                groupMd.appendMarkdown(content)
                groupMd.appendMarkdown('\n')
              })
              return createHover(groupMd)
            }
            const targetKey = word.slice(index + '.value.'.length)
            const target = [...UiCompletions[refName].methods, ...UiCompletions[refName].exposed].find((item: any) => item.label === targetKey)

            if (!target)
              return

            return target.hover
          }
          if (r.type === 'script')
            return
        }
      }
      else if (getActiveTextEditorLanguageId()?.includes('react')) {
        if (word.includes('.current.')) {
          const r = getReactRefsMap()
          const index = word.indexOf('.current.')
          const key = word.slice(0, index)
          const refName = r.refsMap[key]
          if (!refName)
            return

          if (lineText.slice(range.start.character, range.end.character) === 'current') {
            // hover .value.区域 提示所有方法
            const groupMd = createMarkdownString()
              ;[...UiCompletions[refName].methods, ...UiCompletions[refName].exposed].forEach((m, i) => {
              let content = typeof m.documentation === 'string' ? m.documentation : m.documentation?.value || ''
              if (i !== 0) {
                content = content.replace(/##[^\]\n]*[\]\n]/, '')
              }
              groupMd.appendMarkdown(content)
              groupMd.appendMarkdown('\n')
            })
            return createHover(groupMd)
          }
          const targetKey = word.slice(index + '.current.'.length)
          const target = [...UiCompletions[refName].methods, ...UiCompletions[refName].exposed].find(item => item.label === targetKey)

          if (!target)
            return

          return target.hover
        }
      }
      const data = optionsComponents.data.map(c => c()).flat()
      if (!data?.length || !word)
        return createHover('')
      word = toCamel(word)[0].toUpperCase() + toCamel(word).slice(1)
      const from = uiDeps?.[word]
      const cacheMap = getCacheMap()
      if (from && cacheMap.size > 2) {
        // 存在多个 UI 库
        const nameReg = new RegExp(`${toCamel(from)}\\d+$`)
        const keys = Array.from(cacheMap.keys())
        const targetKey = keys.find(k => nameReg.test(k))!
        const targetValue = cacheMap.get(targetKey)! as PropsConfig
        UiCompletions = targetValue
      }
      const target = await findDynamicComponent(word, {}, UiCompletions, optionsComponents.prefix, uiDeps?.[word])
      if (!target)
        return

      const tableDocument = target.tableDocument

      if (tableDocument)
        return createHover(tableDocument)
    },
  }))
}

export function deactivate() {
  deactivateUICache()
}

function getEffectWord(preText: string) {
  let i = preText.length - 1
  let active = ''
  while (preText[i] && (preText[i] !== ' ')) {
    active = `${preText[i]}${active}`
    i--
  }
  return active
}

function getHoverAttribute(attributeList: any[], attr: string) {
  return attributeList.filter(a =>
    toCamel(a?.params?.[1]?.replace('v-model:', '') || '') === toCamel(attr),
  ).map(i => `- ${i.details}`).join('\n\n')
}

const IMPORT_UI_REG = /import\s+\{([^}]+)\}\s+from\s+['"]([^"']+)['"]/g

function getImportUiComponents(text: string) {
  // 读取需要按需导入的ui库， 例如 antd, 拿出导入的 components
  const deps: Record<string, any> = {}
  for (const match of text.matchAll(IMPORT_UI_REG)) {
    if (!match)
      continue
    const from = match[2]
    if (UINamesMap.includes(from)) {
      deps[from] = {
        match,
        components: match[1].split(',').map(i => i.trim()),
      }
    }
  }
  return deps
}
