<template>
  <div class="tool-page">
    <!-- 浮层提示 -->
    <Transition name="toast">
      <div v-if="toastMessage" class="toast-notification" :class="toastType">
        <div class="toast-content">
          <i v-if="toastType === 'error'" class="fas fa-circle-exclamation"></i>
          <i v-else class="fas fa-circle-info"></i>
          <span>{{ toastMessage }}</span>
        </div>
        <button @click="closeToast" class="toast-close-btn" title="关闭">
          <i class="fas fa-xmark"></i>
        </button>
      </div>
    </Transition>

    <div class="main-container">
      <!-- 左侧侧栏（历史记录） -->
      <div class="sidebar" :class="{ 'sidebar-open': sidebarOpen }">
        <div class="sidebar-header">
          <h3>历史记录</h3>
          <button @click="toggleSidebar" class="close-btn">×</button>
        </div>
        <div class="sidebar-content">
          <div v-if="historyList.length === 0" class="empty-history">
            暂无历史记录
          </div>
          <div
            v-for="(item, index) in historyList"
            :key="index"
            class="history-item"
            @click="loadHistory(item)"
          >
            <div class="history-time">{{ formatTime(item.time) }}</div>
            <div class="history-preview">
              <div class="history-mode">{{ getModeLabel(item.compareMode, item.textSubMode, item.ignoreListOrder) }}</div>
              <div class="history-text">{{ truncateText(item.leftText || '', 30) }} | {{ truncateText(item.rightText || '', 30) }}</div>
            </div>
          </div>
        </div>
      </div>

      <!-- 主内容区域 -->
      <div class="content-wrapper" :class="{ 'sidebar-pushed': sidebarOpen }">
        <!-- 工具栏 -->
        <div class="toolbar">
          <div class="mode-selector">
            <button
                @click="compareMode = 'text'"
                :class="['mode-btn', { active: compareMode === 'text' }]"
                title="文本对比"
            >
              文本对比
            </button>
            <button
                @click="compareMode = 'json'"
                :class="['mode-btn', { active: compareMode === 'json' }]"
                title="JSON对比"
            >
              JSON对比
            </button>
          </div>
          <div class="submode-selector" v-if="compareMode === 'text'">
            <button
                @click="textSubMode = 'line'"
                :class="['submode-btn', { active: textSubMode === 'line' }]"
                title="按行对比"
            >
              行维度
            </button>
            <button
                @click="textSubMode = 'char'"
                :class="['submode-btn', { active: textSubMode === 'char' }]"
                title="按字符对比"
            >
              字符维度
            </button>
          </div>
          <div class="submode-selector" v-if="compareMode === 'json'">
            <label class="switch-label">
              <input type="checkbox" v-model="ignoreListOrder" class="switch-input" />
              <span class="switch-text">忽略列表顺序</span>
            </label>
          </div>
          <div class="toolbar-actions">
            <button @click="performCompare" class="action-btn primary" title="开始对比">
              <i class="fas fa-code-compare"></i>
              <span>对比</span>
            </button>
            <button @click="clearAll" class="action-btn" title="清空">
              <i class="far fa-trash-can"></i>
            </button>
          </div>
        </div>

        <!-- 输入区域 -->
        <div class="input-container">
          <!-- 左侧输入框 -->
          <div class="input-panel" :style="{ width: leftPanelWidth + '%' }">
            <div class="panel-header">
              <span class="panel-label">文本 A</span>
              <div class="panel-actions">
                <button @click="copyToClipboard('left')" class="icon-btn" title="复制">
                  <i class="far fa-copy"></i>
                </button>
                <button @click="pasteFromClipboard('left')" class="icon-btn" title="粘贴">
                  <i class="far fa-paste"></i>
                </button>
                <button @click="clearInput('left')" class="icon-btn" title="清空">
                  <i class="far fa-trash-can"></i>
                </button>
              </div>
            </div>
            <div class="sidebar-toggle">
              <button @click="toggleSidebar" class="toggle-btn">
                {{ sidebarOpen ? '◀' : '▶' }}
              </button>
            </div>
            <div class="editor-container">
              <div class="line-numbers">
                <div v-for="n in leftLineCount" :key="n" class="line-number">{{ n }}</div>
              </div>
              <textarea
                  ref="leftEditorRef"
                  v-model="leftText"
                  @input="updateLeftLineCount"
                  placeholder="请输入或粘贴要对比的内容 A"
                  class="text-editor"
              ></textarea>
            </div>
          </div>

          <!-- 可拖拽分割线 -->
          <div
              class="splitter"
              @mousedown="startResize"
          ></div>

          <!-- 右侧输入框 -->
          <div class="input-panel" :style="{ width: rightPanelWidth + '%' }">
            <div class="panel-header">
              <span class="panel-label">文本 B</span>
              <div class="panel-actions">
                <button @click="copyToClipboard('right')" class="icon-btn" title="复制">
                  <i class="far fa-copy"></i>
                </button>
                <button @click="pasteFromClipboard('right')" class="icon-btn" title="粘贴">
                  <i class="far fa-paste"></i>
                </button>
                <button @click="clearInput('right')" class="icon-btn" title="清空">
                  <i class="far fa-trash-can"></i>
                </button>
              </div>
            </div>
            <div class="editor-container">
              <div class="line-numbers">
                <div v-for="n in rightLineCount" :key="n" class="line-number">{{ n }}</div>
              </div>
              <textarea
                  ref="rightEditorRef"
                  v-model="rightText"
                  @input="updateRightLineCount"
                  placeholder="请输入或粘贴要对比的内容 B"
                  class="text-editor"
              ></textarea>
            </div>
          </div>
        </div>

        <!-- 对比结果区域 -->
        <div class="result-container" v-if="compareResult">
          <div class="result-header">
            <span class="result-label">对比结果</span>
            <div class="result-stats">
              <span class="stat-item">
                <span class="stat-label">相同:</span>
                <span class="stat-value same">{{ compareResult.stats.same }}</span>
              </span>
              <span class="stat-item">
                <span class="stat-label">插入:</span>
                <span class="stat-value insert">{{ compareResult.stats.insert }}</span>
              </span>
              <span class="stat-item">
                <span class="stat-label">删除:</span>
                <span class="stat-value delete">{{ compareResult.stats.delete }}</span>
              </span>
              <span class="stat-item">
                <span class="stat-label">修改:</span>
                <span class="stat-value modify">{{ compareResult.stats.modify }}</span>
              </span>
            </div>
            <button @click="resultFullscreen = true" class="icon-btn" title="全屏展示">
              <i class="fas fa-expand"></i>
            </button>
          </div>
          <div class="result-content" ref="resultContentRef">
            <div class="result-panel left-result" ref="leftResultRef" @scroll="handleLeftScroll">
              <div class="result-line" v-for="(line, index) in compareResult.left" :key="index">
                <span class="line-number">{{ line.lineNumber || index + 1 }}</span>
                <span
                    :class="['line-content', line.type, { 'inline-diff': line.inlineHighlight }]"
                    v-html="line.html || escapeHtml(line.content)"
                ></span>
              </div>
            </div>
            <div class="result-panel right-result" ref="rightResultRef" @scroll="handleRightScroll">
              <div class="result-line" v-for="(line, index) in compareResult.right" :key="index">
                <span class="line-number">{{ line.lineNumber || index + 1 }}</span>
                <span
                    :class="['line-content', line.type, { 'inline-diff': line.inlineHighlight }]"
                    v-html="line.html || escapeHtml(line.content)"
                ></span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- 对比结果全屏展示（覆盖整个网页区域） -->
    <Teleport to="body">
      <Transition name="result-fullscreen">
        <div v-if="resultFullscreen" class="result-fullscreen-overlay">
          <div class="result-fullscreen-inner">
            <div class="result-fullscreen-header">
              <span class="result-label">对比结果</span>
              <div class="result-stats" v-if="compareResult">
                <span class="stat-item">
                  <span class="stat-label">相同:</span>
                  <span class="stat-value same">{{ compareResult.stats.same }}</span>
                </span>
                <span class="stat-item">
                  <span class="stat-label">插入:</span>
                  <span class="stat-value insert">{{ compareResult.stats.insert }}</span>
                </span>
                <span class="stat-item">
                  <span class="stat-label">删除:</span>
                  <span class="stat-value delete">{{ compareResult.stats.delete }}</span>
                </span>
                <span class="stat-item">
                  <span class="stat-label">修改:</span>
                  <span class="stat-value modify">{{ compareResult.stats.modify }}</span>
                </span>
              </div>
              <button @click="resultFullscreen = false" class="icon-btn" title="退出全屏">
                <i class="fas fa-compress"></i>
              </button>
            </div>
            <div class="result-fullscreen-content" v-if="compareResult">
              <div class="result-panel left-result result-fullscreen-panel" ref="leftFullscreenResultRef" @scroll="handleLeftFullscreenScroll">
                <div class="result-line" v-for="(line, index) in compareResult.left" :key="index">
                  <span class="line-number">{{ line.lineNumber || index + 1 }}</span>
                  <span
                      :class="['line-content', line.type, { 'inline-diff': line.inlineHighlight }]"
                      v-html="line.html || escapeHtml(line.content)"
                  ></span>
                </div>
              </div>
              <div class="result-panel right-result result-fullscreen-panel" ref="rightFullscreenResultRef" @scroll="handleRightFullscreenScroll">
                <div class="result-line" v-for="(line, index) in compareResult.right" :key="index">
                  <span class="line-number">{{ line.lineNumber || index + 1 }}</span>
                  <span
                      :class="['line-content', line.type, { 'inline-diff': line.inlineHighlight }]"
                      v-html="line.html || escapeHtml(line.content)"
                  ></span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </Transition>
    </Teleport>
  </div>
</template>

<script setup>
import {ref, computed, watch, onMounted, onUnmounted} from 'vue'

const MAX_INPUT_BYTES = 500 * 1024 // 最大 500KB

const leftText = ref('')
const rightText = ref('')
const compareMode = ref('text') // 'text' 或 'json'
const textSubMode = ref('line') // 'line' | 'char'
const ignoreListOrder = ref(false) // 是否忽略列表顺序
const leftPanelWidth = ref(50)
const rightPanelWidth = ref(50)
const isResizing = ref(false)
const leftLineCount = ref(1)
const rightLineCount = ref(1)
const leftEditorRef = ref(null)
const rightEditorRef = ref(null)
const compareResult = ref(null)
const resultFullscreen = ref(false)
const sidebarOpen = ref(false)

// 历史记录
const historyList = ref([])
const STORAGE_KEY = 'comparator-history'
const MAX_HISTORY = 50

// 全屏时禁止页面滚动
watch(resultFullscreen, (isFullscreen) => {
  document.body.style.overflow = isFullscreen ? 'hidden' : ''
})

const leftResultRef = ref(null)
const rightResultRef = ref(null)
const resultContentRef = ref(null)
const leftFullscreenResultRef = ref(null)
const rightFullscreenResultRef = ref(null)
const isScrolling = ref(false)

// 提示消息
const toastMessage = ref('')
const toastType = ref('error')
let toastTimer = null

// 显示提示
const showToast = (message, type = 'error', duration = 3000) => {
  toastMessage.value = message
  toastType.value = type

  if (toastTimer) {
    clearTimeout(toastTimer)
  }

  toastTimer = setTimeout(() => {
    toastMessage.value = ''
  }, duration)
}

// 关闭提示
const closeToast = () => {
  if (toastTimer) {
    clearTimeout(toastTimer)
    toastTimer = null
  }
  toastMessage.value = ''
}

// 获取字符串 UTF-8 字节长度
const getByteLength = (str) => new TextEncoder().encode(str).length

// 将字符串截断到最大字节数（避免切断多字节字符）
const truncateToMaxBytes = (str, maxBytes) => {
  if (getByteLength(str) <= maxBytes) return str
  let end = str.length
  while (end > 0 && getByteLength(str.slice(0, end)) > maxBytes) end--
  return str.slice(0, end)
}

// 应用输入大小限制，超出则截断并提示
const applyInputLimit = (side) => {
  const ref = side === 'left' ? leftText : rightText
  if (getByteLength(ref.value) <= MAX_INPUT_BYTES) return
  ref.value = truncateToMaxBytes(ref.value, MAX_INPUT_BYTES)
  showToast(`内容已超过 500KB 限制，已自动截断`, 'info', 3000)
  if (side === 'left') updateLeftLineCount()
  else updateRightLineCount()
}

// 更新行号
const updateLeftLineCount = () => {
  applyInputLimit('left')
  if (leftText.value) {
    leftLineCount.value = leftText.value.split('\n').length
  } else {
    leftLineCount.value = 1
  }
}

const updateRightLineCount = () => {
  applyInputLimit('right')
  if (rightText.value) {
    rightLineCount.value = rightText.value.split('\n').length
  } else {
    rightLineCount.value = 1
  }
}

// 拖拽调整宽度
const startResize = (e) => {
  isResizing.value = true
  document.addEventListener('mousemove', handleResize)
  document.addEventListener('mouseup', stopResize)
  e.preventDefault()
}

const handleResize = (e) => {
  if (!isResizing.value) return

  const container = document.querySelector('.input-container')
  if (!container) return

  const containerWidth = container.offsetWidth
  const mouseX = e.clientX - container.getBoundingClientRect().left
  const percentage = (mouseX / containerWidth) * 100

  const leftPercent = Math.max(20, Math.min(80, percentage))
  const rightPercent = 100 - leftPercent

  leftPanelWidth.value = leftPercent
  rightPanelWidth.value = rightPercent
}

const stopResize = () => {
  isResizing.value = false
  document.removeEventListener('mousemove', handleResize)
  document.removeEventListener('mouseup', stopResize)
}

// 复制到剪贴板
const copyToClipboard = async (side) => {
  const text = side === 'left' ? leftText.value : rightText.value
  if (!text.trim()) {
    showToast(`${side === 'left' ? '左侧' : '右侧'}内容为空，无法复制`)
    return
  }
  try {
    await navigator.clipboard.writeText(text)
    showToast(`已复制${side === 'left' ? '左侧' : '右侧'}内容到剪贴板`, 'info', 2000)
  } catch (e) {
    showToast('复制失败：' + e.message)
  }
}

// 从剪贴板粘贴
const pasteFromClipboard = async (side) => {
  if (navigator.clipboard && navigator.clipboard.readText) {
    try {
      let text = await navigator.clipboard.readText()
      if (text.trim()) {
        if (getByteLength(text) > MAX_INPUT_BYTES) {
          text = truncateToMaxBytes(text, MAX_INPUT_BYTES)
          showToast('粘贴内容已超过 500KB 限制，已自动截断', 'info', 3000)
        }
        if (side === 'left') {
          leftText.value = text
          updateLeftLineCount()
        } else {
          rightText.value = text
          updateRightLineCount()
        }
      } else {
        showToast('剪贴板内容为空')
      }
    } catch (e) {
      const editorRef = side === 'left' ? leftEditorRef.value : rightEditorRef.value
      if (editorRef) {
        editorRef.focus()
        showToast('请按 Ctrl+V 或 Cmd+V 粘贴内容', 'info', 3000)
      }
    }
  }
}

// 清空输入
const clearInput = (side) => {
  if (side === 'left') {
    leftText.value = ''
    updateLeftLineCount()
  } else {
    rightText.value = ''
    updateRightLineCount()
  }
}

// 清空所有
const clearAll = () => {
  leftText.value = ''
  rightText.value = ''
  compareResult.value = null
  updateLeftLineCount()
  updateRightLineCount()
  showToast('已清空', 'info', 2000)
}

// 转义HTML
const escapeHtml = (text) => {
  const div = document.createElement('div')
  div.textContent = text
  return div.innerHTML
}

// 同步滚动处理（非全屏模式）
const handleLeftScroll = () => {
  if (isScrolling.value) return
  isScrolling.value = true
  if (leftResultRef.value && rightResultRef.value) {
    rightResultRef.value.scrollTop = leftResultRef.value.scrollTop
  }
  setTimeout(() => {
    isScrolling.value = false
  }, 10)
}

const handleRightScroll = () => {
  if (isScrolling.value) return
  isScrolling.value = true
  if (leftResultRef.value && rightResultRef.value) {
    leftResultRef.value.scrollTop = rightResultRef.value.scrollTop
  }
  setTimeout(() => {
    isScrolling.value = false
  }, 10)
}

// 同步滚动处理（全屏模式）
const handleLeftFullscreenScroll = () => {
  if (isScrolling.value) return
  isScrolling.value = true
  if (leftFullscreenResultRef.value && rightFullscreenResultRef.value) {
    rightFullscreenResultRef.value.scrollTop = leftFullscreenResultRef.value.scrollTop
  }
  setTimeout(() => {
    isScrolling.value = false
  }, 10)
}

const handleRightFullscreenScroll = () => {
  if (isScrolling.value) return
  isScrolling.value = true
  if (leftFullscreenResultRef.value && rightFullscreenResultRef.value) {
    leftFullscreenResultRef.value.scrollTop = rightFullscreenResultRef.value.scrollTop
  }
  setTimeout(() => {
    isScrolling.value = false
  }, 10)
}

// 高亮差异文本
const highlightDiff = (text, diffRanges) => {
  if (!diffRanges || diffRanges.length === 0) {
    return escapeHtml(text)
  }

  let result = ''
  let lastIndex = 0

  diffRanges.forEach(range => {
    // 添加差异前的正常文本
    if (range.start > lastIndex) {
      result += escapeHtml(text.substring(lastIndex, range.start))
    }
    // 添加高亮的差异文本
    result += `<span class="diff-highlight">${escapeHtml(text.substring(range.start, range.end))}</span>`
    lastIndex = range.end
  })

  // 添加剩余的文本
  if (lastIndex < text.length) {
    result += escapeHtml(text.substring(lastIndex))
  }

  return result
}

// 计算最小编辑距离的diff（简化版LCS算法）
const computeDiff = (arrA, arrB) => {
  const n = arrA.length
  const m = arrB.length

  // 使用动态规划计算LCS
  const dp = Array(n + 1).fill(null).map(() => Array(m + 1).fill(0))

  for (let i = 1; i <= n; i++) {
    for (let j = 1; j <= m; j++) {
      if (arrA[i - 1] === arrB[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1
      } else {
        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
      }
    }
  }

  // 回溯找出所有匹配点。当同一字符在 B 中出现多次时，优先选 j 更小的（靠左匹配）
  const matches = []
  let i = n, j = m

  while (i > 0 && j > 0) {
    if (arrA[i - 1] === arrB[j - 1]) {
      // 若 dp[i][j-1] === dp[i][j]，说明不匹配 (i-1,j-1) 也能得到同样长的 LCS，可先 j-- 尝试更靠左的匹配
      if (j > 1 && dp[i][j - 1] === dp[i][j]) {
        j--
      } else {
        matches.unshift({x: i - 1, y: j - 1})
        i--
        j--
      }
    } else if (dp[i - 1][j] > dp[i][j - 1]) {
      i--
    } else {
      j--
    }
  }

  // 构建路径：每个匹配点都要加入，否则 compareTextByChar 会漏掉匹配（如 hello vs helloworld 的 o）
  const path = []
  for (const match of matches) {
    path.push({x: match.x, y: match.y})
  }
  if (path.length > 0) {
    const last = path[path.length - 1]
    if (last.x < n || last.y < m) {
      path.push({x: n, y: m})
    }
  } else {
    path.push({x: n, y: m})
  }

  return path
}

// 文本对比 - 行维度（使用最小编辑距离）
const compareTextByLine = (textA, textB) => {
  const linesA = textA.split('\n')
  const linesB = textB.split('\n')

  const n = linesA.length
  const m = linesB.length

  // 使用动态规划计算LCS
  const dp = Array(n + 1).fill(null).map(() => Array(m + 1).fill(0))
  const path = Array(n + 1).fill(null).map(() => Array(m + 1).fill(null))

  for (let i = 1; i <= n; i++) {
    for (let j = 1; j <= m; j++) {
      if (linesA[i - 1] === linesB[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1
        path[i][j] = 'match'
      } else if (dp[i - 1][j] > dp[i][j - 1]) {
        dp[i][j] = dp[i - 1][j]
        path[i][j] = 'delete'
      } else {
        dp[i][j] = dp[i][j - 1]
        path[i][j] = 'insert'
      }
    }
  }

  // 回溯构建结果
  const resultA = []
  const resultB = []
  let stats = {same: 0, insert: 0, delete: 0, modify: 0}

  let i = n, j = m
  let lineNumA = n, lineNumB = m
  const pendingDelete = []
  const pendingInsert = []

  // 判断两行是否应该合并为修改（而不是删除+插入）
  const shouldMergeAsModify = (lineA, lineB) => {
    // 如果两行完全相同，不应该到达这里
    if (lineA === lineB) return false

    // 对于JSON格式的行，检查是否是同一类型的结构
    // 例如：都是键值对，但值不同；或者都是数组元素等
    const trimmedA = lineA.trim()
    const trimmedB = lineB.trim()

    // 如果都是键值对格式（"key": value），且键相同，认为是修改
    const keyValuePattern = /^\s*"([^"]+)":\s*(.+)$/
    const matchA = trimmedA.match(keyValuePattern)
    const matchB = trimmedB.match(keyValuePattern)

    if (matchA && matchB) {
      // 如果键相同，认为是修改
      if (matchA[1] === matchB[1]) {
        return true
      }
      // 如果键不同，认为是删除+插入
      return false
    }

    // 如果都是数组元素或对象结构，检查结构相似性
    // 这里简化处理：如果行结构相似（都有相同的括号、引号等），可能是修改
    const structureA = trimmedA.replace(/"[^"]*"/g, '"..."').replace(/\d+/g, '0')
    const structureB = trimmedB.replace(/"[^"]*"/g, '"..."').replace(/\d+/g, '0')

    // 如果结构相似度较高，认为是修改
    // 这里使用简单的启发式：如果结构字符串的前几个字符相同
    if (structureA.length > 0 && structureB.length > 0) {
      const minLen = Math.min(structureA.length, structureB.length)
      let samePrefix = 0
      for (let k = 0; k < minLen && k < 10; k++) {
        if (structureA[k] === structureB[k]) {
          samePrefix++
        } else {
          break
        }
      }
      // 如果前缀相似度超过50%，认为是修改
      if (samePrefix / minLen > 0.5) {
        return true
      }
    }

    // 默认不合并为修改，分别显示为删除和插入
    return false
  }

  while (i > 0 || j > 0) {
    if (i > 0 && j > 0 && path[i][j] === 'match') {
      // 处理待处理的删除和插入
      // 只有在内容相似时才合并为修改
      while (pendingDelete.length > 0 && pendingInsert.length > 0) {
        const deleteLine = pendingDelete[0]
        const insertLine = pendingInsert[0]
        if (shouldMergeAsModify(deleteLine, insertLine)) {
          resultA.unshift({type: 'modify', content: pendingDelete.shift(), lineNumber: lineNumA--})
          resultB.unshift({type: 'modify', content: pendingInsert.shift(), lineNumber: lineNumB--})
          stats.modify++
        } else {
          // 不相似，分别显示为删除和插入
          resultA.unshift({type: 'delete', content: pendingDelete.shift(), lineNumber: lineNumA--})
          resultB.unshift({type: 'delete', content: '', lineNumber: null})
          stats.delete++
          resultA.unshift({type: 'insert', content: '', lineNumber: null})
          resultB.unshift({type: 'insert', content: pendingInsert.shift(), lineNumber: lineNumB--})
          stats.insert++
        }
      }
      // 处理剩余的删除
      while (pendingDelete.length > 0) {
        resultA.unshift({type: 'delete', content: pendingDelete.shift(), lineNumber: lineNumA--})
        resultB.unshift({type: 'delete', content: '', lineNumber: null})
        stats.delete++
      }
      // 处理剩余的插入
      while (pendingInsert.length > 0) {
        resultA.unshift({type: 'insert', content: '', lineNumber: null})
        resultB.unshift({type: 'insert', content: pendingInsert.shift(), lineNumber: lineNumB--})
        stats.insert++
      }
      // 添加匹配的行
      resultA.unshift({type: 'same', content: linesA[i - 1], lineNumber: lineNumA})
      resultB.unshift({type: 'same', content: linesB[j - 1], lineNumber: lineNumB})
      stats.same++
      i--
      j--
      lineNumA--
      lineNumB--
    } else if (i > 0 && path[i][j] === 'delete') {
      pendingDelete.unshift(linesA[i - 1])
      i--
      lineNumA--
    } else if (j > 0 && path[i][j] === 'insert') {
      pendingInsert.unshift(linesB[j - 1])
      j--
      lineNumB--
    } else if (i > 0) {
      pendingDelete.unshift(linesA[i - 1])
      i--
      lineNumA--
    } else if (j > 0) {
      pendingInsert.unshift(linesB[j - 1])
      j--
      lineNumB--
    } else {
      break
    }
  }

  // 处理剩余的待处理项
  while (pendingDelete.length > 0 && pendingInsert.length > 0) {
    const deleteLine = pendingDelete[0]
    const insertLine = pendingInsert[0]
    if (shouldMergeAsModify(deleteLine, insertLine)) {
      resultA.unshift({type: 'modify', content: pendingDelete.shift(), lineNumber: lineNumA--})
      resultB.unshift({type: 'modify', content: pendingInsert.shift(), lineNumber: lineNumB--})
      stats.modify++
    } else {
      // 不相似，分别显示为删除和插入
      resultA.unshift({type: 'delete', content: pendingDelete.shift(), lineNumber: lineNumA--})
      resultB.unshift({type: 'delete', content: '', lineNumber: null})
      stats.delete++
      resultA.unshift({type: 'insert', content: '', lineNumber: null})
      resultB.unshift({type: 'insert', content: pendingInsert.shift(), lineNumber: lineNumB--})
      stats.insert++
    }
  }
  while (pendingDelete.length > 0) {
    resultA.unshift({type: 'delete', content: pendingDelete.shift(), lineNumber: lineNumA--})
    resultB.unshift({type: 'delete', content: '', lineNumber: null})
    stats.delete++
  }
  while (pendingInsert.length > 0) {
    resultA.unshift({type: 'insert', content: '', lineNumber: null})
    resultB.unshift({type: 'insert', content: pendingInsert.shift(), lineNumber: lineNumB--})
    stats.insert++
  }

  return {left: resultA, right: resultB, stats}
}

// 文本对比 - 字符维度（高亮差异字符）
const compareTextByChar = (textA, textB) => {
  const linesA = textA.split('\n')
  const linesB = textB.split('\n')

  const resultA = []
  const resultB = []
  let stats = {same: 0, insert: 0, delete: 0, modify: 0}

  const maxLines = Math.max(linesA.length, linesB.length)

  for (let i = 0; i < maxLines; i++) {
    const lineA = linesA[i] || ''
    const lineB = linesB[i] || ''

    if (lineA === lineB) {
      resultA.push({type: 'same', content: lineA, html: escapeHtml(lineA), lineNumber: i + 1})
      resultB.push({type: 'same', content: lineB, html: escapeHtml(lineB), lineNumber: i + 1})
      stats.same += lineA.length
    } else {
      // 按字符进行diff，统计按高亮单元（字符）计数
      const charsA = lineA.split('')
      const charsB = lineB.split('')

      const charDiff = computeDiff(charsA, charsB)

      let htmlA = ''
      let htmlB = ''
      let x = 0, y = 0
      let hasDiff = false

      for (let j = 0; j < charDiff.length; j++) {
        const point = charDiff[j]
        const nextPoint = charDiff[j + 1] || {x: charsA.length, y: charsB.length}

        // 从 (x,y) 到匹配点 (point.x, point.y) 之前：只可能是差异（如 na vs aa 中 (0,0) 是修改不是相同）
        while (x < point.x || y < point.y) {
          if (x < point.x && y < point.y) {
            htmlA += `<span class="diff-highlight diff-highlight-modify">${escapeHtml(charsA[x])}</span>`
            htmlB += `<span class="diff-highlight diff-highlight-modify">${escapeHtml(charsB[y])}</span>`
            stats.modify++
            hasDiff = true
            x++
            y++
          } else if (x < point.x) {
            htmlA += `<span class="diff-highlight diff-highlight-delete">${escapeHtml(charsA[x])}</span>`
            htmlB += ''
            stats.delete++
            hasDiff = true
            x++
          } else {
            htmlA += ''
            htmlB += `<span class="diff-highlight diff-highlight-insert">${escapeHtml(charsB[y])}</span>`
            stats.insert++
            hasDiff = true
            y++
          }
        }
        // 仅匹配点 (point.x, point.y) 为相同（终点 (n,m) 不是匹配点，不输出）
        if (x === point.x && y === point.y && point.x < charsA.length && point.y < charsB.length) {
          htmlA += escapeHtml(charsA[x])
          htmlB += escapeHtml(charsB[y])
          stats.same++
          x++
          y++
        }

        // 差异的字符：修改用黄色，仅左为删除（红），仅右为插入（蓝）
        if (x < nextPoint.x && y < nextPoint.y) {
          htmlA += `<span class="diff-highlight diff-highlight-modify">${escapeHtml(charsA[x])}</span>`
          htmlB += `<span class="diff-highlight diff-highlight-modify">${escapeHtml(charsB[y])}</span>`
          stats.modify++
          hasDiff = true
          x++
          y++
        } else if (x < nextPoint.x) {
          htmlA += `<span class="diff-highlight diff-highlight-delete">${escapeHtml(charsA[x])}</span>`
          htmlB += ''
          stats.delete++
          hasDiff = true
          x++
        } else if (y < nextPoint.y) {
          htmlA += ''
          htmlB += `<span class="diff-highlight diff-highlight-insert">${escapeHtml(charsB[y])}</span>`
          stats.insert++
          hasDiff = true
          y++
        }
      }

      // 处理剩余字符
      while (x < charsA.length && y < charsB.length) {
        if (charsA[x] === charsB[y]) {
          htmlA += escapeHtml(charsA[x])
          htmlB += escapeHtml(charsB[y])
          stats.same++
        } else {
          htmlA += `<span class="diff-highlight diff-highlight-modify">${escapeHtml(charsA[x])}</span>`
          htmlB += `<span class="diff-highlight diff-highlight-modify">${escapeHtml(charsB[y])}</span>`
          stats.modify++
          hasDiff = true
        }
        x++
        y++
      }

      while (x < charsA.length) {
        htmlA += `<span class="diff-highlight diff-highlight-delete">${escapeHtml(charsA[x])}</span>`
        stats.delete++
        hasDiff = true
        x++
      }

      while (y < charsB.length) {
        htmlB += `<span class="diff-highlight diff-highlight-insert">${escapeHtml(charsB[y])}</span>`
        stats.insert++
        hasDiff = true
        y++
      }

      resultA.push({
        type: hasDiff ? 'modify' : (lineA ? 'delete' : 'insert'),
        content: lineA,
        html: htmlA || escapeHtml(lineA),
        lineNumber: i + 1,
        inlineHighlight: hasDiff
      })
      resultB.push({
        type: hasDiff ? 'modify' : (lineB ? 'insert' : 'delete'),
        content: lineB,
        html: htmlB || escapeHtml(lineB),
        lineNumber: i + 1,
        inlineHighlight: hasDiff
      })
    }
  }

  return {left: resultA, right: resultB, stats}
}

const isNullOrUndefined = (value) => {
  return value === undefined || value === null
}

const isNotNullAndUndefined = (value) => {
  return value !== undefined && value !== null
}

// 判断是否为基本类型（除字符串外）
const isPrimitiveTypeOrNon = (value) => {
  return isNullOrUndefined(value) ||
      typeof value === 'number' ||
      typeof value === 'boolean' ||
      (typeof value === 'string' && false)
}

const isPrimitiveType = (value) => {
  return isNotNullAndUndefined(value) && (
      typeof value === 'number' ||
      typeof value === 'boolean' ||
      (typeof value === 'string' && false)
  )
}


// 判断是否为字符串
const isStringTypeOrNon = (value) => {
  return isNullOrUndefined(value) || typeof value === 'string'
}

const isStringType = (value) => {
  return isNotNullAndUndefined(value) && typeof value === 'string'
}

// 判断是否为map
const isMapTypeOrNon = (value) => {
  return isNullOrUndefined(value) || (typeof value === 'object' && !Array.isArray(value))
}

const isMapType = (value) => {
  return isNotNullAndUndefined(value) && typeof value === 'object' && !Array.isArray(value)
}

// 判断是否为list
const isListTypeOrNon = (value) => {
  return isNullOrUndefined(value) || Array.isArray(value)
}

const isListType = (value) => {
  return isNotNullAndUndefined(value) && Array.isArray(value)
}

// 判断是否为叶子节点（基本类型或字符串）
const isLeafNodeOrNon = (value) => {
  return isNullOrUndefined(value) ||
      typeof value === 'number' ||
      typeof value === 'boolean' ||
      typeof value === 'string'
}

// 计算字符串的最长公共子串长度
const longestCommonSubstring = (strA, strB) => {
  if (!strA || !strB) return 0

  const m = strA.length
  const n = strB.length
  let maxLen = 0
  const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0))

  for (let i = 1; i <= m; i++) {
    for (let j = 1; j <= n; j++) {
      if (strA[i - 1] === strB[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1
        maxLen = Math.max(maxLen, dp[i][j])
      } else {
        dp[i][j] = 0
      }
    }
  }

  return maxLen
}

// 计算字符串相似度（公共子串占全体的比例）
const stringSimilarity = (strA, strB) => {
  if (strA === strB) return {type: 'same', similarity: 1.0}

  const maxLen = Math.max(strA.length, strB.length)
  if (maxLen === 0) return {type: 'same', similarity: 1.0}

  const lcsLen = longestCommonSubstring(strA, strB)
  if (lcsLen === 0) {
    return {type: 'different', similarity: 0.0}
  }

  const similarity = lcsLen / maxLen
  return {type: 'similar', similarity}
}

// 节点对比结果类型
const NodeComparisonResult = {
  SAME: 'same',      // 相同
  SIMILAR: 'similar', // 相似
  DIFFERENT: 'different' // 不同
}

// 对比两个JSON节点，返回对比结果和相似度
const compareJsonNodes = (nodeA, nodeB, ignoreOrder = false) => {
  const result = (type, similarity) => ({ type, similarity, nodeA, nodeB })

  if (isNullOrUndefined(nodeA) && isNullOrUndefined(nodeB)) {
    return nodeA === nodeB ? result(NodeComparisonResult.SAME, 1.0) : result(NodeComparisonResult.DIFFERENT, 0.0)
  }
  if (isNullOrUndefined(nodeA) || isNullOrUndefined(nodeB)) {
    return result(NodeComparisonResult.DIFFERENT, 0.0)
  }
  if (isPrimitiveType(nodeA) && isPrimitiveType(nodeB)) {
    return nodeA === nodeB ? result(NodeComparisonResult.SAME, 1.0) : result(NodeComparisonResult.DIFFERENT, 0.0)
  }
  if (isStringType(nodeA) && isStringType(nodeB)) {
    const { type, similarity } = stringSimilarity(nodeA, nodeB)
    return result(type, similarity)
  }
  if (typeof nodeA !== typeof nodeB) {
    return result(NodeComparisonResult.DIFFERENT, 0.0)
  }
  if (isMapType(nodeA) && isMapType(nodeB)) return compareMaps(nodeA, nodeB, ignoreOrder)
  if (isListType(nodeA) && isListType(nodeB)) {
    // 根据参数选择是否忽略列表顺序
    return ignoreOrder ? compareListsIgnoreOrder(nodeA, nodeB, ignoreOrder) : compareLists(nodeA, nodeB, ignoreOrder)
  }
  return result(NodeComparisonResult.DIFFERENT, 0.0)
}

// 对比Map（对象）
const compareMaps = (mapA, mapB, ignoreOrder = false) => {
  const keysA = Object.keys(mapA).sort()
  const keysB = Object.keys(mapB).sort()
  const setA = new Set(keysA)
  const setB = new Set(keysB)
  const allKeys = new Set([...keysA, ...keysB])
  const comparisons = []
  let allSame = true
  let allDifferent = true
  let allInsert = true
  let allDelete = true

  for (const key of allKeys) {
    const hasKeyA = setA.has(key)
    const hasKeyB = setB.has(key)
    const valueComparison = compareJsonNodes(
      hasKeyA ? mapA[key] : undefined,
      hasKeyB ? mapB[key] : undefined,
      ignoreOrder
    )
    if (hasKeyA && hasKeyB && valueComparison.type === NodeComparisonResult.DIFFERENT) {
      valueComparison.type = NodeComparisonResult.SIMILAR
      valueComparison.similarity = 0.5
    }
    if (valueComparison.type === NodeComparisonResult.SAME) {
      allDifferent = false
    } else if (valueComparison.type === NodeComparisonResult.SIMILAR) {
      allSame = false
      allDifferent = false
      allInsert = false
      allDelete = false
    } else if (valueComparison.type === NodeComparisonResult.DIFFERENT) {
      allSame = false
      if (isNullOrUndefined(mapA) || hasKeyA) allInsert = false
      if (isNullOrUndefined(mapB) || hasKeyB) allDelete = false
    }
    comparisons.push({ ...valueComparison, key })
  }

  const base = { nodeA: mapA, nodeB: mapB, children: comparisons }
  if (allSame) {
    return { type: NodeComparisonResult.SAME, similarity: 1.0, ...base }
  }
  if (allDifferent && !allInsert && !allDelete) {
    return { type: NodeComparisonResult.DIFFERENT, similarity: 0.0, ...base }
  }
  const avgSimilarity = comparisons.length > 0
    ? comparisons.reduce((sum, c) => sum + c.similarity, 0) / comparisons.length
    : 0
  return { type: NodeComparisonResult.SIMILAR, similarity: avgSimilarity, ...base }
}

// 对比List（数组）- 忽略顺序，使用贪心算法寻找最佳匹配
const compareListsIgnoreOrder = (listA, listB, ignoreOrder = false) => {
  const n = listA.length
  const m = listB.length

  // 计算所有元素对的相似度矩阵
  const similarityMatrix = []
  for (let i = 0; i < n; i++) {
    similarityMatrix[i] = []
    for (let j = 0; j < m; j++) {
      const comp = compareJsonNodes(listA[i], listB[j], ignoreOrder)
      similarityMatrix[i][j] = comp
    }
  }

  // 使用贪心算法找到最佳匹配（不考虑顺序）
  // 每次选择相似度最高的未匹配对
  const matches = []
  const usedA = new Set()
  const usedB = new Set()

  // 创建所有可能的匹配对，按相似度降序排序
  const allPairs = []
  for (let i = 0; i < n; i++) {
    for (let j = 0; j < m; j++) {
      allPairs.push({
        indexA: i,
        indexB: j,
        similarity: similarityMatrix[i][j].similarity,
        comparison: similarityMatrix[i][j]
      })
    }
  }
  allPairs.sort((a, b) => b.similarity - a.similarity)

  // 贪心匹配：选择相似度最高的未匹配对
  for (const pair of allPairs) {
    if (!usedA.has(pair.indexA) && !usedB.has(pair.indexB)) {
      matches.push({
        indexA: pair.indexA,
        indexB: pair.indexB,
        comparison: pair.comparison
      })
      usedA.add(pair.indexA)
      usedB.add(pair.indexB)
    }
  }

  // 添加未匹配的A中元素（删除）
  for (let i = 0; i < n; i++) {
    if (!usedA.has(i)) {
      matches.push({
        indexA: i,
        indexB: undefined,
        comparison: {
          type: NodeComparisonResult.DIFFERENT,
          similarity: 0.0,
          nodeA: listA[i],
          nodeB: undefined
        }
      })
    }
  }

  // 添加未匹配的B中元素（插入）
  for (let j = 0; j < m; j++) {
    if (!usedB.has(j)) {
      matches.push({
        indexA: undefined,
        indexB: j,
        comparison: {
          type: NodeComparisonResult.DIFFERENT,
          similarity: 0.0,
          nodeA: undefined,
          nodeB: listB[j]
        }
      })
    }
  }

  // 按原始顺序排序匹配结果（先A后B，保持展示的一致性）
  matches.sort((a, b) => {
    if (a.indexA !== undefined && b.indexA !== undefined) {
      return a.indexA - b.indexA
    }
    if (a.indexA !== undefined) return -1
    if (b.indexA !== undefined) return 1
    if (a.indexB !== undefined && b.indexB !== undefined) {
      return a.indexB - b.indexB
    }
    return 0
  })

  // 判断父节点类型
  const totalElements = n + m
  const matchedElements = matches.filter(m => m.indexA !== undefined && m.indexB !== undefined).length
  const sameMatches = matches.filter(m =>
      m.indexA !== undefined &&
      m.indexB !== undefined &&
      m.comparison.type === NodeComparisonResult.SAME
  ).length
  const differentMatches = matches.filter(m =>
      m.comparison.type === NodeComparisonResult.DIFFERENT
  ).length

  if (totalElements === 0) {
    return {
      type: NodeComparisonResult.SAME,
      similarity: 1.0,
      nodeA: listA,
      nodeB: listB,
      matches
    }
  }

  if (n !== 0 && m !== 0 && differentMatches === totalElements) {
    return {
      type: NodeComparisonResult.DIFFERENT,
      similarity: 0.0,
      nodeA: listA,
      nodeB: listB,
      matches
    }
  }

  if (sameMatches === matchedElements && matchedElements === totalElements) {
    return {
      type: NodeComparisonResult.SAME,
      similarity: 1.0,
      nodeA: listA,
      nodeB: listB,
      matches
    }
  }

  // 计算加权平均相似度
  const totalSimilarity = matches.reduce((sum, m) => sum + m.comparison.similarity, 0)
  const avgSimilarity = totalElements > 0 ? totalSimilarity / totalElements : 0

  return {
    type: NodeComparisonResult.SIMILAR,
    similarity: avgSimilarity,
    nodeA: listA,
    nodeB: listB,
    matches
  }
}

// 对比List（数组）- 寻找最佳匹配使相似度最高
const compareLists = (listA, listB, ignoreOrder = false) => {

  const n = listA.length
  const m = listB.length

  // 计算所有元素对的相似度矩阵
  const similarityMatrix = []
  for (let i = 0; i < n; i++) {
    similarityMatrix[i] = []
    for (let j = 0; j < m; j++) {
      const comp = compareJsonNodes(listA[i], listB[j], ignoreOrder)
      similarityMatrix[i][j] = comp
    }
  }

  // 使用动态规划寻找最佳匹配（最大化总相似度）
  // dp[i][j] 表示 listA[0..i-1] 和 listB[0..j-1] 的最佳匹配总相似度
  const dp = Array(n + 1).fill(null).map(() => Array(m + 1).fill(0))
  const path = Array(n + 1).fill(null).map(() => Array(m + 1).fill(null))

  // 初始化边界情况：当j=0时，只能跳过A中的元素
  for (let i = 1; i <= n; i++) {
    path[i][0] = 'skipA'
  }
  // 初始化边界情况：当i=0时，只能跳过B中的元素
  for (let j = 1; j <= m; j++) {
    path[0][j] = 'skipB'
  }

  for (let i = 1; i <= n; i++) {
    for (let j = 1; j <= m; j++) {
      const matchSimilarity = similarityMatrix[i - 1][j - 1].similarity
      const matchScore = dp[i - 1][j - 1] + matchSimilarity
      const skipAScore = dp[i - 1][j]
      const skipBScore = dp[i][j - 1]

      if (matchScore >= skipAScore && matchScore >= skipBScore) {
        dp[i][j] = matchScore
        path[i][j] = 'match'
      } else if (skipAScore >= skipBScore) {
        dp[i][j] = skipAScore
        path[i][j] = 'skipA'
      } else {
        dp[i][j] = skipBScore
        path[i][j] = 'skipB'
      }
    }
  }

  // 回溯构建匹配结果
  const matches = []
  let i = n, j = m

  while (i > 0 || j > 0) {
    if (i > 0 && j > 0 && path[i][j] === 'match') {
      matches.unshift({
        indexA: i - 1,
        indexB: j - 1,
        comparison: similarityMatrix[i - 1][j - 1]
      })
      i--
      j--
    } else if (i > 0 && path[i][j] === 'skipA') {
      matches.unshift({
        indexA: i - 1,
        indexB: undefined,
        comparison: {
          type: NodeComparisonResult.DIFFERENT,
          similarity: 0.0,
          nodeA: listA[i - 1],
          nodeB: undefined
        }
      })
      i--
    } else if (j > 0) {
      matches.unshift({
        indexA: undefined,
        indexB: j - 1,
        comparison: {
          type: NodeComparisonResult.DIFFERENT,
          similarity: 0.0,
          nodeA: undefined,
          nodeB: listB[j - 1]
        }
      })
      j--
    } else {
      break
    }
  }

  // 判断父节点类型
  const totalElements = n + m
  const matchedElements = matches.filter(m => m.indexA !== null && m.indexB !== null).length
  const sameMatches = matches.filter(m =>
      m.indexA !== undefined &&
      m.indexB !== undefined &&
      m.comparison.type === NodeComparisonResult.SAME
  ).length
  const differentMatches = matches.filter(m =>
      m.comparison.type === NodeComparisonResult.DIFFERENT
  ).length

  if (totalElements === 0) {
    return {
      type: NodeComparisonResult.SAME,
      similarity: 1.0,
      nodeA: listA,
      nodeB: listB,
      matches
    }
  }

  if (n !== 0 && m !== 0 && differentMatches === totalElements) {
    return {
      type: NodeComparisonResult.DIFFERENT,
      similarity: 0.0,
      nodeA: listA,
      nodeB: listB,
      matches
    }
  }

  if (sameMatches === matchedElements && matchedElements === totalElements) {
    return {
      type: NodeComparisonResult.SAME,
      similarity: 1.0,
      nodeA: listA,
      nodeB: listB,
      matches
    }
  }

  // 计算加权平均相似度
  const totalSimilarity = matches.reduce((sum, m) => sum + m.comparison.similarity, 0)
  const avgSimilarity = totalElements > 0 ? totalSimilarity / totalElements : 0

  return {
    type: NodeComparisonResult.SIMILAR,
    similarity: avgSimilarity,
    nodeA: listA,
    nodeB: listB,
    matches
  }
}

// 在展示行末尾添加/移除逗号的辅助
const addTrailingComma = (line, lastNotEmptyRef) => {
  if (line.content !== '') {
    line.content += ','
    lastNotEmptyRef.current = line
  }
}
const removeTrailingComma = (lastNotEmptyRef) => {
  if (lastNotEmptyRef.current !== undefined) {
    lastNotEmptyRef.current.content = lastNotEmptyRef.current.content.slice(0, -1)
  }
}

// 将对比结果转换为展示格式
const convertComparisonToDisplay = (comparison, indent = 0) => {
  const indentStr = '  '.repeat(indent)
  const childIndentStr = '  '.repeat(indent + 1)
  const isLeaf = !comparison.children && !comparison.matches

  // 根据展示规则确定类型
  const { type, nodeA, nodeB } = comparison
  let displayType = 'same'
  if (type === NodeComparisonResult.DIFFERENT) {
    if (nodeA !== undefined && nodeB === undefined) displayType = 'delete'
    else if (nodeA === undefined && nodeB !== undefined) displayType = 'insert'
    else displayType = 'different'
  } else if (type === NodeComparisonResult.SIMILAR && isLeaf) {
    displayType = 'modify'
  }

  // 处理 Map（对象）
  if (!isLeaf && isMapType(nodeA) && isMapType(nodeB)) {
    const leftLines = []
    const rightLines = []
    const sortedKeys = [...new Set(comparison.children.map(c => c.key).filter(Boolean))].sort()
    const commaLeft = { current: undefined }
    const commaRight = { current: undefined }

    leftLines.push({ type: displayType, content: indentStr + '{' })
    rightLines.push({ type: displayType, content: indentStr + '{' })

    for (const key of sortedKeys) {
      const child = comparison.children.find(c => c.key === key)
      const childResult = convertComparisonToDisplay(child, indent + 1)
      if (childResult.left.length === 0 && childResult.right.length === 0) continue

      const leftFirst = childResult.left[0]?.content ?? null
      const rightFirst = childResult.right[0]?.content ?? null
      const formatFirst = (content) => (content === null || content === '' ? '' : content.trimStart())

      leftLines.push({
        type: childResult.left[0].type,
        content: leftFirst === null || leftFirst === '' ? '' : `${childIndentStr}"${key}": ${formatFirst(leftFirst)}`
      })
      rightLines.push({
        type: childResult.right[0].type,
        content: rightFirst === null || rightFirst === '' ? '' : `${childIndentStr}"${key}": ${formatFirst(rightFirst)}`
      })

      const maxLength = Math.max(childResult.left.length, childResult.right.length)
      for (let j = 1; j < maxLength; j++) {
        leftLines.push(
          j < childResult.left.length ? childResult.left[j] : { type: 'insert', content: '' }
        )
        rightLines.push(
          j < childResult.right.length ? childResult.right[j] : { type: 'delete', content: '' }
        )
      }

      addTrailingComma(leftLines[leftLines.length - 1], commaLeft)
      addTrailingComma(rightLines[rightLines.length - 1], commaRight)
    }

    removeTrailingComma(commaLeft)
    removeTrailingComma(commaRight)
    leftLines.push({ type: displayType, content: indentStr + '}' })
    rightLines.push({ type: displayType, content: indentStr + '}' })
    return { left: leftLines, right: rightLines }
  }

  // 处理 List（数组）
  if (!isLeaf && isListType(nodeA) && isListType(nodeB)) {
    const leftLines = []
    const rightLines = []
    const commaLeft = { current: undefined }
    const commaRight = { current: undefined }

    leftLines.push({ type: displayType, content: indentStr + '[' })
    rightLines.push({ type: displayType, content: indentStr + '[' })

    for (const match of comparison.matches) {
      const childResult = convertComparisonToDisplay(match.comparison, indent + 1)
      if (childResult.left.length === 0 && childResult.right.length === 0) continue

      const leftFirst = childResult.left[0]?.content
      const rightFirst = childResult.right[0]?.content
      const leftFirstLine = {
        type: childResult.left[0].type,
        content: leftFirst === undefined || leftFirst === '' ? '' : `${childIndentStr}${leftFirst.trimStart()}`
      }
      const rightFirstLine = {
        type: childResult.right[0].type,
        content: rightFirst === undefined || rightFirst === '' ? '' : `${childIndentStr}${rightFirst.trimStart()}`
      }
      leftLines.push(leftFirstLine)
      rightLines.push(rightFirstLine)

      let leftLastLine = leftFirstLine
      let rightLastLine = rightFirstLine
      const maxLength = Math.max(childResult.left.length, childResult.right.length)

      for (let j = 1; j < maxLength; j++) {
        const leftLine = j < childResult.left.length ? childResult.left[j] : { type: 'insert', content: '' }
        const rightLine = j < childResult.right.length ? childResult.right[j] : { type: 'delete', content: '' }
        if (leftLine.content !== '') leftLastLine = leftLine
        if (rightLine.content !== '') rightLastLine = rightLine
        leftLines.push(leftLine)
        rightLines.push(rightLine)
      }

      addTrailingComma(leftLastLine, commaLeft)
      addTrailingComma(rightLastLine, commaRight)
    }

    removeTrailingComma(commaLeft)
    removeTrailingComma(commaRight)
    leftLines.push({ type: displayType, content: indentStr + ']' })
    rightLines.push({ type: displayType, content: indentStr + ']' })
    return { left: leftLines, right: rightLines }
  }

  // 处理叶子节点
  const leftLines = []
  const rightLines = []
  const leftData = nodeA !== undefined ? formatJsonData(nodeA, indent) : ['']
  const rightData = nodeB !== undefined ? formatJsonData(nodeB, indent) : ['']

  if (displayType === 'different') {
    for (let i = 0; i < leftData.length; i++) {
      leftLines.push({ type: 'delete', content: leftData[i] })
      rightLines.push({ type: 'delete', content: '' })
    }
    for (let i = 0; i < rightData.length; i++) {
      leftLines.push({ type: 'insert', content: '' })
      rightLines.push({ type: 'insert', content: (i === 0 && rightData[i] !== '' ? indentStr : '') + rightData[i] })
    }
  } else {
    const maxLineSize = Math.max(leftData.length, rightData.length)
    for (let i = 0; i < maxLineSize; i++) {
      const leftContent = i < leftData.length ? leftData[i] : undefined
      const rightContent = i < rightData.length ? (i === 0 && rightData[i] !== '' ? indentStr : '') + rightData[i] : undefined
      leftLines.push({ type: displayType, content: leftContent ?? '' })
      rightLines.push({ type: displayType, content: rightContent ?? '' })
    }
  }
  return { left: leftLines, right: rightLines }
}


// 格式化JSON值为字符串
const formatJsonData = (value, indent = 0) => {
  const indentStr = '  '.repeat(indent)

  if (value === null) {
    return ["null"]
  }

  if (typeof value === 'string') {
    return [`"${value}"`]
  }

  if (typeof value === 'number' || typeof value === 'boolean') {
    return [String(value)]
  }

  if (Array.isArray(value)) {
    if (value.length === 0) return ['[]']
    const lines = ["["]
    for (let i = 0; i < value.length; i++) {
      const itemLines = formatJsonData(value[i], indent + 1)
      for (let j = 0; j < itemLines.length; j++) {
        let content = j === 0 ? `${indentStr}  ` : ''
        content = content + itemLines[j]
        if (i < value.length - 1 && j === itemLines.length - 1) {
          content = content + ','
        }
        lines.push(content)
      }
    }
    lines.push(`${indentStr}]`)
    return lines
  }

  if (typeof value === 'object') {
    const keys = Object.keys(value).sort()
    if (keys.length === 0) return ['{}']
    const lines = ['{']
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      const val = value[key]
      const itemLines = formatJsonData(val, indent + 1)
      for (let j = 0; j < itemLines.length; j++) {
        let content = j === 0 ? `${indentStr}  "${key}": ` : ''
        content = content + itemLines[j]
        if (i < value.length - 1 && j === itemLines.length - 1) {
          content = content + ','
        }
        lines.push(content)
      }
    }
    lines.push(`${indentStr}}`)
    return lines
  }

  return [String(value)]
}

// 递归计算统计信息
const calculateStats = (comparison) => {
  const stats = {same: 0, insert: 0, delete: 0, modify: 0}
  const isLeaf = isLeafNodeOrNon(comparison.nodeA) || isLeafNodeOrNon(comparison.nodeB)

  if (comparison.type === NodeComparisonResult.DIFFERENT) {
    // 不同节点：根据展示规则，左侧显示为删除，右侧显示为插入
    if (comparison.nodeA !== undefined && comparison.nodeB === undefined) {
      stats.delete++
    } else if (comparison.nodeA === undefined && comparison.nodeB !== undefined) {
      stats.insert++
    } else {
      // 两个节点都存在但不同
      stats.delete++ // 左侧
      stats.insert++ // 右侧
    }
  } else if (comparison.type === NodeComparisonResult.SAME ||
      comparison.type === NodeComparisonResult.SIMILAR) {
    // 相同或相似节点
    if (isLeaf) {
      // 叶子节点：视作修改
      if (comparison.type === NodeComparisonResult.SAME) {
        stats.same++;
      } else {
        stats.modify++;
      }
    } else {
      if (comparison.children) {
        comparison.children.forEach(child => {
          const childStats = calculateStats(child)
          stats.same += childStats.same
          stats.insert += childStats.insert
          stats.delete += childStats.delete
          stats.modify += childStats.modify
        })
      }

      if (comparison.matches) {
        comparison.matches.forEach(match => {
          const matchStats = calculateStats(match.comparison)
          stats.same += matchStats.same
          stats.insert += matchStats.insert
          stats.delete += matchStats.delete
          stats.modify += matchStats.modify
        })
      }
    }
  }

  return stats
}

// JSON对比（按节点维度）
const compareJson = (textA, textB) => {
  try {
    const jsonA = JSON.parse(textA)
    const jsonB = JSON.parse(textB)

    // 执行节点对比，传递忽略列表顺序的参数
    const comparison = compareJsonNodes(jsonA, jsonB, ignoreListOrder.value)
    console.log(comparison)

    // 转换为展示格式
    const displayResult = convertComparisonToDisplay(comparison, 0)
    console.log(displayResult)

    // 计算统计信息
    const stats = calculateStats(comparison)

    // 为每行添加行号
    const maxLines = Math.max(displayResult.left.length, displayResult.right.length)
    const leftLines = []
    const rightLines = []

    for (let i = 0; i < maxLines; i++) {
      const leftLine = displayResult.left[i] || {type: 'same', content: ''}
      const rightLine = displayResult.right[i] || {type: 'same', content: ''}

      leftLines.push({
        type: leftLine.type,
        content: leftLine.content,
        lineNumber: i + 1
      })
      rightLines.push({
        type: rightLine.type,
        content: rightLine.content,
        lineNumber: i + 1
      })
    }

    return {
      left: leftLines,
      right: rightLines,
      stats
    }
  } catch (e) {
    throw new Error('JSON解析失败：' + e.message)
  }
}

// 执行对比
const performCompare = () => {
  if (!leftText.value.trim() && !rightText.value.trim()) {
    showToast('请输入要对比的内容')
    return
  }

  try {
    if (compareMode.value === 'json') {
      compareResult.value = compareJson(leftText.value, rightText.value)
    } else {
      // 文本对比
      if (textSubMode.value === 'line') {
        compareResult.value = compareTextByLine(leftText.value, rightText.value)
      } else {
        compareResult.value = compareTextByChar(leftText.value, rightText.value)
      }
    }
    // 保存到历史记录
    saveToHistory()
    showToast('对比完成', 'info', 2000)
  } catch (e) {
    showToast(e.message)
    compareResult.value = null
  }
}

// 复制结果
const copyResult = async () => {
  if (!compareResult.value) {
    showToast('没有对比结果可复制')
    return
  }

  try {
    const leftContent = compareResult.value.left.map(l => l.content).join('\n')
    const rightContent = compareResult.value.right.map(l => l.content).join('\n')
    const result = `文本A:\n${leftContent}\n\n文本B:\n${rightContent}`
    await navigator.clipboard.writeText(result)
    showToast('已复制对比结果到剪贴板', 'info', 2000)
  } catch (e) {
    showToast('复制失败：' + e.message)
  }
}


const compareTest = () => {
  let total = {
    left: [],
    right: [],
    stats: {same: 0, insert: 0, delete: 0, modify: 0}
  }
  const cases = [
    ["[]","[\"a\"]"],
    ["[\"a\"]", "[]"],
    ["[[\"a\",\"b\"]]", "[]"],
    ["[]","[\"a\",\"b\"]"],
    ["[]","[[\"a\",\"b\"]]"],
    ["{}","{\"a\": {\"c\":\"d\"}}"],
    ["{}", "{\"a\":\"b\"}"],
    ["[\"a\",\"b\"]", "[]"],
    ["{\"a\": {\"c\":\"d\"}}", "[]"],
    ["{\"a\":\"b\"}", "{}"],
    ["{\"a\":\"a\",\"b\":\"b\",\"d\":null}", "{\"a\":\"a\",\"c\":\"c\"}"],
    ["[\"a\",\"b\",null]", "[\"a\",\"c\"]"],
    ["[{\"a\":\"a\"},{\"b\":\"b\"},null]", "[{\"a\":\"a\"},{\"c\":\"c\"}]"],
    ["{\"a\":\"a\",\"b\":\"b\",\"c\":null}", "{\"a\":\"a\",\"c\":\"c\"}"],
    ["[{\"c\":\"d\"}]","[[\"c\",\"d\"]]"],
    ["{\"a\": {\"c\":\"d\"}}","{\"a\": [\"c\",\"d\"]}"],
    ["{\"a\": {\"c\":\"d\"}}","[\"c\",\"d\"]"],
    ["{\"a\": {\"c\":\"d\"}}","{\"a\": null}"],
    ["{\"a\": null}","{\"a\": {\"c\":\"d\"}}"],
    ["{\"a\": {\"c\":\"d\"}}","{}"],
    ["{\"a\":\"a1\"}", "{\"b\":\"b2\"}"],
    ["{\"a\":{\"c\":\"d\",\"e\":\"f\"}}", "{\"b\":{\"c\":\"d\",\"m\":\"l\"}}"],
    ["{\"a\":{\"c\":\"d\",\"e\":\"f\"}}", "{\"a\":{\"c\":\"d\",\"e\":\"l\"}}"],
    ["{\"a\": {\"c\":\"d\"}}", "{\"a\":[\"c\",\"d\"]}"],
    ["[{\"a\":\"a1\"},{\"b\":\"b2\"}]", "[{\"c\":\"b1\"},{\"a\":\"a1\"}]"],
    ["[{\"a\":\"a1\"},{\"b\":\"b2\"}]", "[{\"b\":\"b1\"},{\"a\":\"a1\"}]"],
    ["[{\"a\":\"a1\"},{\"b\":\"b2\"}]", "[{\"c\":\"b1\"},{\"a\":\"a1\"}]"],
    ["{\"a\":{\"c\":\"d\",\"e\":\"f\"},\"c\":\"a\",\"d\":\"e\",\"g\":[\"a\",\"b\",\"c\"],\"h\":[\"a\"],\"i\":[{\"a\":\"a1\"},{\"b\":\"b2\"},{\"c\":\"b1\"}]}", "{\"b\":{\"c\":\"d\",\"m\":\"l\"},\"c\":\"a\",\"d\":\"f\",\"g\":[\"a\",\"c\",\"b\"],\"h\":[\"b\",\"a\"],\"i\":[{\"b\":\"b1\"},{\"a\":\"a1\"},{\"d\":\"b1\"}]}"],
    ["[[[\"a\",\"b\"],[]]]","[[[\"a\",\"b\",\"c\"],[\"b\"]],[\"c\"]]"]
  ]
  for (let item of cases) {
    console.log("=================================================")
    console.log(item)
    console.log("=================================================")
    let res = compareJson(item[0], item[1])
    total.left.push({type: 'same', content: ''}, {
      type: 'same',
      content: '====================================='
    }, {type: 'same', content: ''}, {type: 'same', content: item[0]}, {type: 'same', content: ''}, {
      type: 'same',
      content: '-----------------------------------'
    })
    total.right.push({type: 'same', content: ''}, {
      type: 'same',
      content: '====================================='
    }, {type: 'same', content: ''}, {type: 'same', content: item[1]}, {type: 'same', content: ''}, {
      type: 'same',
      content: '-----------------------------------'
    })
    total.left.push({type: 'same', content: JSON.stringify(res.stats)}, {
      type: 'same',
      content: '-----------------------------------'
    })
    total.right.push({type: 'same', content: JSON.stringify(res.stats)}, {
      type: 'same',
      content: '-----------------------------------'
    })
    total.left.push(...res.left)
    total.right.push(...res.right)
  }
  compareResult.value = total
}

// 切换侧栏
const toggleSidebar = () => {
  sidebarOpen.value = !sidebarOpen.value
}

// 格式化时间
const formatTime = (timestamp) => {
  const date = new Date(timestamp)
  const now = new Date()
  const diff = now - date
  
  if (diff < 60000) return '刚刚'
  if (diff < 3600000) return Math.floor(diff / 60000) + '分钟前'
  if (diff < 86400000) return Math.floor(diff / 3600000) + '小时前'
  
  return date.toLocaleString('zh-CN', {
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit'
  })
}

// 截断文本
const truncateText = (text, maxLength) => {
  if (!text) return ''
  if (text.length <= maxLength) return text
  return text.substring(0, maxLength) + '...'
}

// 获取模式标签
const getModeLabel = (mode, subMode, ignoreOrder) => {
  if (mode === 'json') {
    return ignoreOrder ? 'JSON对比（忽略列表顺序）' : 'JSON对比'
  } else {
    return subMode === 'line' ? '文本对比（行维度）' : '文本对比（字符维度）'
  }
}

// 保存到历史记录
const saveToHistory = () => {
  // 如果两个输入框都为空，不保存
  if (!leftText.value.trim() && !rightText.value.trim()) {
    return
  }

  const historyItem = {
    leftText: leftText.value,
    rightText: rightText.value,
    compareMode: compareMode.value,
    textSubMode: textSubMode.value,
    ignoreListOrder: ignoreListOrder.value,
    time: Date.now()
  }
  
  // 从localStorage读取现有历史
  let history = []
  try {
    const stored = localStorage.getItem(STORAGE_KEY)
    if (stored) {
      history = JSON.parse(stored)
    }
  } catch (e) {
    console.error('读取历史记录失败', e)
  }
  
  // 检查是否与最新记录相同，如果相同则不保存
  if (history.length > 0) {
    const lastItem = history[0]
    if (lastItem.leftText === historyItem.leftText &&
        lastItem.rightText === historyItem.rightText &&
        lastItem.compareMode === historyItem.compareMode &&
        lastItem.textSubMode === historyItem.textSubMode &&
        lastItem.ignoreListOrder === historyItem.ignoreListOrder) {
      return
    }
  }
  
  // 添加到开头
  history.unshift(historyItem)
  
  // 限制最多50条
  if (history.length > MAX_HISTORY) {
    history = history.slice(0, MAX_HISTORY)
  }
  
  // 保存到localStorage
  try {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(history))
    loadHistoryList()
  } catch (e) {
    console.error('保存历史记录失败', e)
  }
}

// 加载历史记录列表
const loadHistoryList = () => {
  try {
    const stored = localStorage.getItem(STORAGE_KEY)
    if (stored) {
      historyList.value = JSON.parse(stored)
    }
  } catch (e) {
    console.error('加载历史记录失败', e)
    historyList.value = []
  }
}

// 加载历史记录
const loadHistory = (item) => {
  leftText.value = item.leftText || ''
  rightText.value = item.rightText || ''
  compareMode.value = item.compareMode || 'text'
  textSubMode.value = item.textSubMode || 'line'
  ignoreListOrder.value = item.ignoreListOrder || false
  updateLeftLineCount()
  updateRightLineCount()
  compareResult.value = null
}

onMounted(() => {
  updateLeftLineCount()
  updateRightLineCount()
  loadHistoryList()
  // compareTest()
})

onUnmounted(() => {
  document.removeEventListener('mousemove', handleResize)
  document.removeEventListener('mouseup', stopResize)
  if (toastTimer) {
    clearTimeout(toastTimer)
  }
})
</script>

<style scoped>
/* 浮层提示样式 */
.toast-notification {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  min-width: 300px;
  max-width: 90%;
  padding: 0.75rem 1rem;
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  display: flex;
  align-items: center;
  font-size: 0.875rem;
}

.toast-notification.error {
  background: #fff5f5;
  color: #c33;
  border: 1px solid #ffcccc;
}

.toast-notification.info {
  background: #f0f9ff;
  color: #0369a1;
  border: 1px solid #bae6fd;
}

.toast-content {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
  min-width: 0;
}

.toast-content svg,
.toast-content i {
  flex-shrink: 0;
}

.toast-content span {
  flex: 1;
  word-break: break-word;
}

.toast-close-btn {
  flex-shrink: 0;
  margin-left: 0.75rem;
  padding: 0.25rem;
  border: none;
  background: transparent;
  border-radius: 4px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: inherit;
  opacity: 0.6;
  transition: all 0.2s;
  width: 20px;
  height: 20px;
}

.toast-close-btn:hover {
  opacity: 1;
  background: rgba(0, 0, 0, 0.08);
}

.toast-notification.error .toast-close-btn:hover {
  background: rgba(204, 51, 51, 0.15);
}

.toast-notification.info .toast-close-btn:hover {
  background: rgba(3, 105, 161, 0.15);
}

.toast-close-btn:active {
  opacity: 0.8;
}

.toast-close-btn svg,
.toast-close-btn i {
  display: block;
  font-size: 14px;
}

/* 浮层提示动画 */
.toast-enter-active {
  animation: slideUp 0.3s ease-out;
}

.toast-leave-active {
  animation: slideDown 0.3s ease-in;
}

@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateX(-50%) translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }
}

@keyframes slideDown {
  from {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }
  to {
    opacity: 0;
    transform: translateX(-50%) translateY(20px);
  }
}

.tool-page {
  height: calc(100vh - 64px);
  display: flex;
  flex-direction: column;
  margin: -1rem;
  padding: 0;
  background: #ffffff;
}

.main-container {
  flex: 1;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
  background: #ffffff;
}

/* 侧栏样式 */
.sidebar {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 300px;
  background: #ffffff;
  border-right: 1px solid #e5e5e5;
  transform: translateX(-100%);
  transition: transform 0.3s ease;
  z-index: 10;
  display: flex;
  flex-direction: column;
  box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
}

.sidebar-open {
  transform: translateX(0);
}

.content-wrapper.sidebar-pushed {
  margin-left: 300px;
}

.sidebar-header {
  padding: 1rem;
  border-bottom: 1px solid #e5e5e5;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #ffffff;
}

.sidebar-header h3 {
  margin: 0;
  font-size: 1rem;
  color: #1a1a1a;
  font-weight: 500;
}

.close-btn {
  background: none;
  border: none;
  font-size: 1.5rem;
  cursor: pointer;
  color: #666666;
  padding: 0;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: color 0.2s;
}

.close-btn:hover {
  color: #1a1a1a;
}

.sidebar-content {
  flex: 1;
  overflow-y: auto;
  padding: 0.5rem;
  background: #ffffff;
}

.empty-history {
  padding: 2rem;
  text-align: center;
  color: #999999;
  font-size: 0.875rem;
}

.history-item {
  padding: 0.75rem;
  margin-bottom: 0.5rem;
  background: #ffffff;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s;
  border: 1px solid #e5e5e5;
}

.history-item:hover {
  background: #f5f5f5;
  border-color: #d0d0d0;
}

.history-time {
  font-size: 0.75rem;
  color: #999999;
  margin-bottom: 0.25rem;
}

.history-preview {
  font-size: 0.875rem;
  color: #1a1a1a;
}

.history-mode {
  font-weight: 500;
  color: #1a1a1a;
  margin-bottom: 0.25rem;
  font-size: 0.8125rem;
}

.history-text {
  font-family: 'Courier New', monospace;
  word-break: break-all;
  color: #666666;
  font-size: 0.8125rem;
}

.content-wrapper {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: #ffffff;
  transition: margin-left 0.3s ease;
}

/* 工具栏 */
.toolbar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.75rem 1rem;
  background: #ffffff;
  border-bottom: 1px solid #e5e5e5;
  flex-wrap: wrap;
}

.mode-selector {
  display: flex;
  gap: 0.25rem;
}

.mode-btn {
  padding: 0.375rem 0.75rem;
  border: 1px solid #e5e5e5;
  border-radius: 4px;
  background: transparent;
  color: #666666;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s;
}

.mode-btn:hover {
  background: #f5f5f5;
  color: #1a1a1a;
  border-color: #d0d0d0;
}

.mode-btn.active {
  background: #1a1a1a;
  color: #ffffff;
  border-color: #1a1a1a;
}

.submode-selector {
  display: flex;
  gap: 0.25rem;
  padding-left: 0.5rem;
  border-left: 1px solid #e5e5e5;
}

.submode-btn {
  padding: 0.25rem 0.5rem;
  border: 1px solid #e5e5e5;
  border-radius: 3px;
  background: transparent;
  color: #666666;
  font-size: 0.8125rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s;
}

.submode-btn:hover {
  background: #f5f5f5;
  color: #1a1a1a;
  border-color: #d0d0d0;
}

.submode-btn.active {
  background: #1a1a1a;
  color: #ffffff;
  border-color: #1a1a1a;
}

.switch-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  user-select: none;
}

.switch-input {
  width: 36px;
  height: 20px;
  appearance: none;
  background: #e5e5e5;
  border-radius: 10px;
  position: relative;
  cursor: pointer;
  transition: background 0.2s;
  outline: none;
}

.switch-input:checked {
  background: #1a1a1a;
}

.switch-input::before {
  content: '';
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #ffffff;
  top: 2px;
  left: 2px;
  transition: transform 0.2s;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}

.switch-input:checked::before {
  transform: translateX(16px);
}

.switch-text {
  font-size: 0.8125rem;
  color: #666666;
  font-weight: 500;
}

.switch-input:checked + .switch-text {
  color: #1a1a1a;
}

.toolbar-actions {
  display: flex;
  gap: 0.5rem;
  margin-left: auto;
}

.action-btn {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border: 1px solid #e5e5e5;
  border-radius: 4px;
  background: transparent;
  color: #666666;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s;
}

.action-btn:hover {
  background: #f5f5f5;
  color: #1a1a1a;
  border-color: #d0d0d0;
}

.action-btn.primary {
  background: #1a1a1a;
  color: #ffffff;
  border-color: #1a1a1a;
}

.action-btn.primary:hover {
  background: #333333;
}

.action-btn i {
  font-size: 14px;
}

/* 输入容器 */
.input-container {
  flex: 1;
  display: flex;
  overflow: hidden;
  min-height: 0;
}

.input-panel {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: #ffffff;
  position: relative;
}

.sidebar-toggle {
  position: absolute;
  left: 0;
  bottom: 1rem;
  z-index: 5;
}

.toggle-btn {
  width: 32px;
  height: 48px;
  background: #1a1a1a;
  color: #ffffff;
  border: none;
  border-radius: 0 4px 4px 0;
  cursor: pointer;
  font-size: 0.875rem;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s;
  box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
}

.toggle-btn:hover {
  background: #333333;
}

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 0.75rem;
  background: #fafafa;
  border-bottom: 1px solid #e5e5e5;
}

.panel-label {
  font-size: 0.875rem;
  font-weight: 500;
  color: #1a1a1a;
}

.panel-actions {
  display: flex;
  gap: 0.25rem;
}

.icon-btn {
  padding: 0.25rem;
  border: none;
  background: transparent;
  border-radius: 3px;
  cursor: pointer;
  transition: all 0.2s;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  color: #666666;
}

.icon-btn:hover {
  background: #e5e5e5;
  color: #1a1a1a;
}

.icon-btn i {
  font-size: 12px;
}

.editor-container {
  flex: 1;
  display: flex;
  position: relative;
  overflow: hidden;
  background: #ffffff;
}

.line-numbers {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 40px;
  padding: 1rem 0.5rem;
  background: #fafafa;
  border-right: 1px solid #e5e5e5;
  font-family: 'Courier New', monospace;
  font-size: 14px;
  color: #999999;
  text-align: right;
  user-select: none;
  z-index: 1;
  overflow-y: auto;
}

.line-number {
  line-height: 1.6;
  height: 22.4px;
}

.text-editor {
  flex: 1;
  width: 100%;
  padding: 1rem 1rem 1rem 3rem;
  border: none;
  font-family: 'Courier New', monospace;
  font-size: 14px;
  resize: none;
  outline: none;
  background: #ffffff;
  color: #1a1a1a;
  line-height: 1.6;
  overflow-y: auto;
}

.text-editor:focus {
  background: #ffffff;
}

.text-editor::placeholder {
  color: #999999;
}

/* 分割线 */
.splitter {
  width: 1px;
  background: #e5e5e5;
  cursor: col-resize;
  position: relative;
  flex-shrink: 0;
  transition: background 0.2s;
}

.splitter:hover {
  background: #d0d0d0;
  width: 2px;
}

.splitter::before {
  content: '';
  position: absolute;
  left: -2px;
  right: -2px;
  top: 0;
  bottom: 0;
}

/* 对比结果 */
.result-container {
  border-top: 1px solid #e5e5e5;
  display: flex;
  flex-direction: column;
  height: 50%;
  min-height: 300px;
  background: #ffffff;
}

.result-header {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.75rem 1rem;
  background: #fafafa;
  border-bottom: 1px solid #e5e5e5;
}

.result-label {
  font-size: 0.875rem;
  font-weight: 500;
  color: #1a1a1a;
}

.result-stats {
  display: flex;
  gap: 1rem;
  flex: 1;
}

.stat-item {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.8125rem;
}

.stat-label {
  color: #666666;
}

.stat-value {
  font-weight: 500;
  padding: 0.125rem 0.5rem;
  border-radius: 3px;
  font-size: 0.75rem;
}

.stat-value.same {
  background: #d1fae5;
  color: #065f46;
}

.stat-value.insert {
  background: #dbeafe;
  color: #1e40af;
}

.stat-value.delete {
  background: #fee2e2;
  color: #991b1b;
}

.stat-value.modify {
  background: #fef3c7;
  color: #92400e;
}

.result-content {
  flex: 1;
  display: flex;
  overflow: hidden;
}

/* 对比结果全屏展示（覆盖整个网页） */
.result-fullscreen-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: #ffffff;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.result-fullscreen-inner {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

.result-fullscreen-header {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.75rem 1rem;
  background: #fafafa;
  border-bottom: 1px solid #e5e5e5;
  flex-shrink: 0;
}

.result-fullscreen-content {
  flex: 1;
  display: flex;
  overflow: hidden;
  min-height: 0;
}

.result-fullscreen-panel {
  flex: 1;
  overflow-y: auto;
  padding: 0.5rem;
  font-family: 'Courier New', monospace;
  font-size: 14px;
  line-height: 1.6;
}

.result-fullscreen-enter-active,
.result-fullscreen-leave-active {
  transition: opacity 0.2s ease;
}

.result-fullscreen-enter-from,
.result-fullscreen-leave-to {
  opacity: 0;
}

.result-panel {
  flex: 1;
  overflow-y: auto;
  padding: 0.5rem;
  font-family: 'Courier New', monospace;
  font-size: 14px;
  line-height: 1.6;
}

.left-result {
  border-right: 1px solid #e5e5e5;
}

.result-line {
  display: flex;
  min-height: 22.4px;
}

.result-line .line-number {
  width: 40px;
  padding-right: 0.5rem;
  text-align: right;
  color: #999999;
  user-select: none;
  flex-shrink: 0;
}

.result-line .line-content {
  flex: 1;
  white-space: pre-wrap;
  word-break: break-word;
}

.result-line .line-content.same {
  color: #1a1a1a;
  background: transparent;
}

.result-line .line-content.modify {
  color: #1a1a1a;
  background: #fef3c7;
}

.result-line .line-content.delete {
  color: #991b1b;
  background: #fee2e2;
  text-decoration: line-through;
}

.result-line .line-content.insert {
  color: #1e40af;
  background: #dbeafe;
}

/* 单词/字符维度：整行不铺背景，仅高亮片段 */
.result-line .line-content.inline-diff.modify,
.result-line .line-content.inline-diff.delete,
.result-line .line-content.inline-diff.insert {
  background: transparent;
  color: #1a1a1a;
  text-decoration: none;
}

/* v-html 内的元素无 data-v 属性，须用 :deep() 才能命中 */
.result-line .line-content :deep(.diff-highlight) {
  font-weight: 500;
  padding: 0 2px;
  border-radius: 2px;
}

.result-line .line-content :deep(.diff-highlight-delete) {
  background: #fee2e2;
  color: #991b1b;
}

.result-line .line-content :deep(.diff-highlight-insert) {
  background: #dbeafe;
  color: #1e40af;
}

.result-line .line-content :deep(.diff-highlight-modify) {
  background: #fef3c7;
  color: #92400e;
}

@media (max-width: 768px) {
  .tool-page {
    padding: 0;
    margin: -1rem;
    height: calc(100vh - 64px + 2rem);
  }

  .toolbar {
    padding: 0.5rem;
    gap: 0.5rem;
  }

  .mode-btn {
    padding: 0.25rem 0.5rem;
    font-size: 0.8125rem;
  }

  .submode-btn {
    padding: 0.125rem 0.375rem;
    font-size: 0.75rem;
  }

  .action-btn {
    padding: 0.375rem 0.75rem;
    font-size: 0.8125rem;
  }

  .action-btn span {
    display: none;
  }

  .input-panel {
    min-width: 0;
  }

  .line-numbers {
    width: 32px;
    font-size: 12px;
  }

  .text-editor {
    padding-left: 2.5rem;
  }

  .result-container {
    height: 40%;
    min-height: 200px;
  }

  .result-stats {
    flex-direction: column;
    gap: 0.25rem;
  }

  .sidebar {
    width: 80%;
    max-width: 300px;
  }

  .toast-notification {
    bottom: 10px;
    left: 1rem;
    right: 1rem;
    transform: none;
    min-width: auto;
    max-width: none;
  }

  @keyframes slideUp {
    from {
      opacity: 0;
      transform: translateY(20px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  @keyframes slideDown {
    from {
      opacity: 1;
      transform: translateY(0);
    }
    to {
      opacity: 0;
      transform: translateY(20px);
    }
  }
}
</style>


