<template>
  <div ref="editorContainer" class="codemirror-editor"></div>
</template>

<script setup>
import { ref, onMounted, watch, onBeforeUnmount, nextTick } from 'vue'
import {
  EditorView, lineNumbers, highlightActiveLineGutter, keymap,
  EditorState, Compartment,
  defaultKeymap, history,
  bracketMatching, defaultHighlightStyle, syntaxHighlighting,
  oneDark,
  openSearchPanel, search
} from '@/utils/codemirrorExports'
import { useThemeStore } from '@/stores/theme'
import { loadLanguageExtension, getLanguageFromExtension } from '@/utils/codeMirrorLoader'

// ==================== Props & Emits ====================

const props = defineProps({
  modelValue: { type: String, required: true },
  fileExtension: { type: String, default: '' },
  filePath: { type: String, default: '' },
  fileMtime: { type: String, default: '' }
})

const emit = defineEmits(['update:modelValue'])

// ==================== 状态管理 ====================

const themeStore = useThemeStore()
const editorContainer = ref(null)
let view = null

// 滚动位置缓存：LRU 最多 5 份，每份 3 分钟过期
const MAX_SCROLL_CACHE = 5
const SCROLL_CACHE_TTL = 3 * 60 * 1000 // 3 分钟
const fileScrollPositions = new Map() // filePath → { scrollTop, anchor, timestamp }
let currentFilePath = ''
let saveScrollTimer = null

// 清理过期缓存 + LRU 淘汰，保持最多 MAX_SCROLL_CACHE 条
const cleanScrollCache = () => {
  const now = Date.now()
  // 清理过期的
  for (const [key, val] of fileScrollPositions) {
    if (now - val.timestamp > SCROLL_CACHE_TTL) {
      fileScrollPositions.delete(key)
    }
  }
  // LRU：超出上限时删除最旧的
  if (fileScrollPositions.size > MAX_SCROLL_CACHE) {
    let oldestKey = null
    let oldestTime = Infinity
    for (const [key, val] of fileScrollPositions) {
      if (val.timestamp < oldestTime) {
        oldestTime = val.timestamp
        oldestKey = key
      }
    }
    if (oldestKey) fileScrollPositions.delete(oldestKey)
  }
}

// 使用 Compartment 实现动态切换，避免重建编辑器
const themeCompartment = new Compartment()
const languageCompartment = new Compartment()

// ==================== 防抖处理 ====================

let emitTimeout = null
const debouncedEmit = (value) => {
  if (emitTimeout) {
    clearTimeout(emitTimeout)
  }
  emitTimeout = setTimeout(() => {
    emit('update:modelValue', value)
  }, 150)
}

// 获取当前主题扩展
const getThemeExtension = () => {
  if (themeStore.isDark) {
    return [oneDark]
  } else {
    // 亮色主题：使用默认语法高亮样式
    return [
      EditorView.theme({
        '&': { backgroundColor: '#ffffff' },
        '.cm-gutters': { backgroundColor: '#f7f7f7', color: '#999', border: 'none' },
        '.cm-activeLineGutter': { backgroundColor: '#e8e8e8', color: '#333' },
        '.cm-line': { caretColor: '#000' },
        '.cm-selection': { backgroundColor: '#d9d9d9' },
        '.cm-cursor': { borderLeftColor: '#000' }
      }),
      syntaxHighlighting(defaultHighlightStyle)
    ]
  }
}

// ==================== 扩展配置 ====================

const createExtensions = () => {
  const extensions = [
    lineNumbers(),
    highlightActiveLineGutter(),
    history(),
    keymap.of(defaultKeymap),
    bracketMatching(),

    // 查找替换（Ctrl+F / Ctrl+H）
    search(),

    // 内容更新监听（带防抖）
    EditorView.updateListener.of((update) => {
      if (update.docChanged) {
        debouncedEmit(update.state.doc.toString())
      }
    }),

    // 基础样式
    EditorView.theme({
      '&': { height: '100%', fontSize: '13px' },
      '.cm-scroller': { overflow: 'auto', fontFamily: 'Consolas, Monaco, Courier New, monospace' },
      '.cm-content': { padding: '8px' },
      '.cm-line': { padding: '0 0' },
      '&.cm-focused': { outline: 'none' }
    }),

    // 使用 Compartment 支持动态切换主题
    themeCompartment.of(getThemeExtension()),

    // 使用 Compartment 支持动态切换语言
    languageCompartment.of([])
  ]

  return extensions
}

// ==================== 语言管理 ====================

const initLanguage = async () => {
  const language = getLanguageFromExtension(props.fileExtension)
  if (language === 'text') return

  try {
    const langExtension = await loadLanguageExtension(language)
    if (langExtension && view) {
      view.dispatch({
        effects: languageCompartment.reconfigure(langExtension)
      })
    }
  } catch (error) {
    console.warn(`[CodeEditor] 加载语言包失败: ${language}`, error)
  }
}

// ==================== 编辑器创建 ====================

const createEditor = (docContent = '') => {
  if (!editorContainer.value) return

  const state = EditorState.create({
    doc: docContent,
    extensions: createExtensions()
  })

  view = new EditorView({ state, parent: editorContainer.value })

  // 滚动时防抖保存位置
  view.scrollDOM.addEventListener('scroll', () => {
    if (saveScrollTimer) clearTimeout(saveScrollTimer)
    saveScrollTimer = setTimeout(saveScrollPosition, 200)
  }, { passive: true })

  // 初始化语言
  initLanguage()
}

// ==================== 生命周期 ====================

onMounted(() => {
  createEditor(props.modelValue || '')

  // 确保主题正确应用（在下一 tick）
  nextTick(() => {
    if (view) {
      view.dispatch({
        effects: themeCompartment.reconfigure(getThemeExtension())
      })
    }
  })
})

onBeforeUnmount(() => {
  if (emitTimeout) clearTimeout(emitTimeout)
  if (saveScrollTimer) clearTimeout(saveScrollTimer)
  if (view?.scrollDOM) {
    view.scrollDOM.removeEventListener('scroll', saveScrollPosition)
  }
  view?.destroy()
  view = null
})

// ==================== 监听器 ====================

// 保存当前文件滚动位置（防抖）
const saveScrollPosition = () => {
  if (!view || !currentFilePath) return
  const scroller = view.scrollDOM
  if (!scroller) return
  fileScrollPositions.set(currentFilePath, {
    scrollTop: scroller.scrollTop,
    anchor: view.state.selection.main.anchor,
    timestamp: Date.now()
  })
  cleanScrollCache()
}

// 监听外部内容变化（切换文件/文件变更时触发）
watch([() => props.modelValue, () => props.fileMtime], ([newValue, newMtime], [oldValue, oldMtime]) => {
  // 文件修改时间变了 → 说明磁盘内容有变更 → 强制刷新
  const mtimeChanged = newMtime && oldMtime && newMtime !== oldMtime
  if (view && (mtimeChanged || newValue !== view.state.doc.toString())) {
    // 先保存旧文件的滚动位置
    saveScrollPosition()

    const newPath = props.filePath || ''
    const isSameFile = currentFilePath && currentFilePath === newPath

    view.dispatch({
      changes: { from: 0, to: view.state.doc.length, insert: newValue || '' },
      selection: { anchor: 0 }
    })

    currentFilePath = newPath

    if (isSameFile && fileScrollPositions.has(newPath)) {
      // 同一文件 → 检查是否过期，未过期则恢复位置
      const saved = fileScrollPositions.get(newPath)
      if (saved && Date.now() - saved.timestamp <= SCROLL_CACHE_TTL) {
        nextTick(() => {
          if (view) {
            view.dispatch({
              selection: { anchor: saved.anchor },
              effects: EditorView.scrollIntoView(saved.anchor)
            })
            view.scrollDOM.scrollTop = saved.scrollTop
          }
        })
      } else {
        // 过期了 → 强制滚动到顶部
        nextTick(() => {
          if (view) view.scrollDOM.scrollTop = 0
        })
      }
    } else {
      // 不同文件 → 强制滚动到顶部（scrollIntoView 不一定重置 DOM scrollTop）
      nextTick(() => {
        if (view) {
          view.scrollDOM.scrollTop = 0
        }
      })
    }
  }
})

// 监听主题变化（使用 Compartment 重建，不丢失状态）
watch(() => themeStore.isDark, () => {
  if (view) {
    view.dispatch({
      effects: themeCompartment.reconfigure(getThemeExtension())
    })
  }
})

// 监听文件扩展名变化（重新加载语言）
watch(() => props.fileExtension, () => {
  initLanguage()
})
</script>

<style scoped>
.codemirror-editor {
  height: 100%;
  width: 100%;
  overflow: hidden;
}

.codemirror-editor :deep(.cm-editor) {
  height: 100%;
  width: 100%;
}

.codemirror-editor :deep(.cm-scroller) {
  overflow: auto;
  height: 100%;
}

.codemirror-editor :deep(.cm-content) {
  /* 不设 height，让 CodeMirror 虚拟滚动自行计算文档高度 */
}
</style>
