<template>
  <div class="file-system-container">

    <!-- 顶部工具栏 -->
    <div class="toolbar">
      <div class="toolbar-left">
        <!-- 路径输入 -->
        <div class="path-input-wrapper">
          <!-- ZIP 浏览模式：显示 ZIP 路径和退出按钮 -->
          <div v-if="isBrowsingZip" class="zip-breadcrumb">
            <span class="zip-path-text">{{ displayPath }}</span>
            <a-button size="small" type="outline" @click="exitZipMode">
              <template #icon><icon-close /></template>
              退出 ZIP
            </a-button>
          </div>
          <!-- 正常模式：路径输入 -->
          <a-auto-complete
            v-else
            v-model="filePath"
            :data="pathHistory"
            placeholder="输入路径 (如: C:\Users)"
            class="path-input"
            @select="onPathSelect"
            @pressEnter="onPathEnter"
          />
        </div>
      </div>

      <div class="toolbar-right">
        <!-- 快捷路径下拉 -->
        <a-dropdown v-if="!isBrowsingZip">
          <a-button size="small">
            <template #icon>
              <icon-forward />
            </template>
            快捷访问
          </a-button>
          <template #content>
            <a-doption v-for="shortcut in commonPaths" :key="shortcut.path" @click="goToPath(shortcut.path)">
              <template #icon>{{ shortcut.name.split(' ')[0] }}</template>
              {{ shortcut.name.substring(2) }}
            </a-doption>
          </template>
        </a-dropdown>

        <!-- 历史记录下拉 -->
        <a-dropdown>
          <a-button size="small">
            <template #icon>
              <icon-history />
            </template>
            历史
          </a-button>
          <template #content>
            <a-doption v-for="path in pathHistory.slice(0, 10)" :key="path" @click="goToPath(path)">
              {{ path }}
            </a-doption>
            <a-doption v-if="pathHistory.length === 0" disabled>暂无历史</a-doption>
          </template>
        </a-dropdown>

        <!-- 刷新按钮 -->
        <a-button type="primary" size="small" @click="listDirectory" :loading="fileLoading">
          <template #icon>
            <icon-refresh />
          </template>
          刷新
        </a-button>

        <!-- 切换侧边栏 -->
        <a-button
          size="small"
          :type="showSidebar ? 'primary' : 'text'"
          @click="showSidebar = !showSidebar"
        >
          <template #icon>
            <icon-menu />
          </template>
        </a-button>
      </div>
    </div>

    <!-- 主内容区 -->
    <div class="main-content">
      <!-- 左侧收藏夹侧边栏 -->
      <transition name="slide">
        <div v-show="showSidebar" class="sidebar">
          <div class="sidebar-header">
            <span class="sidebar-title">⭐ 收藏夹</span>
            <span class="sidebar-count">{{ favoriteFiles.length }}</span>
          </div>
          <div class="sidebar-content">
            <div
              v-for="(fav, index) in favoriteFiles"
              :key="fav.path"
              class="sidebar-item"
              :class="{
                'sidebar-item-dragging': draggingState.isDragging && draggingState.draggedIndex === index,
                'sidebar-item-drag-over': draggingState.isDragging && draggingState.draggedIndex !== index
              }"
              :draggable="draggingState.isDragging && draggingState.draggedIndex === index"
              @click="openFavoriteFile(fav.path)"
              @mousedown="onLongPressStart($event, index)"
              @mouseup="onLongPressCancel"
              @mouseleave="onLongPressCancel"
              @touchstart="onLongPressStart($event, index)"
              @touchend="onLongPressCancel"
              @touchcancel="onLongPressCancel"
              @dragstart="onDragStart($event, index)"
              @dragover="onDragOver($event)"
              @drop="onDrop($event, index)"
              @dragend="onDragEnd"
            >
              <span class="sidebar-item-icon">{{ fav.is_dir ? '📁' : '📄' }}</span>
              <span class="sidebar-item-name" :title="fav.name">{{ fav.name }}</span>
              <a-button
                type="text"
                size="mini"
                @click.stop="removeFavorite(fav.path)"
                class="sidebar-item-remove"
              >
                <template #icon>
                  <icon-close />
                </template>
              </a-button>
            </div>
            <div v-if="favoriteFiles.length === 0" class="sidebar-empty">
              <icon-star />
              <span>暂无收藏</span>
              <span class="sidebar-hint">点击文件列表中的星标收藏</span>
            </div>
          </div>
        </div>
      </transition>

      <!-- 文件列表和编辑器区域 -->
      <div class="file-workspace">
        <!-- 文件列表面板 -->
        <div class="file-list-panel" :style="{ width: panelWidth.left + '%' }">
          <div class="panel-header">
            <span class="panel-title">📋 文件列表</span>
            <span class="panel-count">{{ fileList.length }} 项</span>
          </div>
          <div
            class="file-list-wrapper"
            @contextmenu.prevent="handleFileListContextMenu"
          >
            <a-list
              :data="fileList"
              :loading="fileLoading"
              :bordered="false"
              :pagination="false"
              class="compact-list"
            >
              <template #item="{ item }">
                <div
                  class="file-item-row"
                  :class="{ 'file-item-selected': selectedFileItem?.path === item.path }"
                  @click="handleFileClick(item)"
                  :data-file-path="item.path"
                  @dblclick="handleFileDoubleClick(item)"
                >
                  <span class="file-item-icon">{{ getFileIcon(item) }}</span>
                  <!-- 编辑状态 -->
                  <a-input
                    v-if="editingFilePath === item.path"
                    v-model="editingFileName"
                    ref="editingInputRef"
                    size="mini"
                    class="file-name-edit-input"
                    @blur="saveEditingFileName"
                    @keyup.enter="saveEditingFileName"
                    @keyup.esc="cancelEditingFileName"
                    @click.stop
                  />
                  <!-- 正常显示状态 -->
                  <span v-else class="file-item-name" :title="item.name">{{ item.name }}</span>
                  <span v-if="!item.is_dir && editingFilePath !== item.path" class="file-item-size">{{ formatBytes(item.size) }}</span>
                  <a-button
                    v-if="editingFilePath !== item.path"
                    type="text"
                    size="mini"
                    @click.stop="toggleFavorite(item)"
                    class="file-item-fav"
                  >
                    <template #icon>
                      <icon-star-fill v-if="isFavorite(item.path)" :style="{ color: '#ffcd00' }" />
                      <icon-star v-else />
                    </template>
                  </a-button>
                </div>
              </template>
            </a-list>
            <div v-if="fileList.length === 0 && !fileLoading" class="empty-state">
              <span style="font-size: 32px">📭</span>
              <span>此文件夹为空</span>
            </div>
          </div>
        </div>

        <!-- 分隔条 -->
        <div class="resizer" @mousedown="startResizeHorizontal"></div>

        <!-- 文件内容编辑器面板 -->
        <div class="file-editor-panel" :style="{ width: panelWidth.right + '%' }">
          <div class="panel-header">
            <span class="panel-title">
              <template v-if="isImageView">🖼️ 图片预览</template>
              <template v-else-if="isVideoView">🎬 视频预览</template>
              <template v-else-if="isAudioView">🎵 音频预览</template>
              <template v-else-if="isPdfFile">📕 PDF 预览</template>
              <template v-else-if="isHtmlFile">🌐 HTML 预览</template>
              <template v-else-if="isMarkdownFile">📝 Markdown 预览</template>
              <template v-else>📝 文件内容</template>
            </span>
            <a-tooltip
              v-if="currentFileName"
              :content="currentFileFullPath"
              position="bottom"
            >
              <span
                class="panel-filename"
                :class="{ 'file-outside-dir': !isFileInCurrentDirectory && selectedFilePath }"
              >
                {{ currentFileName }}
                <template v-if="!isFileInCurrentDirectory && selectedFilePath">
                  <span class="file-location-hint"> (不在当前目录)</span>
                </template>
              </span>
            </a-tooltip>
          </div>

          <div class="editor-content">
            <!-- 图片预览 -->
            <div v-if="isImageView" class="media-preview">
              <img
                :src="previewUrl"
                class="preview-image"
                @load="onImageLoad"
                @error="onImageError"
                alt="预览"
              />
              <div v-if="imageLoading" class="media-loading">
                <a-spin />
              </div>
              <div class="media-meta">
                <span class="file-name">{{ getFileName(filePath.value) }}</span>
                <span v-if="currentImageDimensions" class="image-dimensions">{{ currentImageDimensions }}</span>
              </div>
            </div>

            <!-- 视频预览 -->
            <div v-else-if="isVideoView" class="media-preview">
              <video :src="previewUrl" controls class="preview-video"></video>
              <div class="media-meta">
                <a-tag color="arcoblue">🎬 视频</a-tag>
              </div>
            </div>

            <!-- 音频预览 -->
            <div v-else-if="isAudioView" class="media-preview">
              <audio :src="previewUrl" controls class="preview-audio"></audio>
              <div class="media-meta">
                <a-tag color="green">🎵 音频</a-tag>
              </div>
            </div>

            <!-- PDF 预览 -->
            <div v-else-if="isPdfFile" class="media-preview media-preview-pdf">
              <iframe :src="previewUrl" class="preview-pdf"></iframe>
              <div class="media-meta">
                <a-tag color="orangered">📕 PDF</a-tag>
              </div>
            </div>

            <!-- HTML 预览/编辑 -->
            <div v-else-if="isHtmlFile" class="html-preview-wrapper">
              <!-- 编辑模式/预览模式切换按钮 -->
              <div class="preview-mode-switch">
                <!-- 保存按钮 -->
                <a-tooltip v-if="canSaveFile" position="left" content="保存 (Ctrl+S)">
                  <a-button
                    type="primary"
                    size="small"
                    @click="handleSaveContent"
                  >
                    <template #icon><icon-save /></template>
                  </a-button>
                </a-tooltip>
                <!-- 编辑/预览切换 -->
                <a-tooltip position="left" :content="canPreviewFile ? (isEditMode ? '切换到预览' : '切换到编辑') : '该文件不支持预览模式'">
                  <a-button
                    type="primary"
                    size="small"
                    :disabled="!canPreviewFile"
                    @click="toggleEditMode"
                  >
                    <template #icon>
                      <icon-edit v-if="!isEditMode" />
                      <icon-eye v-else />
                    </template>
                  </a-button>
                </a-tooltip>
              </div>

              <!-- 预览模式 -->
              <iframe
                v-if="!isEditMode"
                class="html-preview-content"
                :srcdoc="rendered"
              ></iframe>

              <!-- 编辑模式 -->
              <div v-else class="html-edit-wrapper" :style="{ height: fileContentHeight + 'px' }">
                <CodeEditor
                  v-model="fileContent"
                  :file-extension="currentFileExtension"
                  class="code-editor"
                />
                <!-- 调整高度的手柄 -->
                <div class="resize-handle-v" @mousedown="startResize" title="拖拽调整高度">
                  <div class="resize-dots"></div>
                </div>
              </div>
            </div>

            <!-- Markdown 预览/编辑 -->
            <div v-else-if="isMarkdownFile" class="markdown-preview-wrapper">
              <!-- 编辑模式/预览模式切换按钮 -->
              <div class="preview-mode-switch">
                <!-- 保存按钮 -->
                <a-tooltip v-if="canSaveFile" position="left" content="保存 (Ctrl+S)">
                  <a-button
                    type="primary"
                    size="small"
                    @click="handleSaveContent"
                  >
                    <template #icon><icon-save /></template>
                  </a-button>
                </a-tooltip>
                <!-- 编辑/预览切换 -->
                <a-tooltip position="left" :content="canPreviewFile ? (isEditMode ? '切换到预览' : '切换到编辑') : '该文件不支持预览模式'">
                  <a-button
                    type="primary"
                    size="small"
                    :disabled="!canPreviewFile"
                    @click="toggleEditMode"
                  >
                    <template #icon>
                      <icon-edit v-if="!isEditMode" />
                      <icon-eye v-else />
                    </template>
                  </a-button>
                </a-tooltip>
              </div>

              <!-- 预览模式 -->
              <div v-if="!isEditMode" class="markdown-preview-content markdown-content" v-html="rendered"></div>

              <!-- 编辑模式 -->
              <div v-else class="markdown-edit-wrapper" :style="{ height: fileContentHeight + 'px' }">
                <CodeEditor
                  v-model="fileContent"
                  :file-extension="currentFileExtension"
                  class="code-editor"
                />
                <!-- 调整高度的手柄 -->
                <div class="resize-handle-v" @mousedown="startResize" title="拖拽调整高度">
                  <div class="resize-dots"></div>
                </div>
              </div>
            </div>

            <!-- 文本编辑器（带代码高亮） -->
            <div v-else class="text-editor-wrapper" :style="{ height: fileContentHeight + 'px' }">
              <!-- 编辑模式/预览模式切换按钮 -->
              <div class="preview-mode-switch">
                <!-- 保存按钮 -->
                <a-tooltip v-if="canSaveFile" position="left" content="保存 (Ctrl+S)">
                  <a-button
                    type="primary"
                    size="small"
                    @click="handleSaveContent"
                  >
                    <template #icon><icon-save /></template>
                  </a-button>
                </a-tooltip>
                <!-- 编辑/预览切换 -->
                <a-tooltip position="left" :content="canPreviewFile ? (isEditMode ? '切换到预览' : '切换到编辑') : '该文件不支持预览模式'">
                  <a-button
                    type="primary"
                    size="small"
                    :disabled="!canPreviewFile"
                    @click="toggleEditMode"
                  >
                    <template #icon>
                      <icon-edit v-if="!isEditMode" />
                      <icon-eye v-else />
                    </template>
                  </a-button>
                </a-tooltip>
              </div>

              <CodeEditor
                v-model="fileContent"
                :file-extension="currentFileExtension"
                class="code-editor"
              />
              <!-- 调整高度的手柄 -->
              <div class="resize-handle-v" @mousedown="startResize" title="拖拽调整高度">
                <div class="resize-dots"></div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- 右键菜单 -->
    <div
      v-if="contextMenuVisible"
      class="context-menu"
      :style="{ left: contextMenuPosition.x + 'px', top: contextMenuPosition.y + 'px' }"
      @click.stop
    >
      <div v-if="contextMenuTarget === 'blank'" class="context-menu-item" @click="handleCreateFile">
        <span class="context-menu-icon">📄</span>
        <span>新建文件</span>
        <span class="context-menu-shortcut">Ctrl+N</span>
      </div>
      <div v-if="contextMenuTarget === 'blank'" class="context-menu-item" @click="handleCreateDir">
        <span class="context-menu-icon">📁</span>
        <span>新建文件夹</span>
        <span class="context-menu-shortcut">Ctrl+Shift+N</span>
      </div>
      <div v-if="contextMenuTarget === 'file'" class="context-menu-divider"></div>
      <div v-if="contextMenuTarget === 'file' && selectedContextFile && !selectedContextFile.is_dir && isOfficeFile(selectedContextFile.name)" class="context-menu-item" @click="handleOpenWithSystem">
        <span class="context-menu-icon">🚀</span>
        <span>系统默认程序打开</span>
      </div>
      <div v-if="contextMenuTarget === 'file'" class="context-menu-item" @click="handleRenameSelectedFile">
        <span class="context-menu-icon">✏️</span>
        <span>重命名</span>
        <span class="context-menu-shortcut">F2</span>
      </div>
      <div v-if="contextMenuTarget === 'file'" class="context-menu-item danger" @click="handleDeleteSelectedFile">
        <span class="context-menu-icon">🗑️</span>
        <span>删除</span>
        <span class="context-menu-shortcut">Del</span>
      </div>
    </div>

    <!-- 输入对话框 -->
    <a-modal
      v-model:visible="inputDialogVisible"
      :title="inputDialogTitle"
      :ok-text="inputDialogOkText"
      :cancel-text="inputDialogCancelText"
      @ok="handleInputDialogConfirm"
      @cancel="inputDialogVisible = false"
      :mask-closable="false"
    >
      <a-input
        v-model="inputDialogValue"
        :placeholder="inputDialogPlaceholder"
        @keyup.enter="handleInputDialogConfirm"
        ref="inputDialogInputRef"
        :max-length="255"
        show-word-limit
      />
    </a-modal>
  </div>
</template>

<script setup>
// 定义组件名称，用于 KeepAlive 缓存
defineOptions({
  name: 'FileSystem'
})

import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { Message, Modal } from '@arco-design/web-vue'
import { marked } from 'marked'
import CodeEditor from '@/components/CodeEditor.vue'
import {
  IconFolder,
  IconForward,
  IconStarFill,
  IconStar,
  IconFile,
  IconSave,
  IconDelete,
  IconEraser,
  IconRefresh,
  IconMenu,
  IconClose,
  IconHistory,
  IconEye,
  IconEdit
} from '@arco-design/web-vue/es/icon'
import {
  listDir,
  readFile as readFileApi,
  writeFile as writeFileApi,
  deletePath as deletePathApi,
  createFile,
  createDir,
  listZipContents,
  extractFileFromZip,
  extractFileFromZipToTemp,
  openPath,
  getFileServerURL
} from '@/api'

// 导入公共工具函数和常量
import { STORAGE_KEYS, FILE_EXTENSIONS, DEFAULTS, FILE_SIZE_THRESHOLDS } from '@/utils/constants'
import {
  formatBytes,
  getFileName,
  getFileIcon,
  normalizeFilePath
} from '@/utils/fileUtils'
import {
  getExt,
  isImage,
  isVideo,
  isAudio,
  isVideoAny,
  isPdf,
  isHtml,
  isMarkdown,
  isCode,
  isArchive,
  isExecutable,
  isDatabase,
  isEditableDoc
} from '@/utils/fileHelpers'

// 导入调试日志工具
import { debugLog, debugWarn, debugError } from '@/utils/debugLog'

// 导入 composables
import { useFileOperations } from '@/composables/useFileOperations'
import { useFavoriteFiles } from '@/composables/useFavoriteFiles'
import { useLocalStorage } from '@/composables/useLocalStorage'

// 导入 Wails runtime
import { BrowserOpenURL } from '@/wailsjs/wailsjs/runtime/runtime'

// ========== 使用 Composables ==========

// 文件操作
const {
  filePath,
  fileContent,
  fileList,
  fileLoading,
  listDirectory: listDirBase,
  readFile: readFileBase,
  writeFile,
  deleteFile: deleteFileBase,
} = useFileOperations({
  onSuccess: (operation, data) => {
    debugLog(`${operation} 成功:`, data)
  },
  onError: (operation, error) => {
    console.error(`[FileSystem] ${operation} 失败:`, error)
  }
})

// 收藏功能
const {
  favoriteFiles,
  isFavorite,
  toggleFavorite,
  removeFavorite,
  reorderFavorites,
} = useFavoriteFiles(STORAGE_KEYS.FILESYSTEM.FAVORITE_FILES)

// localStorage管理
const { storedValue: fileContentHeight } = useLocalStorage(
  STORAGE_KEYS.FILESYSTEM.FILE_CONTENT_HEIGHT,
  DEFAULTS.DEFAULT_CONTENT_HEIGHT
)

const { storedValue: showSidebar } = useLocalStorage(
  STORAGE_KEYS.FILESYSTEM.SIDEBAR_VISIBLE,
  false
)

const { storedValue: panelWidth } = useLocalStorage(
  STORAGE_KEYS.FILESYSTEM.PANEL_WIDTH,
  { left: 50, right: 50 }
)

const { storedValue: pathHistory } = useLocalStorage(
  STORAGE_KEYS.FILESYSTEM.PATH_HISTORY,
  []
)

// ========== Zip 文件浏览状态 ==========
const currentZipPath = ref('') // 当前浏览的 zip 文件路径
const currentZipDirectory = ref('') // 当前在 zip 中的目录路径
const isBrowsingZip = ref(false) // 是否正在浏览 zip 文件
const pathBeforeZip = ref('') // 进入 zip 之前的原始路径
const selectedFilePath = ref('') // 当前选中的文件完整路径（用于读取文件）

// ========== 右键菜单状态 ==========
const contextMenuVisible = ref(false) // 是否显示右键菜单
const contextMenuPosition = ref({ x: 0, y: 0 }) // 右键菜单位置
const contextMenuTarget = ref('blank') // 右键菜单目标: 'blank' (空白区域) 或 'file' (文件项)
const selectedContextFile = ref(null) // 右键选中的文件
const selectedFileItem = ref(null) // 当前选中的文件项（用于 F2/Delete 等快捷键）

// ========== 文件名编辑状态 ==========
const editingFilePath = ref('') // 正在编辑的文件路径
const editingFileName = ref('') // 编辑中的文件名
const editingInputRef = ref() // 编辑输入框引用

// ========== 输入对话框状态 ==========
const inputDialogVisible = ref(false) // 是否显示输入对话框
const inputDialogTitle = ref('') // 输入对话框标题
const inputDialogOkText = ref('确定') // 确定按钮文本
const inputDialogCancelText = ref('取消') // 取消按钮文本
const inputDialogPlaceholder = ref('') // 输入框占位符
const inputDialogValue = ref('') // 输入框的值
const inputDialogInputRef = ref() // 输入框引用
const inputDialogCallback = ref(null) // 输入对话框确认回调

// ========== 保存状态管理 ==========
const isSaving = ref(false) // 是否正在保存
const isShortcutSave = ref(false) // 是否是快捷键触发
const saveSuccessMessage = ref('') // 保存成功提示消息
const originalContent = ref('') // 原始文件内容（用于检测变更）

// ========== 草稿自动保存 ==========
const DRAFT_STORAGE_KEY = 'filesystem_draft_content' // localStorage 键

/**
 * 保存草稿到 localStorage
 */
const saveDraft = () => {
  if (fileContent.value && fileContent.value.trim() !== '') {
    try {
      localStorage.setItem(DRAFT_STORAGE_KEY, fileContent.value)
      localStorage.setItem(DRAFT_STORAGE_KEY + '_time', new Date().toISOString())
    } catch (error) {
      console.warn('[saveDraft] 保存草稿失败:', error)
    }
  } else {
    // 内容为空时，清除草稿
    clearDraft()
  }
}

/**
 * 清除草稿
 */
const clearDraft = () => {
  try {
    localStorage.removeItem(DRAFT_STORAGE_KEY)
    localStorage.removeItem(DRAFT_STORAGE_KEY + '_time')
  } catch (error) {
    console.warn('[clearDraft] 清除草稿失败:', error)
  }
}

/**
 * 加载草稿
 */
const loadDraft = () => {
  try {
    const draft = localStorage.getItem(DRAFT_STORAGE_KEY)
    if (draft && draft.trim() !== '') {
      const draftTime = localStorage.getItem(DRAFT_STORAGE_KEY + '_time')
      if (draftTime) {
        const time = new Date(draftTime)
        const now = new Date()
        const hoursDiff = (now - time) / (1000 * 60 * 60)

        // 如果草稿不超过 24 小时，自动恢复
        if (hoursDiff < 24) {
          fileContent.value = draft
          originalContent.value = draft
          Message.info({
            content: `📝 已恢复草稿内容（${time.toLocaleString()}）`,
            duration: 2000,
            position: 'bottom'
          })
        } else {
          // 草稿过期，清除
          clearDraft()
        }
      }
    }
  } catch (error) {
    console.warn('[loadDraft] 加载草稿失败:', error)
  }
}

// 监听内容变化，自动保存草稿
watch(fileContent, () => {
  // 只有在草稿模式下（未选择文件）才自动保存到缓存
  if (!selectedFilePath.value) {
    saveDraft()
  }
}, { debounce: 1000 }) // 防抖，1秒后才保存

// ========== 媒体预览相关状态 ==========

const previewUrl = ref('')
const fileServerURL = ref('http://localhost:18765') // 本地文件服务器URL
const rendered = ref('') // 渲染后的 HTML/Markdown 内容
const imageLoading = ref(false)
const imageWidth = ref(0)
const imageHeight = ref(0)
const isImageView = ref(false) // 是否显示图片预览
const isVideoView = ref(false) // 是否显示视频预览
const isAudioView = ref(false) // 是否显示音频预览
const isPdfFile = ref(false)
const isHtmlFile = ref(false) // HTML 预览
const isMarkdownFile = ref(false) // Markdown 预览
const isBinaryFile = ref(false) // 是否为二进制文件信息展示
// 从 localStorage 读取上次的编辑模式选择，默认为预览模式
const isEditMode = ref(localStorage.getItem(STORAGE_KEYS.FILESYSTEM.EDIT_MODE) === 'true')
const htmlPreviewUrl = ref('') // HTML 预览的 blob URL

// ========== 系统路径功能（FileSystem.vue 特有） ==========

const commonPaths = ref([])
const systemPaths = ref({})

// 加载常用系统路径
const loadCommonPaths = async () => {
  try {
    // 检查 Wails 是否准备好
    if (!window.go || !window.go.main || !window.go.main.App || !window.go.main.App.GetCommonPaths) {
      console.warn('Wails API 未就绪，使用默认路径')
      commonPaths.value = [
        { name: '💿 C盘', path: 'C:\\' },
        { name: '💿 D盘', path: 'D:\\' }
      ]
      return
    }

    const paths = await window.go.main.App.GetCommonPaths()
    systemPaths.value = paths

    const platform = window.navigator.platform
    if (platform.includes('Win')) {
      // 基础路径
      const pathList = [
        { name: '🖥️ 桌面', path: paths.desktop },
        { name: '📁 文档', path: paths.documents },
        { name: '📥 下载', path: paths.downloads },
        { name: '💾 用户目录', path: paths.home }
      ]

      // 动态添加所有盘符（按字母顺序）
      const drives = []
      for (const key in paths) {
        if (key.startsWith('root_')) {
          const driveLetter = key.substring(5)
          drives.push({
            letter: driveLetter,
            path: paths[key]
          })
        }
      }
      drives.sort((a, b) => a.letter.localeCompare(b.letter))

      // 添加盘符到路径列表
      drives.forEach(drive => {
        pathList.push({
          name: `💿 ${drive.letter}盘`,
          path: drive.path
        })
      })

      commonPaths.value = pathList
    } else {
      commonPaths.value = [
        { name: '🖥️ 桌面', path: paths.desktop },
        { name: '📁 文档', path: paths.documents },
        { name: '📥 下载', path: paths.downloads },
        { name: '🏠 主目录', path: paths.home },
        { name: '📂 根目录', path: '/' }
      ]
    }
  } catch (error) {
    // 降级方案：使用默认路径
    commonPaths.value = [
      { name: '💿 C盘', path: 'C:\\' },
      { name: '💿 D盘', path: 'D:\\' }
    ]
  }
}

// ========== 路径历史记录 ==========

const addToHistory = (path) => {
  if (!path || path.trim() === '') return

  const index = pathHistory.value.indexOf(path)
  if (index > -1) {
    pathHistory.value.splice(index, 1)
  }

  pathHistory.value.unshift(path)
  if (pathHistory.value.length > 20) {
    pathHistory.value = pathHistory.value.slice(0, 20)
  }
}

// ========== 导航历史记录（支持后退/前进） ==========

const navHistory = ref([]) // 导航历史栈
const navIndex = ref(-1) // 当前在历史栈中的位置
const isNavigating = ref(false) // 是否正在导航（防止重复记录）

/**
 * 添加路径到导航历史
 * @param {string} path - 要添加的路径
 */
const pushNav = (path) => {
  if (!path || path.trim() === '') return
  if (isNavigating.value) return // 如果正在导航，不重复记录

  // 如果当前位置不在历史栈末尾，删除当前位置之后的所有记录
  if (navIndex.value < navHistory.value.length - 1) {
    navHistory.value = navHistory.value.slice(0, navIndex.value + 1)
  }

  // 如果新路径与当前路径不同，才添加
  const currentPath = navHistory.value[navIndex.value]
  if (currentPath !== path) {
    navHistory.value.push(path)
    navIndex.value = navHistory.value.length - 1
    debugLog('导航历史:', navHistory.value, '当前位置:', navIndex.value)
  }
}

/**
 * 后退到上一个目录
 */
const goBack = async () => {
  if (navIndex.value > 0) {
    isNavigating.value = true
    navIndex.value--
    const path = navHistory.value[navIndex.value]
    debugLog('后退到:', path, '位置:', navIndex.value)
    filePath.value = path
    await listDirectory()
    isNavigating.value = false
  } else {
    Message.info('已经是最早的记录了')
  }
}

/**
 * 前进到下一个目录
 */
const goForward = async () => {
  if (navIndex.value < navHistory.value.length - 1) {
    isNavigating.value = true
    navIndex.value++
    const path = navHistory.value[navIndex.value]
    debugLog('前进到:', path, '位置:', navIndex.value)
    filePath.value = path
    await listDirectory()
    isNavigating.value = false
  } else {
    Message.info('已经是最新的记录了')
  }
}

/**
 * 检查是否可以后退
 */
const canGoBack = computed(() => navIndex.value > 0)

/**
 * 检查是否可以前进
 */
const canGoForward = computed(() => navIndex.value < navHistory.value.length - 1)

// ========== 列出目录（重写以添加历史记录） ==========

const listDirectory = async () => {
  if (!filePath.value) return

  // 如果当前在 ZIP 浏览模式，自动退出
  // 但只在明确请求列出普通目录时才退出，避免误触发
  if (isBrowsingZip.value && filePath.value !== pathBeforeZip.value) {
    debugLog('检测到路径切换，退出 ZIP 模式')
    exitZipMode()
  }

  addToHistory(filePath.value)
  pushNav(filePath.value)
  fileLoading.value = true
  try {
    fileList.value = await listDir(filePath.value)

    // 目录加载完成后，检查原选中的文件是否还在新目录中
    // 如果不在，清空 selectedFileItem，避免视觉闪烁
    if (selectedFileItem.value) {
      const stillExists = fileList.value.some(f => f.path === selectedFileItem.value.path)
      if (!stillExists) {
        selectedFileItem.value = null
      }
    }

    if (selectedFilePath.value) {
      debugLog('[listDirectory] 目录已切换，保留原文件引用:', selectedFilePath.value)
    }
  } catch (error) {
    Message.error('列出目录失败: ' + error.message)
    // 发生错误时也清空选择状态
    selectedFileItem.value = null
  } finally {
    fileLoading.value = false
  }
}

// ========== 路径操作 ==========

const onPathSelect = (value) => {
  if (!value) return
  filePath.value = value
  listDirectory()
}

const onPathEnter = () => {
  if (!filePath.value) return
  listDirectory()
}

const goToPath = (path) => {
  if (!path) return

  // 如果当前在 ZIP 浏览模式，自动退出
  if (isBrowsingZip.value) {
    debugLog('自动退出 ZIP 模式')
    exitZipMode()
  }

  filePath.value = path
  listDirectory()
}

const browseDirectory = () => {
  const path = prompt('请输入目录路径（例如: C:\\Users 或 /home/user）')
  if (path) {
    // 如果当前在 ZIP 浏览模式，自动退出
    if (isBrowsingZip.value) {
      debugLog('自动退出 ZIP 模式')
      exitZipMode()
    }

    filePath.value = path
    listDirectory()
  }
}

// ========== 文件选择（智能判断文件/目录） ==========

const selectFile = (path) => {
  if (!path) return

  const item = fileList.value.find(f => f.path === path)

  // 如果正在浏览 zip 文件
  if (isBrowsingZip.value) {
    if (!item) return

    if (item.is_dir) {
      // 在 zip 中进入子目录
      currentZipDirectory.value = path
      listZipDirectory()
    } else {
      // 读取 zip 中的文件
      selectedFilePath.value = path
      readZipFile(path)
    }
    return
  }

  // 正常文件系统浏览
  // 如果 fileList 为空或找不到该文件，尝试读取
  if (!item) {
    // 无法判断文件类型，默认作为文件读取
    selectedFilePath.value = path
    const parentPath = path.substring(0, path.lastIndexOf('\\')) || path.substring(0, path.lastIndexOf('/'))
    filePath.value = parentPath || path
    readFile()
    return
  }

  if (item.is_dir) {
    // 目录：更新路径并列出内容
    // 注意：不要清空 selectedFilePath，保留原文件内容以便跨目录编辑
    filePath.value = path
    addToHistory(path)
    listDirectory()
  } else {
    // 文件：路径保持为父目录，保存选中文件完整路径
    const parentPath = path.substring(0, path.lastIndexOf('\\')) || path.substring(0, path.lastIndexOf('/'))
    filePath.value = parentPath || path
    selectedFilePath.value = path
    readFile()
  }
}

// ========== 文件读取（重写以支持媒体预览） ==========

const readFile = async () => {
  // ========== 1. 准备阶段 ==========
  const fileToRead = selectedFilePath.value || filePath.value
  if (!fileToRead) return

  // 只添加目录路径到历史记录
  if (filePath.value && filePath.value !== fileToRead) {
    addToHistory(filePath.value)
  }

  const ext = fileToRead.split('.').pop()?.toLowerCase() || ''
  const file = fileList.value.find(f => f.path === fileToRead)

  // ========== 2. 配置常量 ==========
  // 可预览类型：有专门的预览处理函数
  const previewableTypes = [
    ...FILE_EXTENSIONS.IMAGE,
    ...FILE_EXTENSIONS.VIDEO_BROWSER,
    ...FILE_EXTENSIONS.AUDIO,
    'pdf', 'html', 'htm', 'md', 'markdown'
  ]

  // 已知二进制类型：直接显示二进制文件信息
  const knownBinaryTypes = [
    'exe', 'dll', 'so', 'bin',
    'zip', 'rar', '7z', 'tar', 'gz', 'iso', 'img', 'dmg',
    'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'
  ]

  // ========== 3. 快速路径：无扩展名文件 ==========
  if (!ext) {
    if (file && file.size >= FILE_SIZE_THRESHOLDS.LARGE_FILE) {
      // 无扩展名大文件：直接判定为二进制
      debugLog('[readFile] 无扩展名大文件，直接判定为二进制')
      isBinaryFile.value = true
      fileContent.value = getBinaryFileInfo(fileToRead, '', file)
      return
    } else {
      // 无扩展名小文件：快速检测
      debugLog('[readFile] 无扩展名小文件，快速检测:', fileToRead, '大小:', file?.size)
      const isBinary = await quickCheckBinarySample(fileToRead)
      if (isBinary) {
        isBinaryFile.value = true
        fileContent.value = getBinaryFileInfo(fileToRead, '', file)
        return
      }
      // 不是二进制，继续读取完整内容
    }
  }

  // ========== 4. 大文件智能检测 ==========
  if (file && file.size >= FILE_SIZE_THRESHOLDS.LARGE_FILE) {
    if (!previewableTypes.includes(ext)) {
      // 不是可预览类型，需要进行二进制检测
      if (knownBinaryTypes.includes(ext)) {
        // 已知二进制类型：直接判定
        debugLog('[readFile] 已知二进制类型（大文件）:', fileToRead)
        isBinaryFile.value = true
        fileContent.value = getBinaryFileInfo(fileToRead, ext, file)
        return
      } else {
        // 未知类型：快速检测
        debugLog('[readFile] 大文件，快速检测:', fileToRead, '大小:', file.size)
        const isBinary = await quickCheckBinarySample(fileToRead)
        if (isBinary) {
          isBinaryFile.value = true
          fileContent.value = getBinaryFileInfo(fileToRead, ext, file)
          return
        }
        // 不是二进制，继续读取完整内容
      }
    } else {
      debugLog('[readFile] 可预览的大文件类型，跳过二进制检测:', fileToRead)
    }
  }

  // ========== 5. 按处理方式分发 ==========

  // 5.1 可预览类型（使用专门的预览函数）
  if (isImage(fileToRead)) {
    await previewImage(fileToRead)
    return
  }

  if (isVideo(fileToRead)) {
    await previewVideo(fileToRead)
    return
  }

  if (isAudio(fileToRead)) {
    await previewAudio(fileToRead)
    return
  }

  if (isPdf(fileToRead)) {
    await previewPdf(fileToRead)
    return
  }

  if (isHtml(fileToRead)) {
    await previewHtml(fileToRead)
    return
  }

  if (isMarkdown(fileToRead)) {
    await previewMarkdown(fileToRead)
    return
  }

  // 5.2 显示二进制信息的类型
  if (FILE_EXTENSIONS.VIDEO_EXTERNAL.includes(ext)) {
    showBinaryFileInfo(ext, fileToRead)
    return
  }

  if (isExecutable(fileToRead)) {
    showBinaryFileInfo(ext, fileToRead)
    return
  }

  if (isEditableDoc(fileToRead)) {
    // 可编辑的文档文件，继续作为文本读取
  }

  if (isDatabase(fileToRead)) {
    showBinaryFileInfo(ext, fileToRead)
    return
  }

  if (ext === 'lnk') {
    showBinaryFileInfo(ext, fileToRead)
    return
  }

  // 5.3 压缩文件特殊处理
  if (isArchive(fileToRead)) {
    if (ext === 'zip') {
      // zip 文件：进入 zip 浏览模式
      selectedFilePath.value = fileToRead
      await enterZipMode()
      return
    } else {
      // 其他压缩文件：显示信息
      showBinaryFileInfo(ext, fileToRead)
      return
    }
  }

  // 5.4 文本文件（包括 txt 和其他未分类文件）
  await performFileRead()
}

/**
 * 生成二进制文件信息提示（同步函数，极速响应）
 * @param filePath 文件路径
 * @param ext 文件扩展名
 * @param fileInfo 文件信息（从列表中获取）
 * @returns 格式化的提示信息
 */
const getBinaryFileInfo = (filePath, ext, fileInfo) => {
  const fileName = getFileName(filePath)
  const fileSize = fileInfo?.size ? formatBytes(fileInfo.size) : '未知'
  const modifiedTime = fileInfo?.modified_time || '未知'

  const fileTypeDescriptions = {
    'exe': '可执行文件 (EXE)',
    'dll': '动态链接库 (DLL)',
    'so': '共享库 (SO)',
    'dylib': '动态库 (DYLIB)',
    'bin': '二进制文件 (BIN)',
    'dat': '数据文件 (DAT)',
    'db': '数据库文件 (DB)',
    'sqlite': 'SQLite 数据库',
    'zip': '压缩文件 (ZIP)',
    'rar': '压缩文件 (RAR)',
    '7z': '压缩文件 (7Z)',
    'tar': '归档文件 (TAR)',
    'gz': '压缩文件 (GZ)',
    'pdf': 'PDF 文档',
    'doc': 'Word 文档 (DOC)',
    'docx': 'Word 文档 (DOCX)',
    'xls': 'Excel 表格 (XLS)',
    'xlsx': 'Excel 表格 (XLSX)',
    'ppt': 'PowerPoint 演示文稿 (PPT)',
    'pptx': 'PowerPoint 演示文稿 (PPTX)'
  }

  const fileTypeDesc = ext ? (fileTypeDescriptions[ext] || `${ext.toUpperCase()} 文件`) : '二进制文件（无扩展名）'
  const fileSizeBytes = fileInfo?.size ? `(${fileInfo.size.toLocaleString()} 字节)` : ''

  return `================================================================
文件信息：${fileTypeDesc}
================================================================

文件名:    ${fileName}
完整路径:  ${filePath}
文件大小:  ${fileSize} ${fileSizeBytes}
修改时间:  ${modifiedTime}
文件类型:  ${fileTypeDesc}

================================================================
ℹ️  这是二进制文件，不支持文本预览
   如需查看或编辑，请使用专门的工具

💡 提示:
   • 右键菜单 → "使用系统程序打开" 在默认应用中打开
   • 右键菜单 → "在资源管理器中显示" 查看文件位置
================================================================`
}

/**
 * 快速检测文件样本是否为二进制（只读取前100字节）
 * @param filePath 文件路径
 * @returns 是否为二进制文件
 */
const quickCheckBinarySample = async (filePath) => {
  try {
    // 只读取前100字节进行快速检测
    const sample = await readFileApi(filePath)

    // 检查前100个字符
    const checkLength = Math.min(sample.length, 100)
    let binaryCharCount = 0

    for (let i = 0; i < checkLength; i++) {
      const charCode = sample.charCodeAt(i)
      // 空字节或其他控制字符（除了常见的换行符、制表符等）
      if (charCode === 0 || (charCode < 32 && charCode !== 9 && charCode !== 10 && charCode !== 13)) {
        binaryCharCount++
      }
    }

    // 如果二进制字符超过10%，认为是二进制文件（使用更宽松的阈值）
    const binaryRatio = binaryCharCount / checkLength
    const isBinary = binaryRatio > 0.1

    debugLog(`[quickCheckBinarySample] ${filePath}: 二进制字符比例: ${(binaryRatio * 100).toFixed(1)}%, 判定结果: ${isBinary ? '二进制' : '文本'}`)

    return isBinary
  } catch (error) {
    debugWarn('[quickCheckBinarySample] 检测失败:', error)
    // 检测失败时，保守判定为二进制
    return true
  }
}

// ========== 显示二进制文件信息 ==========

/**
 * 计算字符串的显示宽度（中文字符算2个宽度，英文字符算1个宽度）
 * 注意：emoji 和特殊符号按1个字符宽度计算
 */
const getDisplayWidth = (str) => {
  let width = 0
  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i)
    // 中文字符、中文标点算2个宽度，emoji和特殊符号算1个宽度
    if (/[\u4e00-\u9fa5]/.test(str[i])) {
      width += 2
    } else {
      width += 1
    }
  }
  return width
}

/**
 * 根据显示宽度填充字符串
 */
const padByDisplayWidth = (str, targetWidth) => {
  const currentWidth = getDisplayWidth(str)
  const padding = Math.max(0, targetWidth - currentWidth)
  return str + ' '.repeat(padding)
}

const showBinaryFileInfo = (ext, filePathParam) => {
  const file = fileList.value.find(f => f.path === (filePathParam || filePath.value))
  if (!file) return

  // 重置所有文件类型标志
  isImageView.value = false
  isVideoView.value = false
  isAudioView.value = false
  isPdfFile.value = false
  isBinaryFile.value = true

  const extDisplay = ext.toUpperCase()
  const sizeDisplay = formatBytes(file.size)

  // 使用工具函数判断文件类型
  const filePath = filePathParam || filePath.value
  let fileType = '二进制文件'
  if (isImage(filePath)) fileType = '图片文件'
  else if (isVideoAny(filePath)) fileType = '视频文件'
  else if (isAudio(filePath)) fileType = '音频文件'
  else if (['exe', 'dll', 'so'].includes(ext)) fileType = '可执行文件'
  else if (['jar', 'jsa'].includes(ext)) fileType = 'Java归档文件'
  else if (isArchive(filePath)) fileType = '压缩文件'
  else if (FILE_EXTENSIONS.DOCUMENT.includes(getExt(filePath))) fileType = '文档文件'
  else if (ext === 'lnk') fileType = '快捷方式'

  const displayFilePath = filePathParam || filePath.value

  // ========== 通用格式：键值对 + 分隔线 ==========

  fileContent.value = `${'='.repeat(64)}
文件信息：${fileType} (${extDisplay})
${'='.repeat(64)}

文件名:    ${file.name}
完整路径:  ${displayFilePath}
文件大小:  ${sizeDisplay} (${file.size.toLocaleString()} 字节)
修改时间:  ${file.mod_time}
文件类型:  ${fileType} (${extDisplay})

${'='.repeat(64)}
ℹ️  这是二进制文件，不支持文本预览
   如需查看或编辑，请使用专门的工具
${'='.repeat(64)}`

  // 二进制文件信息已加载，静默无提示
}

// ========== Zip 文件浏览功能 ==========

// 进入 zip 浏览模式
const enterZipMode = async () => {
  // 使用选中的 ZIP 文件完整路径
  const zipFilePath = selectedFilePath.value
  if (!zipFilePath) {
    console.error('[enterZipMode] ZIP 文件路径为空')
    return
  }

  debugLog('准备进入 ZIP 模式:', zipFilePath)

  try {
    // 保存进入 zip 之前的原始路径
    pathBeforeZip.value = filePath.value

    // 设置 zip 浏览状态
    currentZipPath.value = zipFilePath
    currentZipDirectory.value = ''
    isBrowsingZip.value = true

    debugLog('ZIP 状态已设置:', {
      currentZipPath: currentZipPath.value,
      pathBeforeZip: pathBeforeZip.value,
      isBrowsingZip: isBrowsingZip.value
    })

    // 列出 zip 根目录内容
    await listZipDirectory()

    // 成功进入 ZIP 模式，静默无提示
  } catch (error) {
    console.error('[enterZipMode] 进入 ZIP 模式失败:', error)
    Message.error('进入 ZIP 模式失败: ' + error.message)
    // 失败时重置状态
    exitZipMode()
  }
}

// 列出 zip 目录内容
const listZipDirectory = async () => {
  if (!currentZipPath.value) {
    console.error('[listZipDirectory] ZIP 路径为空')
    return
  }

  fileLoading.value = true
  try {
    debugLog('开始列出 ZIP 内容:', {
      zipPath: currentZipPath.value,
      currentDir: currentZipDirectory.value
    })

    // 获取所有 zip 内容
    const allFiles = await listZipContents(currentZipPath.value)
    debugLog('获取到文件数量:', allFiles.length)

    if (!allFiles || !Array.isArray(allFiles)) {
      throw new Error('ZIP 内容格式无效')
    }

    // 如果当前在子目录中，过滤出该目录的文件
    let filteredFiles = allFiles
    if (currentZipDirectory.value) {
      debugLog('过滤子目录:', currentZipDirectory.value)

      // 规范化当前目录路径（移除尾部斜杠）
      const normalizedDir = currentZipDirectory.value.replace(/\\/g, '/').replace(/\/+$/, '')

      // 过滤出当前目录的直接子文件和子目录
      filteredFiles = allFiles.filter(f => {
        if (!f.path) return false

        // 规范化路径（统一使用 /）
        const normalizedPath = f.path.replace(/\\/g, '/')

        // 获取文件所在目录
        const fileDir = normalizedPath.substring(0, normalizedPath.lastIndexOf('/'))

        debugLog('检查文件:', normalizedPath, '所在目录:', fileDir, '目标目录:', normalizedDir)

        return fileDir === normalizedDir
      })

      debugLog('过滤后文件数量:', filteredFiles.length)

      // 为子目录中的文件，只显示文件名部分
      filteredFiles = filteredFiles.map(f => {
        const normalizedPath = f.path.replace(/\\/g, '/')
        const name = normalizedPath.substring(normalizedPath.lastIndexOf('/') + 1) || f.name

        return {
          ...f,
          name: name,
          path: f.path // 保持原始路径用于点击
        }
      })
    }

    fileList.value = filteredFiles
    debugLog('最终文件列表:', filteredFiles.length, '项')
  } catch (error) {
    console.error('[listZipDirectory] 列出 ZIP 内容失败:', error)
    const errorMsg = error?.message || error?.error || (typeof error === 'string' ? error : error?.toString?.()) || '未知错误'
    console.error('[listZipDirectory] 错误详情:', errorMsg)
    Message.error('列出 ZIP 内容失败: ' + errorMsg)
  } finally {
    fileLoading.value = false
  }
}

// 读取 zip 中的文件
const readZipFile = async (zipFilePath) => {
  if (!currentZipPath.value || !zipFilePath) {
    console.error('[readZipFile] 参数缺失:', { currentZipPath: currentZipPath.value, zipFilePath })
    return
  }

  addToHistory(`${currentZipPath.value} :: ${zipFilePath}`)

  // 重置所有预览状态
  isImageView.value = false
  isVideoView.value = false
  isAudioView.value = false
  isPdfFile.value = false
  isBinaryFile.value = false

  // 获取文件扩展名
  const ext = zipFilePath.split('.').pop()?.toLowerCase() || ''
  debugLog('文件路径:', zipFilePath)
  debugLog('文件扩展名:', ext)

  fileLoading.value = true
  try {
    // 根据文件类型选择处理方式
    if (isImage(zipFilePath)) {
      console.log('[readZipFile] 检测到图片文件，提取到临时目录')
      // 图片文件：提取到临时目录，然后使用文件路径显示
      const tempFilePath = await extractFileFromZipToTemp(currentZipPath.value, zipFilePath)
      console.log('[readZipFile] 提取成功，临时文件路径:', tempFilePath)

      // 使用临时文件路径直接显示图片（通过文件服务器）
      selectedFilePath.value = tempFilePath
      previewUrl.value = `${fileServerURL.value}/localfs/${normalizeFilePath(tempFilePath, true)}`
      isImageView.value = true
      // 图片加载成功，静默无提示
    } else if (isHtml(zipFilePath) || isMarkdown(zipFilePath)) {
      console.log('[readZipFile] 检测到 HTML/Markdown 文件，处理图片引用')
      let content = await extractFileFromZip(currentZipPath.value, zipFilePath)
      const htmlDir = zipFilePath.substring(0, zipFilePath.lastIndexOf('/')) || ''

      // 查找并替换图片引用
      const imgRegex = /<img([^>]*)(src=["']([^"']+)["'])([^>]*)>/gi
      const images = []
      let match
      while ((match = imgRegex.exec(content)) !== null) {
        const src = match[3]
        if (!src.startsWith('data:') && !src.startsWith('http')) {
          images.push({ src, fullMatch: match[0] })
        }
      }

      console.log('[readZipFile] 找到图片引用:', images.length, '个')

      for (const img of images) {
        try {
          let imgPath = img.src.startsWith('/') ? img.src : (htmlDir ? `${htmlDir}/${img.src}` : img.src)
          imgPath = imgPath.replace(/^\.\//, '').replace(/\/\.\//g, '/').replace(/\/$/, '')

          const tempImgPath = await extractFileFromZipToTemp(currentZipPath.value, imgPath)
          const imgUrl = `${fileServerURL.value}/localfs/${normalizeFilePath(tempImgPath, true)}`
          content = content.replace(img.fullMatch, img.fullMatch.replace(img.src, imgUrl))
        } catch (err) {
          console.error('[readZipFile] 提取图片失败:', img.src, err)
        }
      }

      // 显示内容
      const maxSize = 2 * 1024 * 1024
      if (content.length > maxSize) {
        content = content.substring(0, maxSize) + '\n\n... (文件过大，已截断) ...'
        console.warn(`ZIP文件过大 (${(content.length / 1024).toFixed(2)} KB)`)
      }

      if (isHtml(zipFilePath)) {
        isHtmlFile.value = true
        isEditMode.value = false
        fileContent.value = content
        rendered.value = content
      } else {
        isMarkdownFile.value = true
        fileContent.value = content
      }
    } else {
      console.log('[readZipFile] 不是图片文件，读取文本内容')
      const content = await extractFileFromZip(currentZipPath.value, zipFilePath)
      const maxSize = 2 * 1024 * 1024
      fileContent.value = content.length > maxSize
        ? content.substring(0, maxSize) + '\n\n... (文件过大，已截断) ...'
        : content
    }
  } catch (error) {
    console.error('[readZipFile] 读取失败:', error)
    const errorMsg = error?.message || error?.error || error?.toString() || '未知错误'
    Message.error('读取 ZIP 文件失败: ' + errorMsg)
  } finally {
    fileLoading.value = false
  }
}

// 获取 MIME 类型
const getMimeType = (ext) => {
  const mimeTypes = {
    'png': 'image/png',
    'jpg': 'image/jpeg',
    'jpeg': 'image/jpeg',
    'gif': 'image/gif',
    'svg': 'image/svg+xml',
    'webp': 'image/webp',
    'bmp': 'image/bmp',
    'ico': 'image/x-icon'
  }
  return mimeTypes[ext] || 'application/octet-stream'
}

// 退出 zip 浏览模式
const exitZipMode = () => {
  isBrowsingZip.value = false
  currentZipPath.value = ''
  currentZipDirectory.value = ''

  // 恢复到进入 zip 之前的路径
  if (pathBeforeZip.value) {
    filePath.value = pathBeforeZip.value
    listDirectory()
  }
}

// 获取当前显示的路径（用于界面显示）
const displayPath = computed(() => {
  if (isBrowsingZip.value) {
    if (currentZipDirectory.value) {
      return `📦 ${currentZipPath.value} [${currentZipDirectory.value}]`
    }
    return `📦 ${currentZipPath.value}`
  }
  return filePath.value
})

// 判断当前打开的文件是否在当前目录中（优化性能，减少计算）
const isFileInCurrentDirectory = computed(() => {
  if (!selectedFilePath.value || !filePath.value) return false

  // 提取文件的父目录
  const lastBackslash = selectedFilePath.value.lastIndexOf('\\')
  const lastSlash = selectedFilePath.value.lastIndexOf('/')
  const lastSeparator = Math.max(lastBackslash, lastSlash)

  if (lastSeparator === -1) return false

  const fileDir = selectedFilePath.value.substring(0, lastSeparator)

  // 直接比较路径，避免频繁调用 normalizeFilePath
  // 只在必要时才进行路径标准化
  const fileDirNormalized = fileDir.replace(/\\/g, '/').replace(/\/$/, '')
  const currentPathNormalized = filePath.value.replace(/\\/g, '/').replace(/\/$/, '')

  return fileDirNormalized.toLowerCase() === currentPathNormalized.toLowerCase()
})

// 获取显示的文件路径（用于面板标题显示）
const currentFileName = computed(() => {
  if (isBrowsingZip.value && selectedFilePath.value) {
    // ZIP 模式：从 zip 内路径中提取文件名
    const parts = selectedFilePath.value.split('/')
    return parts[parts.length - 1] || parts[parts.length - 2] || ''
  }
  if (selectedFilePath.value) {
    // 正常模式：如果文件在当前目录，只显示文件名；否则显示完整路径
    // 使用 try-catch 确保任何错误都不会导致整个计算失败
    try {
      if (isFileInCurrentDirectory.value) {
        return getFileName(selectedFilePath.value)
      } else {
        // 文件不在当前目录，显示完整路径以便用户清楚知道
        return selectedFilePath.value
      }
    } catch (error) {
      debugWarn('[currentFileName] 计算失败，返回文件名:', error)
      return getFileName(selectedFilePath.value)
    }
  }
  return ''
})

// 获取显示的文件完整路径（用于tooltip）
const currentFileFullPath = computed(() => {
  return selectedFilePath.value || ''
})

// 媒体预览功能

const previewImage = async (targetPath) => {
  const pathToPreview = targetPath || selectedFilePath.value || filePath.value
  if (!pathToPreview) return

  isImageView.value = true
  isVideoView.value = false
  isAudioView.value = false
  isPdfFile.value = false
  isBinaryFile.value = false

  try {
    previewUrl.value = `${fileServerURL.value}/localfs/${normalizeFilePath(pathToPreview, true)}`
  } catch (error) {
    Message.error('图片加载失败: ' + error.message)
    isImageView.value = false
    imageLoading.value = false
  }
}

const previewMedia = (mediaType, targetPath) => {
  const pathToPreview = targetPath || selectedFilePath.value || filePath.value
  if (!pathToPreview) return

  isImageView.value = false
  isVideoView.value = false
  isAudioView.value = false
  isPdfFile.value = false

  switch (mediaType) {
    case 'video':
      isVideoView.value = true
      break
    case 'audio':
      isAudioView.value = true
      break
    case 'pdf':
      isPdfFile.value = true
      break
  }

  imageLoading.value = false
  previewUrl.value = `${fileServerURL.value}/localfs/${normalizeFilePath(pathToPreview, true)}`
}

const previewVideo = (targetPath) => previewMedia('video', targetPath)
const previewAudio = (targetPath) => previewMedia('audio', targetPath)
const previewPdf = (targetPath) => previewMedia('pdf', targetPath)

// 判断是否为 Office 文档
const isOfficeFile = (fileName) => {
  if (!fileName) return false
  return isEditableDoc(fileName)
}

// 提取 HTML 中的 CSS 样式并内联，同时转换资源路径
const extractHtmlStyles = async (htmlContent, basePath) => {
  if (!htmlContent) return { processedHtml: htmlContent, styles: '' }

  debugLog('开始处理 CSS')
  debugLog('HTML 文件路径:', basePath)

  const sep = basePath.includes('\\') ? '\\' : '/'
  const htmlDir = basePath.substring(0, Math.max(basePath.lastIndexOf('\\'), basePath.lastIndexOf('/'))) || basePath

  // 解析相对路径
  const resolvePath = (base, relPath) => {
    if (relPath.match(/^[a-zA-Z]:\\/)) return relPath
    if (relPath.startsWith('/')) return base + relPath.replace(/\//g, sep)

    // 处理 ../ 和 ./
    const parts = relPath.replace(/\//g, sep).split(sep)
    const resolved = []
    for (const p of parts) {
      if (p === '..') resolved.pop()
      else if (p !== '.') resolved.push(p)
    }
    return base + sep + resolved.join(sep)
  }

  // 文件转 base64
  const fileToBase64 = async (filePath) => {
    try {
      const content = await readFileApi(filePath)
      const ext = getExt(filePath)
      const mimes = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', svg: 'image/svg+xml', webp: 'image/webp', bmp: 'image/bmp', ico: 'image/x-icon' }
      return `data:${mimes[ext] || 'image/png'};base64,${btoa(content)}`
    } catch (err) {
      console.warn('[fileToBase64] 失败:', filePath, err.message)
      return null
    }
  }

  // 转换 CSS 中的 url()
  const convertCssUrls = async (cssContent, cssFilePath) => {
    const cssDir = cssFilePath.substring(0, Math.max(cssFilePath.lastIndexOf('\\'), cssFilePath.lastIndexOf('/')))

    // 移除 @import
    cssContent = cssContent.replace(/@import\s+(?:url\()?["']?([^"')]+)["']?\)?;?/gi, (match, path) => {
      return path.startsWith('http') ? match : `/* @import "${path}" 已移除 */`
    })

    // 转换 url()
    const urls = []
    let m
    const urlRegex = /url\(["']?([^"')]+)["']?\)/gi
    while ((m = urlRegex.exec(cssContent)) !== null) {
      if (!m[1].startsWith('data:') && !m[1].startsWith('http')) {
        urls.push(m[1])
      }
    }

    for (const u of urls) {
      const fullPath = resolvePath(cssDir, u)
      const base64 = await fileToBase64(fullPath)
      if (base64) {
        console.log(`[convertCssUrls] ${u} -> base64`)
        cssContent = cssContent.replace(`url("${u}")`, base64).replace(`url('${u}')`, base64).replace(`url(${u})`, base64)
      }
    }

    return cssContent
  }

  const styles = []
  // 更宽松的link标签匹配：匹配包含rel="stylesheet"或rel='stylesheet'的link标签
  const linkRegex = /<link\s+(?:[^>]*?\s+)?rel\s*=\s*["']stylesheet["'][^>]*>/gi
  const hrefRegex = /href\s*=\s*["']([^"']+)["']/i

  let match
  let linkCount = 0
  while ((match = linkRegex.exec(htmlContent)) !== null) {
    linkCount++
    const linkTag = match[0]
    const hrefMatch = linkTag.match(hrefRegex)

    console.log(`[extractHtmlStyles] 发现第 ${linkCount} 个 link 标签:`, linkTag)

    if (hrefMatch && hrefMatch[1]) {
      let cssPath = resolvePath(htmlDir, hrefMatch[1])
      console.log('[extractHtmlStyles] 解析后 CSS 路径:', cssPath)

      if (hrefMatch[1].startsWith('http://') || hrefMatch[1].startsWith('https://')) {
        console.log('[extractHtmlStyles] 跳过外部 CSS:', hrefMatch[1])
        continue
      }

      try {
        console.log('[extractHtmlStyles] 正在读取 CSS 文件:', cssPath)
        let cssContent = await readFileApi(cssPath)
        // 转换 CSS 中的 url() 为 base64
        cssContent = await convertCssUrls(cssContent, cssPath)
        const cssSize = cssContent.length
        console.log(`[extractHtmlStyles] 成功读取并转换 CSS: ${cssSize} 字符`)
        styles.push(`/* 从 ${cssPath} 提取 */\n${cssContent}`)
        htmlContent = htmlContent.replace(linkTag, '')
      } catch (error) {
        console.warn('[extractHtmlStyles] 无法读取 CSS:', cssPath, error.message)
        // 失败时也移除link标签，避免404错误
        htmlContent = htmlContent.replace(linkTag, `<!-- CSS加载失败: ${cssPath} -->`)
      }
    }
  }

  debugLog(`处理完成: 找到 ${linkCount} 个 link 标签, 成功提取 ${styles.length} 个 CSS 文件`)
  debugLog(`提取的 CSS 总大小: ${styles.join('\n\n').length} 字符`)

  return { processedHtml: htmlContent, styles: styles.join('\n\n'), htmlDir, resolvePath, fileToBase64 }
}

// HTML 预览
const previewHtml = async (targetPath) => {
  const pathToPreview = targetPath || selectedFilePath.value || filePath.value
  if (!pathToPreview) return

  // ========== 检查文件大小 ==========
  const file = fileList.value.find(f => f.path === pathToPreview)
  if (file && file.size) {
    const maxSize = 5 * 1024 * 1024 // 5MB 限制
    if (file.size > maxSize) {
      const fileSize = formatBytes(file.size)
      showBinaryFileInfo('html', pathToPreview)
      return
    }
  }

  // 重置所有状态
  isImageView.value = false
  isVideoView.value = false
  isAudioView.value = false
  isPdfFile.value = false
  isMarkdownFile.value = false
  isHtmlFile.value = true
  isBinaryFile.value = false
  isEditMode.value = false // 默认预览模式

  // 注意：不设置 fileLoading，因为那是给文件列表用的
  // 这里是读取文件内容，不应该影响列表的显示

  try {
    // 读取 HTML 文件内容
    let content = await readFileApi(pathToPreview)

    // 提取并内联 CSS 样式，获取路径解析函数和 base64 转换函数
    const { processedHtml, styles, resolvePath, fileToBase64, htmlDir } = await extractHtmlStyles(content, pathToPreview)

    // 转换 HTML 中的图片路径为 base64
    const imgRegex = /<img([^>]*)(src=["']([^"']+)["'])([^>]*)>/gi
    const images = []
    let match
    while ((match = imgRegex.exec(processedHtml)) !== null) {
      const src = match[3]
      if (!src.startsWith('data:') && !src.startsWith('http://') && !src.startsWith('https://')) {
        images.push({ src, fullMatch: match[0] })
      }
    }

    let html = processedHtml
    // 转换所有图片
    for (const img of images) {
      const fullPath = resolvePath(htmlDir, img.src)
      const base64 = await fileToBase64(fullPath)
      if (base64) {
        console.log(`[previewHtml] ${img.src} -> base64 (${base64.length} 字符)`)
        html = html.replace(img.fullMatch, img.fullMatch.replace(img.src, base64))
      }
    }

    // 移除或注释外部脚本
    html = html.replace(/<script([^>]*)(src=["']([^"']+)["'])([^>]*)>/gi, (match, before, srcAttr, src, after) => {
      if (src.startsWith('http://') || src.startsWith('https://')) {
        return match // 保留外部脚本
      }
      console.log(`[previewHtml] 移除本地脚本: ${src}`)
      return `<!-- 本地脚本已移除: ${src} -->`
    })

    // 🔥 最终清理：移除所有剩余的外部资源引用（防止遗漏）
    // 移除所有剩余的本地CSS链接
    html = html.replace(/<link\s+(?:[^>]*?\s+)?rel\s*=\s*["']stylesheet["'][^>]*>/gi, (match) => {
      const hrefMatch = match.match(/href\s*=\s*["']([^"']+)["']/i)
      if (hrefMatch && hrefMatch[1] && !hrefMatch[1].startsWith('http://') && !hrefMatch[1].startsWith('https://')) {
        console.log(`[previewHtml] 清理遗漏的CSS链接: ${hrefMatch[1]}`)
        return `<!-- CSS链接已移除: ${hrefMatch[1]} -->`
      }
      return match // 保留外部CSS
    })

    // 检查是否是完整的 HTML 文档
    const hasDoctype = html.trim().toLowerCase().startsWith('<!doctype')
    const hasHtmlTag = /<html/i.test(html)
    const hasHeadTag = /<head/i.test(html)
    const hasBodyTag = /<body/i.test(html)

    // 如果已经是完整的 HTML 文档，保持原样，只添加内联样式
    if (hasDoctype || (hasHtmlTag && hasHeadTag && hasBodyTag)) {
      // 在 </head> 之前插入样式
      if (html.includes('</head>')) {
        html = html.replace(
          '</head>',
          `  <style>
    /* 从本地文件提取的样式 */
    ${styles}
  </style>
</head>`
        )
      } else if (html.includes('<head>')) {
        html = html.replace(
          /<head>/i,
          `<head>
  <style>
    /* 从本地文件提取的样式 */
    ${styles}
  </style>`
        )
      }

      // 在 </body> 之前插入链接拦截脚本
      if (html.includes('</body>')) {
        const linkScript = '<script>(function(){document.addEventListener("click",function(e){const t=e.target.closest("a");if(t&&t.href){e.preventDefault();window.parent.postMessage({type:"open-link",url:t.href},"*")}},true)})()<\/script>'
        html = html.replace('</body>', linkScript + '</body>')
      }
    } else {
      // 不是完整文档，构建完整文档
      const linkScript = '<script>(function(){document.addEventListener("click",function(e){const t=e.target.closest("a");if(t&&t.href){e.preventDefault();window.parent.postMessage({type:"open-link",url:t.href},"*")}},true)})()<\/script>'
      html = `<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${getFileName(pathToPreview)}</title>
  <style>
    /* 基础样式 */
    * { box-sizing: border-box; }
    body { margin: 0; padding: 16px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Microsoft YaHei', sans-serif; line-height: 1.6; }
    img { max-width: 100%; height: auto; }
    /* 从本地文件提取的样式 */
    ${styles}
  </style>
</head>
<body>
  ${html}
  ${linkScript}
</body>
</html>`
    }

    // 存储原始内容到 fileContent 用于编辑
    fileContent.value = await readFileApi(pathToPreview)
    // 保存原始内容，用于检测修改
    originalContent.value = fileContent.value
    // 渲染处理后的 HTML 内容
    rendered.value = html
  } catch (error) {
    Message.error('读取 HTML 文件失败: ' + error.message)
    isHtmlFile.value = false
  } finally {
    fileLoading.value = false
  }
}

// Markdown 预览
const previewMarkdown = async (targetPath) => {
  const pathToPreview = targetPath || selectedFilePath.value || filePath.value
  if (!pathToPreview) return

  // ========== 检查文件大小 ==========
  const file = fileList.value.find(f => f.path === pathToPreview)
  if (file && file.size) {
    const maxSize = 5 * 1024 * 1024 // 5MB 限制
    if (file.size > maxSize) {
      const fileSize = formatBytes(file.size)
      showBinaryFileInfo('md', pathToPreview)
      return
    }
  }

  // 重置所有状态
  isImageView.value = false
  isVideoView.value = false
  isAudioView.value = false
  isPdfFile.value = false
  isHtmlFile.value = false
  isMarkdownFile.value = true
  isBinaryFile.value = false
  isEditMode.value = false // 默认预览模式

  // 注意：不设置 fileLoading，因为那是给文件列表用的

  try {
    // 读取 Markdown 文件内容
    const content = await readFileApi(pathToPreview)
    // 存储原始 Markdown 内容到 fileContent 用于编辑
    fileContent.value = content
    // 保存原始内容，用于检测修改
    originalContent.value = content
    // 转换 Markdown 为 HTML
    rendered.value = renderMarkdown(content)
  } catch (error) {
    Message.error('读取 Markdown 文件失败: ' + error.message)
    isMarkdownFile.value = false
  } finally {
    fileLoading.value = false
  }
}

// 使用 marked 库渲染 Markdown
const renderMarkdown = (markdown) => {
  if (!markdown) return ''

  try {
    // 配置 marked 选项
    marked.setOptions({
      breaks: true, // 支持 GFM 换行
      gfm: true, // 启用 GitHub Flavored Markdown
      headerIds: false, // 不生成标题 ID
      mangle: false // 不转义邮箱地址
    })

    // 使用 marked 解析
    const html = marked.parse(markdown)
    return '<div class="markdown-content">' + html + '</div>'
  } catch (error) {
    console.error('[renderMarkdown] 解析失败:', error)
    return '<div class="markdown-content"><p>Markdown 解析失败: ' + error.message + '</p></div>'
  }
}

const onImageLoad = (e) => {
  imageLoading.value = false
  imageWidth.value = e.target.naturalWidth
  imageHeight.value = e.target.naturalHeight
}

const onImageError = () => {
  imageLoading.value = false
  isImageView.value = false
  Message.error('图片加载失败，可能是格式不支持或文件损坏')
}

const currentImageDimensions = computed(() => {
  if (!imageWidth.value || !imageHeight.value) return ''
  return `${imageWidth.value}×${imageHeight.value}`
})

const performFileRead = async () => {
  const fileToRead = selectedFilePath.value || filePath.value
  if (!fileToRead) return

  isImageView.value = false
  isVideoView.value = false
  isAudioView.value = false
  isPdfFile.value = false
  isHtmlFile.value = false // 纯文本文件不是 HTML
  isMarkdownFile.value = false // 纯文本文件不是 Markdown
  isBinaryFile.value = false
  isEditMode.value = true // 纯文本文件只有编辑模式

  // ========== 检查文件大小（避免卡死）==========
  const file = fileList.value.find(f => f.path === fileToRead)
  if (file && file.size) {
    const maxSize = 5 * 1024 * 1024 // 5MB 限制（CodeMirror 渲染性能考虑）
    if (file.size > maxSize) {
      const fileSize = formatBytes(file.size)
      const ext = fileToRead.split('.').pop()?.toLowerCase() || ''

      // 根据文件类型提供针对性的建议
      let suggestion = '• VS Code\n• Sublime Text'

      if (ext === 'sql') {
        suggestion = '• DBeaver（推荐）\n• HeidiSQL\n• Navicat\n• VS Code'
      } else if (ext === 'json') {
        suggestion = '• VS Code（带格式化）\n• 在线 JSON 查看器\n• jq 命令行工具'
      } else if (isCode(filePath.value)) {
        suggestion = '• VS Code（推荐）\n• Sublime Text\n• JetBrains IDE'
      }

      fileContent.value = `╔════════════════════════════════════════════════════════════╗
║  ⚠️  文件过大 - 无法在编辑器中打开                          ║
╠════════════════════════════════════════════════════════════╣
║                                                              ║
║  📄 文件名:    ${file.name.substring(0, 50).padEnd(50)}║
║  📊 文件大小:  ${fileSize.padEnd(20)}                          ║
║  🚫 大小限制:  5 MB                                             ║
║                                                              ║
║  该文件过大，当前编辑器无法流畅打开。                          ║
║  建议使用以下工具查看和编辑:                                   ║
║  ${suggestion.split('\n').join('                                                          ║\n║  ')}                                                          ║
║                                                              ║
║  💡 提示:                                                     ║
║     • 右键菜单 → "使用系统程序打开"                            ║
║     • 或将文件拖拽到专用工具中                                 ║
║                                                              ║
╚════════════════════════════════════════════════════════════╝`
      isBinaryFile.value = true
      isEditMode.value = false
      return
    }
  }

  // 注意：不设置 fileLoading，因为那是给文件列表用的

  try {
    const content = await readFileApi(fileToRead)

    // 文本文件检查大小
    const maxDisplaySize = 5 * 1024 * 1024 // 5MB
    if (content.length > maxDisplaySize) {
      // 超过 5MB 的文本文件
      fileContent.value = content.substring(0, maxDisplaySize) + '\n\n' +
        '... ═════════════════════════════════════════════════════════════\n' +
        '⚠️  文件过大，已截断显示（仅显示前 5MB）\n' +
        '═════════════════════════════════════════════════════════════ ...'
      console.warn(`文件过大 (${(content.length / 1024 / 1024).toFixed(2)} MB)，已截断显示`)
    } else {
      fileContent.value = content
    }

    // 保存原始内容，用于检测修改
    originalContent.value = fileContent.value

    // 移除明显的成功提示，保持界面简洁
  } catch (error) {
    Message.error('读取文件失败: ' + error.message)
  } finally {
    fileLoading.value = false
  }
}

// ========== 安全的文件操作（修复路径定位问题） ==========

/**
 * 保存内容处理（草稿/文件统一处理）
 */
const handleSaveContent = async () => {
  // 检查是否有内容可保存
  if (!fileContent.value || fileContent.value.trim() === '') {
    Message.warning('没有内容可保存')
    return
  }

  const targetPath = selectedFilePath.value

  if (!targetPath) {
    // 草稿模式：手动触发草稿保存
    saveDraft()
    Message.success({
      content: '✓ 内容已保存到缓存（草稿）',
      duration: 1500,
      position: 'bottom'
    })
    return
  }

  // 文件模式：保存到文件
  // 验证路径是否为目录
  if (fileList.value.some(f => f.path === targetPath && f.is_dir)) {
    Message.error(`"${targetPath}" 是目录，不能保存`)
    return
  }

  // 检查内容是否已修改
  if (!isFileModified.value) {
    const fileName = targetPath.split(/[/\\]/).pop()
    Message.info({
      content: `📝 ${fileName} 未修改，无需保存`,
      duration: 1500,
      position: 'bottom'
    })
    return
  }

  // 保存到文件
  const fileName = targetPath.split(/[/\\]/).pop()

  await saveToFile(targetPath, fileName, false)
}

/**
 * 另存为（草稿模式下保存到文件）
 */
const handleSaveAs = async () => {
  // 检查是否有内容可保存
  if (!fileContent.value || fileContent.value.trim() === '') {
    Message.warning('没有内容可保存')
    return
  }

  try {
    // 检查是否支持 runtime API
    if (!window.runtime || !window.runtime.DialogSave) {
      // 如果不支持，回退到手动输入
      showManualSaveDialog(false)
      return
    }

    // 显示文件保存对话框
    const defaultDir = filePath.value || ''
    const defaultFileName = 'untitled.txt'

    const savePath = await window.runtime.DialogSave({
      title: '另存为',
      defaultFilename: defaultFileName,
      defaultDirectory: defaultDir,
      canCreateDirectories: true
    })

    if (!savePath) {
      // 用户取消了保存
      return
    }

    // 验证文件名
    const fileName = savePath.split(/[/\\]/).pop()
    const validation = validateFileName(fileName)
    if (!validation.valid) {
      Message.error(validation.error)
      return
    }

    // 保存文件
    await saveToFile(savePath, fileName, false)

    // 清除草稿（因为已经保存到文件了）
    clearDraft()
  } catch (error) {
    console.error('[handleSaveAs] 保存对话框失败:', error)
    Message.error(`打开保存对话框失败: ${error.message || error}`)
  }
}

/**
 * 保存文件处理 - 支持新建文件和保存到已有文件
 * @param {boolean} [isShortcut=false] - 是否是快捷键触发
 */
const handleWriteFile = async (isShortcut = false) => {
  // 直接调用 handleSaveContent
  await handleSaveContent()
}

/**
 * 手动输入保存路径（当对话框不可用时的回退方案）
 */
const showManualSaveDialog = (isShortcut) => {
  const defaultDir = filePath.value || 'C:\\'

  showInputDialog(
    '💾 保存新文件',
    `请输入文件路径（例如: ${defaultDir}\\filename.txt）`,
    async (inputPath) => {
      // 验证路径
      if (!inputPath || inputPath.trim() === '') {
        Message.error('请输入文件路径')
        return
      }

      // 验证文件名
      const fileName = inputPath.split(/[/\\]/).pop()
      const validation = validateFileName(fileName)
      if (!validation.valid) {
        Message.error(validation.error)
        // 重新显示对话框
        setTimeout(() => {
          inputDialogVisible.value = true
          inputDialogValue.value = inputPath
        }, 100)
        return
      }

      // 保存文件
      await saveToFile(inputPath, fileName, isShortcut)
    },
    '保存'
  )
}

/**
 * 保存内容到指定文件
 */
const saveToFile = async (targetPath, fileName, isShortcut) => {
  // ========== 安全校验 ==========

  // 验证文件名
  const validation = validateFileName(fileName)
  if (!validation.valid) {
    Message.error(validation.error)
    return
  }

  // 验证路径不为空
  if (!targetPath || targetPath.trim() === '') {
    Message.error('保存路径为空')
    return
  }

  // 验证内容不为空
  if (!fileContent.value || fileContent.value.trim() === '') {
    Message.warning('没有内容可保存')
    return
  }

  // 设置保存状态
  isSaving.value = true
  isShortcutSave.value = isShortcut

  try {
    // 保存文件（传递两个独立的参数）
    await writeFileApi(targetPath, fileContent.value)

    // 保存成功提示
    Message.success({
      content: `✓ 文件 "${fileName}" 保存成功`,
      duration: 1500,
      position: 'bottom'
    })

    // 更新状态
    selectedFilePath.value = targetPath
    originalContent.value = fileContent.value

    // 如果保存到当前目录，刷新文件列表
    const fileDir = targetPath.substring(0, Math.max(
      targetPath.lastIndexOf('\\'),
      targetPath.lastIndexOf('/')
    ))
    if (fileDir === filePath.value) {
      await listDirectory()
    }
  } catch (error) {
    Message.error(`保存失败: ${error.message || error}`)
  } finally {
    // 延迟清除保存状态，让用户看到按钮状态变化
    setTimeout(() => {
      isSaving.value = false
    }, isShortcut ? 300 : 500)
  }
}

/**
 * 删除文件处理 - 始终删除选中的文件
 */
const handleDeleteFile = async () => {
  // 必须有 selectedFilePath 才能删除
  const targetPath = selectedFilePath.value

  if (!targetPath) {
    Message.error('未选择文件，无法删除。请先从左侧文件列表中选择一个文件。')
    return
  }

  // 查找目标文件信息
  const targetItem = fileList.value.find(f => f.path === targetPath)
  const isDirectory = targetItem?.is_dir || false
  const fileName = targetItem?.name || targetPath

  // 根据类型显示不同的确认信息
  const confirmMessage = isDirectory
    ? `⚠️ 确定要删除整个目录吗？\n\n${fileName}\n\n此操作将删除目录及其所有内容，不可恢复！`
    : `确定要删除文件吗？\n\n${fileName}\n\n此操作不可恢复！`

  Modal.confirm({
    title: isDirectory ? '⚠️ 危险操作：删除目录' : '确认删除',
    content: confirmMessage,
    okText: '确定删除',
    cancelText: '取消',
    onOk: async () => {
      fileLoading.value = true
      try {
        await deletePathApi(targetPath)
        Message.success('删除成功')

        // 清空编辑器内容
        selectedFilePath.value = ''
        fileContent.value = ''
        isImageView.value = false
        isVideoView.value = false
        isAudioView.value = false
        isPdfFile.value = false
        isHtmlFile.value = false
        isMarkdownFile.value = false
        isBinaryFile.value = false
        previewUrl.value = ''
        rendered.value = ''

        // 刷新文件列表
        if (filePath.value) {
          await listDirectory()
        }
      } catch (error) {
        Message.error('删除失败: ' + error.message)
      } finally {
        fileLoading.value = false
      }
    }
  })
}

// ========== 右键菜单处理 ==========

/**
 * 显示输入对话框
 * @param {string} title - 对话框标题
 * @param {string} placeholder - 输入框占位符
 * @param {function} callback - 确认回调函数，接收输入值作为参数
 * @param {string} okText - 确定按钮文本，默认为"确定"
 */
const showInputDialog = (title, placeholder, callback, okText = '确定') => {
  // 隐藏右键菜单
  hideContextMenu()

  inputDialogTitle.value = title
  inputDialogPlaceholder.value = placeholder
  inputDialogOkText.value = okText
  inputDialogValue.value = ''
  inputDialogCallback.value = callback
  inputDialogVisible.value = true

  // 自动聚焦到输入框
  setTimeout(() => {
    inputDialogInputRef.value?.focus()
  }, 100)
}

/**
 * 处理输入对话框确认
 */
const handleInputDialogConfirm = () => {
  const value = inputDialogValue.value?.trim()

  if (!value) {
    Message.warning('请输入内容')
    return
  }

  // 调用回调函数
  if (inputDialogCallback.value) {
    inputDialogCallback.value(value)
  }

  // 关闭对话框
  inputDialogVisible.value = false
}

/**
 * 处理文件列表区域的右键菜单（统一处理）
 */
const handleFileListContextMenu = (event) => {
  // 隐藏已显示的菜单
  hideContextMenu()

  // 检查是否点击在文件项上
  const fileItemRow = event.target.closest('.file-item-row')

  if (fileItemRow) {
    // 点击在文件项上：显示删除选项
    const filePath = fileItemRow.getAttribute('data-file-path')
    const file = fileList.value.find(f => f.path === filePath)

    if (file) {
      contextMenuTarget.value = 'file'
      selectedContextFile.value = file
    } else {
      contextMenuTarget.value = 'blank'
      selectedContextFile.value = null
    }
  } else {
    // 点击在空白区域：只显示新建选项
    contextMenuTarget.value = 'blank'
    selectedContextFile.value = null
  }

  // 设置菜单位置
  contextMenuPosition.value = {
    x: event.clientX,
    y: event.clientY
  }

  // 显示菜单
  contextMenuVisible.value = true
}

/**
 * 隐藏右键菜单
 */
const hideContextMenu = () => {
  contextMenuVisible.value = false
  contextMenuTarget.value = 'blank'
  selectedContextFile.value = null
}

/**
 * 使用系统默认程序打开右键选中的文件
 */
const handleOpenWithSystem = async () => {
  if (!selectedContextFile.value) {
    return
  }

  const targetPath = selectedContextFile.value.path
  const fileName = selectedContextFile.value.name

  // 隐藏右键菜单
  hideContextMenu()

  try {
    await openPath(targetPath)
    Message.success(`已使用系统默认程序打开: ${fileName}`)
  } catch (error) {
    Message.error('打开文件失败: ' + error.message)
  }
}

/**
 * 重命名右键选中的文件或目录（启动原地编辑模式）
 */
const handleRenameSelectedFile = async () => {
  if (!selectedContextFile.value) {
    return
  }

  const oldPath = selectedContextFile.value.path
  const oldName = selectedContextFile.value.name

  // 隐藏右键菜单
  hideContextMenu()

  // 设置编辑状态
  editingFilePath.value = oldPath
  editingFileName.value = oldName

  // 自动聚焦并选中文件名（不包括扩展名）
  nextTick(() => {
    if (editingInputRef.value && editingInputRef.value.$el) {
      const input = editingInputRef.value.$el.querySelector('input')
      if (input) {
        input.focus()
        // 选中文件名（不包括扩展名）
        const lastDotIndex = oldName.lastIndexOf('.')
        if (lastDotIndex > 0) {
          input.setSelectionRange(0, lastDotIndex)
        } else {
          input.select()
        }
      }
    }
  })
}

/**
 * 点击文件项处理（选中文件）
 * 优化：对于大文件或无扩展名文件，先加载内容再设置选中状态，避免列表闪烁
 */
const handleFileClick = (item) => {
  const ext = item.path.split('.').pop()?.toLowerCase() || ''
  const isLargeBinaryCandidate = !ext || item.size > 1024 * 1024

  if (isLargeBinaryCandidate) {
    // 先不设置选中状态，避免列表重新渲染
    // 等文件加载完成后再设置（通过 nextTick）
    selectFile(item.path)
    nextTick(() => {
      selectedFileItem.value = item
    })
  } else {
    // 普通文件，正常流程
    selectedFileItem.value = item
    selectFile(item.path)
  }
}

/**
 * 双击文件项处理
 */
const handleFileDoubleClick = (item) => {
  // 如果是文件夹，则进入文件夹
  if (item.is_dir) {
    goToPath(item.path)
  } else {
    // 如果是文件，打开查看
    selectFile(item.path)
  }
}

/**
 * 保存编辑的文件名
 */
const saveEditingFileName = async () => {
  if (!editingFilePath.value) {
    return
  }

  const oldPath = editingFilePath.value
  const oldName = fileList.value.find(f => f.path === oldPath)?.name || ''
  const newName = editingFileName.value.trim()

  // 清空编辑状态
  editingFilePath.value = ''
  editingFileName.value = ''

  // 验证
  if (!newName) {
    Message.warning('文件名不能为空')
    return
  }

  // 如果名称没有变化，直接返回
  if (newName === oldName) {
    return
  }

  // 验证文件名
  const invalidChars = /[<>:"/\\|?*]/g
  if (invalidChars.test(newName)) {
    Message.error('文件名包含非法字符：<>:"/\\|?*')
    return
  }

  fileLoading.value = true
  try {
    // 构造新路径
    const dirPath = oldPath.substring(0, oldPath.lastIndexOf(oldPath.includes('\\') ? '\\' : '/'))
    const newPath = dirPath + (dirPath.endsWith('\\') || dirPath.endsWith('/') ? '' : (oldPath.includes('\\') ? '\\' : '/')) + newName

    // 调用重命名 API
    if (!window.go || !window.go.main || !window.go.main.App || !window.go.main.App.RenamePath) {
      throw new Error('Go 后端未就绪，请确保应用已启动')
    }

    await window.go.main.App.RenamePath({
      oldPath: oldPath,
      newPath: newPath
    })

    Message.success('重命名成功')

    // 如果重命名的是当前选中的文件，更新选中路径
    if (selectedFilePath.value === oldPath) {
      selectedFilePath.value = newPath
    }

    // 刷新文件列表
    await listDirectory()
  } catch (error) {
    Message.error(`重命名失败: ${error.message || error}`)
    // 失败时恢复编辑状态
    editingFilePath.value = oldPath
    editingFileName.value = oldName
  } finally {
    fileLoading.value = false
  }
}

/**
 * 取消编辑文件名
 */
const cancelEditingFileName = () => {
  editingFilePath.value = ''
  editingFileName.value = ''
}

/**
 * 删除右键选中的文件
 */
const handleDeleteSelectedFile = async () => {
  if (!selectedContextFile.value) {
    return
  }

  const targetPath = selectedContextFile.value.path
  const fileName = selectedContextFile.value.name
  const isDirectory = selectedContextFile.value.is_dir

  // 根据类型显示不同的确认信息
  const confirmMessage = isDirectory
    ? `⚠️ 确定要删除整个目录吗？\n\n${fileName}\n\n此操作将删除目录及其所有内容，不可恢复！`
    : `确定要删除文件吗？\n\n${fileName}\n\n此操作不可恢复！`

  // 隐藏右键菜单
  hideContextMenu()

  // 使用 Modal.confirm 进行确认
  Modal.confirm({
    title: isDirectory ? '⚠️ 危险操作：删除目录' : '确认删除',
    content: confirmMessage,
    okText: '确定删除',
    cancelText: '取消',
    onOk: async () => {
      fileLoading.value = true
      try {
        await deletePathApi(targetPath)
        Message.success('删除成功')

        // 如果删除的是当前选中的文件，清空编辑器
        if (selectedFilePath.value === targetPath) {
          selectedFilePath.value = ''
          fileContent.value = ''
          isImageView.value = false
          isVideoView.value = false
          isAudioView.value = false
          isPdfFile.value = false
          isHtmlFile.value = false
          isMarkdownFile.value = false
          isBinaryFile.value = false
          previewUrl.value = ''
          rendered.value = ''
        }

        // 刷新文件列表
        await listDirectory()
      } catch (error) {
        Message.error(`删除失败: ${error.message || error}`)
      } finally {
        fileLoading.value = false
      }
    }
  })
}

// ========== 创建文件夹 ==========

/**
 * 创建文件夹处理
 */
const handleCreateDir = () => {
  // 检查当前是否在有效的目录中
  if (!filePath.value) {
    Message.error('请先选择一个目录')
    return
  }

  // 如果正在浏览 ZIP 文件，不允许创建文件夹
  if (isBrowsingZip.value) {
    Message.warning('ZIP 浏览模式下不支持创建文件夹')
    return
  }

  showInputDialog(
    '📁 新建文件夹',
    '请输入文件夹名称',
    async (folderName) => {
      // 验证文件夹名称
      const validation = validateFileName(folderName)
      if (!validation.valid) {
        Message.error(validation.error)
        // 重新显示对话框（因为验证失败）
        setTimeout(() => {
          inputDialogVisible.value = true
          inputDialogValue.value = folderName
        }, 100)
        return
      }

      // 检查是否已存在同名文件夹
      const existingFolder = fileList.value.find(f =>
        f.name === folderName && f.is_dir
      )
      if (existingFolder) {
        Message.error(`文件夹 "${folderName}" 已存在`)
        // 重新显示对话框（因为文件已存在）
        setTimeout(() => {
          inputDialogVisible.value = true
          inputDialogValue.value = folderName
        }, 100)
        return
      }

      // 构建完整路径
      const fullPath = `${filePath.value}\\${folderName}`

      try {
        await createDir(fullPath)
        Message.success({
          content: `✓ 文件夹 "${folderName}" 创建成功`,
          duration: 1500,
          position: 'bottom'
        })

        // 刷新文件列表
        await listDirectory()
      } catch (error) {
        Message.error(`创建文件夹失败: ${error.message || error}`)
      }
    },
    '创建'
  )
}

// ========== 创建文件 ==========

/**
 * 创建文件处理
 */
const handleCreateFile = () => {
  // 检查当前是否在有效的目录中
  if (!filePath.value) {
    Message.error('请先选择一个目录')
    return
  }

  // 如果正在浏览 ZIP 文件，不允许创建文件
  if (isBrowsingZip.value) {
    Message.warning('ZIP 浏览模式下不支持创建文件')
    return
  }

  showInputDialog(
    '📄 新建文件',
    '请输入文件名（如: todo.md）',
    async (fileName) => {
      // 验证文件名
      const validation = validateFileName(fileName)
      if (!validation.valid) {
        Message.error(validation.error)
        // 重新显示对话框（因为验证失败）
        setTimeout(() => {
          inputDialogVisible.value = true
          inputDialogValue.value = fileName
        }, 100)
        return
      }

      // 检查是否已存在同名文件
      const existingFile = fileList.value.find(f =>
        f.name === fileName && !f.is_dir
      )
      if (existingFile) {
        Message.error(`文件 "${fileName}" 已存在`)
        // 重新显示对话框（因为文件已存在）
        setTimeout(() => {
          inputDialogVisible.value = true
          inputDialogValue.value = fileName
        }, 100)
        return
      }

      // 构建完整路径
      const fullPath = `${filePath.value}\\${fileName}`

      try {
        await createFile(fullPath)
        Message.success({
          content: `✓ 文件 "${fileName}" 创建成功`,
          duration: 1500,
          position: 'bottom'
        })

        // 刷新文件列表
        await listDirectory()

        // 自动打开新创建的文件进行编辑
        selectedFilePath.value = fullPath
        fileContent.value = ''
        originalContent.value = ''

        // 根据文件类型设置预览模式
        const ext = fileName.split('.').pop()?.toLowerCase() || ''
        if (ext === 'html' || ext === 'htm') {
          isHtmlFile.value = true
          isMarkdownFile.value = false
          isEditMode.value = true // 创建新文件默认进入编辑模式
          rendered.value = ''
        } else if (ext === 'md' || ext === 'markdown') {
          isMarkdownFile.value = true
          isHtmlFile.value = false
          isEditMode.value = true // 创建新文件默认进入编辑模式
          rendered.value = ''
        } else {
          // 文本文件
          isHtmlFile.value = false
          isMarkdownFile.value = false
        }
      } catch (error) {
        Message.error(`创建文件失败: ${error.message || error}`)
      }
    },
    '创建'
  )
}

// ========== 文件名验证 ==========

/**
 * 验证文件名/文件夹名是否合法
 * @param {string} name - 文件名
 * @returns {Object} { valid: boolean, error: string }
 */
const validateFileName = (name) => {
  if (!name || name.trim() === '') {
    return { valid: false, error: '名称不能为空' }
  }

  // Windows 文件名非法字符: \ / : * ? " < > |
  const illegalChars = /[\\/:*?"<>|]/
  if (illegalChars.test(name)) {
    return { valid: false, error: '文件名不能包含以下字符: \\ / : * ? " < > |' }
  }

  // Windows 保留文件名
  const reservedNames = [
    'CON', 'PRN', 'AUX', 'NUL',
    'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
    'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
  ]
  const nameWithoutExt = name.split('.')[0].toUpperCase()
  if (reservedNames.includes(nameWithoutExt)) {
    return { valid: false, error: `"${name}" 是系统保留文件名，请使用其他名称` }
  }

  // 文件名长度限制（Windows 限制为 260 个字符）
  if (name.length > 255) {
    return { valid: false, error: '文件名过长（最大 255 个字符）' }
  }

  // 文件名不能以空格或点结尾
  if (name.endsWith(' ') || name.endsWith('.')) {
    return { valid: false, error: '文件名不能以空格或点结尾' }
  }

  return { valid: true }
}

// ========== 删除文件（重写以处理历史记录） - 已废弃，使用 handleDeleteFile ==========

const deleteFile = async () => {
  if (!filePath.value) return

  Modal.confirm({
    title: '确认删除',
    content: `确定要删除 ${filePath.value} 吗？此操作不可恢复！`,
    onOk: async () => {
      fileLoading.value = true
      try {
        await deletePathApi(filePath.value)
        Message.success('删除成功')
        filePath.value = ''
        fileContent.value = ''
        fileList.value = []

        if (pathHistory.value.length > 0) {
          filePath.value = pathHistory.value[0]
          listDirectory()
        }
      } catch (error) {
        Message.error('删除失败: ' + error.message)
      } finally {
        fileLoading.value = false
      }
    }
  })
}

// ========== 重置内容 ==========

/**
 * 重置内容到原始文件内容
 */
const resetContent = () => {
  if (originalContent.value !== undefined) {
    fileContent.value = originalContent.value
    Message.success('内容已重置到原始状态')
  } else {
    Message.warning('没有可重置的原始内容')
  }
}

// ========== 打开收藏的文件 ==========

const openFavoriteFile = (path) => {
  const fav = favoriteFiles.value.find(f => f.path === path)

  if (fav && fav.is_dir) {
    // 目录：列出内容
    // 注意：不要清空 selectedFilePath，保留原文件内容以便跨目录编辑
    filePath.value = path
    addToHistory(path)
    listDirectory()
  } else {
    // 文件：设置选中文件路径并读取
    // 提取父目录
    const parentPath = path.substring(0, Math.max(
      path.lastIndexOf('\\'),
      path.lastIndexOf('/')
    ))

    filePath.value = parentPath || path.substring(0, path.lastIndexOf(/[/\\]/))
    selectedFilePath.value = path // 设置选中文件路径

    addToHistory(parentPath || path)
    readFile()
  }
}

// ========== 收藏夹拖拽排序 ==========

// 拖拽状态
const draggingState = ref({
  isDragging: false,
  draggedIndex: -1,
  draggedItem: null,
})

// 长按定时器
const longPressTimer = ref(null)
const LONG_PRESS_DURATION = 500 // 500ms 长按

// 开始长按
const onLongPressStart = (event, index) => {
  // 只响应左键或触摸
  if (event.type === 'mousedown' && event.button !== 0) {
    return
  }

  longPressTimer.value = setTimeout(() => {
    // 长按触发，开始拖拽
    draggingState.value = {
      isDragging: true,
      draggedIndex: index,
      draggedItem: favoriteFiles.value[index],
    }
    Message.info('开始拖拽排序')
  }, LONG_PRESS_DURATION)
}

// 取消长按
const onLongPressCancel = () => {
  if (longPressTimer.value) {
    clearTimeout(longPressTimer.value)
    longPressTimer.value = null
  }
}

// 拖拽开始
const onDragStart = (event, index) => {
  if (!draggingState.value.isDragging) {
    event.preventDefault()
    return
  }

  // 设置拖拽数据
  event.dataTransfer.effectAllowed = 'move'
  event.dataTransfer.setData('text/plain', index.toString())

  // 设置拖拽图像（可选）
  event.dataTransfer.setDragImage(event.target, 0, 0)
}

// 拖拽经过
const onDragOver = (event) => {
  event.preventDefault()
  event.dataTransfer.dropEffect = 'move'
}

// 放置
const onDrop = (event, targetIndex) => {
  event.preventDefault()

  if (!draggingState.value.isDragging) {
    return
  }

  const fromIndex = draggingState.value.draggedIndex

  if (fromIndex === -1 || fromIndex === targetIndex) {
    // 重置拖拽状态
    draggingState.value = {
      isDragging: false,
      draggedIndex: -1,
      draggedItem: null,
    }
    return
  }

  // 执行重排序
  const success = reorderFavorites(fromIndex, targetIndex)

  if (success) {
    Message.success('排序已更新')
  }

  // 重置拖拽状态
  draggingState.value = {
    isDragging: false,
    draggedIndex: -1,
    draggedItem: null,
  }
}

// 拖拽结束
const onDragEnd = () => {
  // 重置拖拽状态
  draggingState.value = {
    isDragging: false,
    draggedIndex: -1,
    draggedItem: null,
  }
}

// ========== 拖拽调整高度 ==========

const startResize = (e) => {
  const startY = e.clientY
  const startHeight = fileContentHeight.value

  const onMouseMove = (moveEvent) => {
    const deltaY = moveEvent.clientY - startY
    const newHeight = startHeight + deltaY
    if (newHeight >= 150 && newHeight <= 800) {
      fileContentHeight.value = newHeight
    }
  }

  const onMouseUp = () => {
    document.removeEventListener('mousemove', onMouseMove)
    document.removeEventListener('mouseup', onMouseUp)
    localStorage.setItem(STORAGE_KEYS.FILESYSTEM.FILE_CONTENT_HEIGHT, fileContentHeight.value.toString())
  }

  document.addEventListener('mousemove', onMouseMove)
  document.addEventListener('mouseup', onMouseUp)
}

// ========== 水平拖拽调整面板宽度 ==========

// ========== 计算属性：按钮显示控制 ==========

// 获取当前文件扩展名（用于代码高亮）
const currentFileExtension = computed(() => {
  const path = selectedFilePath.value || filePath.value
  if (!path) return ''

  // 特殊文件名映射（无扩展名）
  const fileName = path.split(/[/\\]/).pop()?.toLowerCase() || ''
  const specialFiles = {
    'dockerfile': 'dockerfile',
    'containerfile': 'dockerfile',
    'makefile': 'makefile',
    'cmakelists.txt': 'cmake',
    '.gitignore': 'gitignore',
    '.env': 'properties',
  }

  if (specialFiles[fileName]) return specialFiles[fileName]

  // 普通文件：使用 getExt 函数
  return getExt(path)
})

// 判断当前文件是否支持预览模式（HTML 和 Markdown 支持）
const canPreviewFile = computed(() => {
  return isHtmlFile.value || isMarkdownFile.value
})

// 检查是否为可编辑的视图（非预览模式）
const isEditableView = computed(() => {
  return !isImageView.value &&
         !isVideoView.value &&
         !isAudioView.value &&
         !isPdfFile.value &&
         !isBinaryFile.value
})

// 检查文件内容是否已修改（包括新文件）
const isFileModified = computed(() => {
  // 检查是否有内容
  const hasContent = fileContent.value !== '' && fileContent.value.trim() !== ''

  // 情况1：已选择文件，内容已修改
  const hasModified = selectedFilePath.value && fileContent.value !== originalContent.value

  // 情况2：未选择文件，但有新内容（新建文件）
  const isNewFile = !selectedFilePath.value && hasContent

  return isEditableView.value && (hasModified || isNewFile)
})

// 检查内容是否已修改（提取公共逻辑）
const contentChanged = computed(() => {
  return fileContent.value !== '' &&
         fileContent.value !== originalContent.value
})

// 是否可以保存文件
const canSaveFile = computed(() => isEditableView.value && contentChanged.value)

// 是否可以重置内容
const canResetContent = computed(() =>
  isEditableView.value &&
  contentChanged.value &&
  originalContent.value !== undefined
)

// ========== 水平拖拽调整面板宽度 ==========

const startResizeHorizontal = (e) => {
  console.log('[startResizeHorizontal] 开始拖拽')
  const container = e.target.closest('.file-workspace')
  if (!container) {
    console.error('[startResizeHorizontal] 找不到容器')
    return
  }

  const startX = e.clientX
  const containerWidth = container.offsetWidth
  const startLeftWidth = (panelWidth.value.left / 100) * containerWidth

  console.log('[startResizeHorizontal] 初始状态:', { startX, containerWidth, startLeftWidth })

  const onMouseMove = (moveEvent) => {
    const deltaX = moveEvent.clientX - startX
    const newLeftWidth = startLeftWidth + deltaX
    const newLeftPercent = (newLeftWidth / containerWidth) * 100

    if (newLeftPercent >= 20 && newLeftPercent <= 80) {
      panelWidth.value.left = newLeftPercent
      panelWidth.value.right = 100 - newLeftPercent
    }
  }

  const onMouseUp = () => {
    console.log('[startResizeHorizontal] 结束拖拽, 最终宽度:', panelWidth.value)
    document.removeEventListener('mousemove', onMouseMove)
    document.removeEventListener('mouseup', onMouseUp)
    localStorage.setItem(
      STORAGE_KEYS.FILESYSTEM.PANEL_WIDTH,
      JSON.stringify(panelWidth.value)
    )
  }

  document.addEventListener('mousemove', onMouseMove)
  document.addEventListener('mouseup', onMouseUp)
}

// ========== 编辑/预览模式切换 ==========

// 切换编辑模式
const toggleEditMode = () => {
  isEditMode.value = !isEditMode.value
  debugLog('切换编辑模式:', isEditMode.value ? '编辑' : '预览')
}

// 监听编辑模式变化，保存到 localStorage 并重新渲染内容
watch(isEditMode, (newMode) => {
  // 保存到 localStorage
  try {
    localStorage.setItem(STORAGE_KEYS.FILESYSTEM.EDIT_MODE, String(newMode))
  } catch (error) {
    console.error('[FileSystem] 保存编辑模式失败:', error)
  }

  // 当从编辑模式切换到预览模式时，重新渲染内容
  if (!newMode) {
    if (isHtmlFile.value) {
      // HTML: 直接渲染 fileContent
      rendered.value = fileContent.value
    } else if (isMarkdownFile.value) {
      // Markdown: 重新转换
      rendered.value = renderMarkdown(fileContent.value)
    }
  }
})

// ========== 初始化 ==========

// 键盘快捷键处理
const handleKeyDown = (e) => {
  // F5 刷新文件列表
  if (e.key === 'F5') {
    e.preventDefault() // 阻止浏览器默认刷新行为
    if (filePath.value) {
      listDirectory()
    }
  }

  // Ctrl+Shift+C/D/E/F/G/H 快速打开对应盘符
  if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
    const driveLetter = e.key.toUpperCase()
    if (['C', 'D', 'E', 'F', 'G', 'H'].includes(driveLetter)) {
      e.preventDefault() // 阻止浏览器默认行为
      const drivePath = `${driveLetter}:\\`
      filePath.value = drivePath
      listDirectory()
    }
  }

  // Ctrl+B 切换收藏夹侧边栏
  if ((e.ctrlKey || e.metaKey) && e.key === 'b') {
    e.preventDefault() // 阻止浏览器默认行为
    showSidebar.value = !showSidebar.value
  }

  // Ctrl+S 保存
  if ((e.ctrlKey || e.metaKey) && e.key === 's') {
    e.preventDefault() // 阻止浏览器默认保存行为

    // 只有在可以保存的情况下才执行保存（标记为快捷键操作）
    if (canSaveFile.value) {
      handleWriteFile(true) // 传递 true 表示是快捷键触发
    } else {
      Message.warning('当前文件不支持保存')
    }
  }

  // Ctrl+N 新建文件
  if ((e.ctrlKey || e.metaKey) && e.key === 'n' && !e.shiftKey) {
    e.preventDefault() // 阻止浏览器默认新建窗口行为
    handleCreateFile()
  }

  // Ctrl+Shift+N 新建文件夹
  if ((e.ctrlKey || e.metaKey) && e.key === 'n' && e.shiftKey) {
    e.preventDefault() // 阻止浏览器默认行为
    handleCreateDir()
  }

  // Alt+← 后退到上一个目录
  if (e.altKey && e.key === 'ArrowLeft') {
    e.preventDefault() // 阻止浏览器默认行为
    goBack()
  }

  // Alt+→ 前进到下一个目录
  if (e.altKey && e.key === 'ArrowRight') {
    e.preventDefault() // 阻止浏览器默认行为
    goForward()
  }

  // F2 重命名选中的文件或目录
  if (e.key === 'F2') {
    e.preventDefault()
    // 优先使用右键选中的文件，否则使用当前选中的文件项
    const fileToRename = selectedContextFile.value || selectedFileItem.value
    if (fileToRename) {
      selectedContextFile.value = fileToRename // 设置右键选中的文件，以便复用 handleRenameSelectedFile
      handleRenameSelectedFile()
    }
  }

  // Delete 删除选中的文件或目录
  if (e.key === 'Delete') {
    e.preventDefault()
    // 优先使用右键选中的文件，否则使用当前选中的文件项
    const fileToDelete = selectedContextFile.value || selectedFileItem.value
    if (fileToDelete) {
      selectedContextFile.value = fileToDelete // 设置右键选中的文件，以便复用 handleDeleteSelectedFile
      handleDeleteSelectedFile()
    }
  }
}

// 处理iframe中的链接点击
const handleLinkClick = (e) => {
  if (e.data && e.data.type === 'open-link' && e.data.url) {
    debugLog('[handleLinkClick] 收到链接点击:', e.data.url)
    // 在系统默认浏览器中打开链接
    try {
      BrowserOpenURL(e.data.url)
    } catch (error) {
      console.error('[handleLinkClick] 打开链接失败:', error)
      // 降级处理：使用window.open
      window.open(e.data.url, '_blank')
    }
  }
}

onMounted(() => {
  // 获取文件服务器URL
  getFileServerURL().then(url => {
    fileServerURL.value = url
    console.log('[FileSystem] 文件服务器URL:', url)
  }).catch(err => {
    console.warn('[FileSystem] 获取文件服务器URL失败，使用默认值:', err)
  })

  loadCommonPaths()
  // 加载草稿内容
  loadDraft()
  // 恢复上次访问的路径
  if (filePath.value) {
    listDirectory()
  }
  // 添加键盘事件监听
  window.addEventListener('keydown', handleKeyDown)
  // 添加点击事件监听（关闭右键菜单）
  window.addEventListener('click', hideContextMenu)
  // 添加滚动事件监听（关闭右键菜单）
  window.addEventListener('scroll', hideContextMenu, true)
  // 添加消息监听（处理iframe中的链接点击）
  window.addEventListener('message', handleLinkClick)
})

onUnmounted(() => {
  // 移除键盘事件监听
  window.removeEventListener('keydown', handleKeyDown)
  // 移除点击事件监听
  window.removeEventListener('click', hideContextMenu)
  // 移除滚动事件监听
  window.removeEventListener('scroll', hideContextMenu, true)
  // 移除消息监听
  window.removeEventListener('message', handleLinkClick)
})
</script>

<style scoped>
/* ========== 容器布局 ========== */
.file-system-container {
  display: flex;
  flex-direction: column;
  height: 100%;
  background: var(--color-bg-1);
  gap: 0;
}

/* ========== 顶部工具栏 ========== */
.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  background: var(--color-bg-2);
  border-bottom: 1px solid var(--color-border);
  gap: 12px;
  height: 48px;
  flex-shrink: 0;
}

.toolbar-left {
  flex: 1;
  min-width: 0;
}

.path-input-wrapper {
  width: 100%;
}

.path-input {
  width: 100%;
}

.toolbar-right {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

/* ========== 主内容区 ========== */
.main-content {
  display: flex;
  flex: 1;
  overflow: hidden;
  position: relative;
}

/* ========== 侧边栏 ========== */
.sidebar {
  width: 220px;
  background: var(--color-fill-1);
  border-right: 1px solid var(--color-border);
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.sidebar-enter-active, .sidebar-leave-active {
  transition: all 0.3s ease;
}

.sidebar-enter-from, .sidebar-leave-to {
  width: 0;
  opacity: 0;
}

.sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px;
  border-bottom: 1px solid var(--color-border);
  font-size: 13px;
  font-weight: 500;
}

.sidebar-title {
  color: var(--color-text-1);
}

.sidebar-count {
  font-size: 12px;
  color: var(--color-text-3);
  background: var(--color-fill-3);
  padding: 2px 8px;
  border-radius: 10px;
}

.sidebar-content {
  flex: 1;
  overflow-y: auto;
  padding: 8px;
}

.sidebar-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.2s;
  margin-bottom: 2px;
}

.sidebar-item:hover {
  background: var(--color-fill-2);
}

.sidebar-item-icon {
  font-size: 16px;
  flex-shrink: 0;
}

.sidebar-item-name {
  flex: 1;
  font-size: 13px;
  color: var(--color-text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-item-remove {
  opacity: 0;
  transition: opacity 0.2s;
}

.sidebar-item:hover .sidebar-item-remove {
  opacity: 1;
}

/* 拖拽样式 */
.sidebar-item-dragging {
  opacity: 0.5;
  background: var(--color-fill-3);
  cursor: grabbing !important;
  transform: scale(0.98);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}

.sidebar-item-drag-over {
  border: 2px dashed var(--color-primary-light-3);
  background: var(--color-fill-1);
}

/* 防止拖拽时显示删除按钮 */
.sidebar-item-dragging .sidebar-item-remove {
  opacity: 0 !important;
}

.sidebar-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 32px 16px;
  color: var(--color-text-3);
  text-align: center;
  gap: 8px;
}

.sidebar-hint {
  font-size: 12px;
  color: var(--color-text-4);
  margin-top: 4px;
}

/* ========== 工作区 ========== */
.file-workspace {
  display: flex;
  flex: 1;
  overflow: hidden;
  min-width: 0;
}

/* ========== 文件列表面板 ========== */
.file-list-panel {
  display: flex;
  flex-direction: column;
  /* 宽度通过内联样式动态绑定 */
  min-width: 200px;
  overflow: hidden;
}

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  background: var(--color-fill-1);
  border-bottom: 1px solid var(--color-border);
  font-size: 13px;
  font-weight: 500;
  flex-shrink: 0;
}

.panel-title {
  color: var(--color-text-1);
}

.panel-count, .panel-filename {
  font-size: 12px;
  color: var(--color-text-3);
}

.panel-warning {
  font-size: 12px;
  color: #f53f3f;
  background: var(--color-danger-light-1);
  padding: 2px 8px;
  border-radius: 4px;
  font-weight: 500;
}

.panel-filename {
  font-weight: normal;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 500px;
  display: inline-block;
  vertical-align: middle;
}

.panel-filename.file-outside-dir {
  color: rgb(var(--warning-6));
  font-weight: 500;
}

.file-location-hint {
  font-size: 11px;
  color: var(--color-text-3);
  font-weight: normal;
  white-space: nowrap;
  display: inline;
}

.file-list-wrapper {
  flex: 1;
  overflow-y: auto;
}

/* 紧凑列表样式 */
.compact-list :deep(.arco-list) {
  padding: 0;
}

.file-item-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  cursor: pointer;
  transition: background 0.15s;
  border-bottom: 1px solid var(--color-border-2);
}

.file-item-row:hover {
  background: var(--color-fill-2);
}

.file-item-selected {
  background: var(--color-fill-3) !important;
  font-weight: 500;
}

.file-item-row:last-child {
  border-bottom: none;
}

.file-item-icon {
  font-size: 16px;
  flex-shrink: 0;
  width: 20px;
  text-align: center;
}

.file-item-name {
  flex: 1;
  font-size: 13px;
  color: var(--color-text-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.file-name-edit-input {
  flex: 1;
  min-width: 0;
}

.file-name-edit-input :deep(.arco-input) {
  font-size: 13px;
  padding: 0 8px;
  height: 24px;
  line-height: 24px;
}

.file-item-size {
  font-size: 11px;
  color: var(--color-text-3);
  flex-shrink: 0;
}

.file-item-fav {
  flex-shrink: 0;
  opacity: 0.6;
  transition: opacity 0.2s;
}

.file-item-row:hover .file-item-fav {
  opacity: 1;
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  color: var(--color-text-3);
  gap: 8px;
}

/* ========== 分隔条 ========== */
.resizer {
  width: 12px;
  background: transparent;
  cursor: col-resize;
  flex-shrink: 0;
  position: relative;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 -6px;
  /* 关键：确保所有鼠标事件都能被捕获 */
  pointer-events: auto;
}

.resizer::before {
  content: '';
  position: absolute;
  width: 4px;
  height: 100%;
  background: var(--color-border-2);
  border-left: 1px solid var(--color-border-2);
  border-right: 1px solid var(--color-border-2);
  transition: all 0.2s;
  pointer-events: none; /* 让伪元素不阻止鼠标事件 */
}

.resizer:hover::before {
  background: var(--color-fill-2);
  border-color: var(--color-primary-light-4);
}

.resizer::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 2px;
  height: 40px;
  background: var(--color-border-3);
  border-radius: 1px;
  opacity: 0;
  transition: opacity 0.2s;
  pointer-events: none;
}

.resizer:hover::after {
  opacity: 1;
}

/* ========== 文件编辑器面板 ========== */
.file-editor-panel {
  display: flex;
  flex-direction: column;
  /* 宽度通过内联样式动态绑定 */
  min-width: 200px;
  overflow: hidden;
}

.editor-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 4px;
  gap: 4px;
}

/* ========== HTML 预览 ========== */
.html-preview-wrapper {
  flex: 1;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  position: relative;
}

.preview-mode-switch {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 10;
  display: flex;
  gap: 4px;
  background: var(--color-bg-2);
  border-radius: 4px;
  padding: 4px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  opacity: 0.3;
  transition: opacity 0.2s;
}

.html-preview-wrapper:hover .preview-mode-switch,
.markdown-preview-wrapper:hover .preview-mode-switch,
.text-editor-wrapper:hover .preview-mode-switch {
  opacity: 1;
}

.html-preview-content {
  flex: 1;
  width: 100%;
  height: 100%;
  border: 1px solid var(--color-border-2);
  border-radius: 4px;
  background: #fff;
}

.html-edit-wrapper {
  flex: 1;
  display: flex;
  flex-direction: column;
  position: relative;
}

.code-editor {
  flex: 1;
  height: 100%;
}

/* ========== Markdown 预览 ========== */
.markdown-preview-wrapper {
  flex: 1;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  position: relative;
}

.markdown-preview-content {
  flex: 1;
  overflow: auto;
  background: var(--color-bg-2);
  border: 1px solid var(--color-border-2);
  border-radius: 4px;
  padding: 8px;
  line-height: 1.6;
}

.markdown-edit-wrapper {
  flex: 1;
  display: flex;
  flex-direction: column;
  position: relative;
}

/* Markdown 内容基础样式 */
.markdown-content {
  color: var(--color-text-1);
  line-height: 1.6;
}

.markdown-content :deep(h1),
.markdown-content :deep(h2),
.markdown-content :deep(h3),
.markdown-content :deep(h4),
.markdown-content :deep(h5),
.markdown-content :deep(h6) {
  margin-top: 24px;
  margin-bottom: 16px;
  font-weight: 600;
  line-height: 1.25;
}

.markdown-content :deep(h1) {
  font-size: 2em;
  border-bottom: 1px solid var(--color-border-2);
  padding-bottom: 8px;
}

.markdown-content :deep(h2) {
  font-size: 1.5em;
  border-bottom: 1px solid var(--color-border-2);
  padding-bottom: 6px;
}

.markdown-content :deep(p) {
  margin-bottom: 16px;
}

.markdown-content :deep(code) {
  background: var(--color-fill-3);
  padding: 2px 6px;
  border-radius: 3px;
  font-family: 'Consolas', 'Monaco', monospace;
  font-size: 0.9em;
}

.markdown-content :deep(pre) {
  background: var(--color-fill-3);
  padding: 12px;
  border-radius: 4px;
  overflow-x: auto;
  margin-bottom: 16px;
}

.markdown-content :deep(pre code) {
  background: none;
  padding: 0;
}

.markdown-content :deep(blockquote) {
  border-left: 4px solid var(--color-border-3);
  padding-left: 16px;
  margin: 16px 0;
  color: var(--color-text-3);
}

.markdown-content :deep(ul),
.markdown-content :deep(ol) {
  margin-bottom: 16px;
  padding-left: 24px;
}

.markdown-content :deep(li) {
  margin-bottom: 4px;
}

.markdown-content :deep(a) {
  color: var(--color-primary-6);
  text-decoration: none;
}

.markdown-content :deep(a:hover) {
  text-decoration: underline;
}

.markdown-content :deep(img) {
  max-width: 100%;
  height: auto;
}

.markdown-content :deep(table) {
  border-collapse: collapse;
  width: 100%;
  margin-bottom: 16px;
}

.markdown-content :deep(th),
.markdown-content :deep(td) {
  border: 1px solid var(--color-border-2);
  padding: 8px 12px;
}

.markdown-content :deep(th) {
  background: var(--color-fill-2);
  font-weight: 600;
}

.markdown-content :deep(hr) {
  border: none;
  border-top: 1px solid var(--color-border-2);
  margin: 24px 0;
}

.markdown-content :deep(strong) {
  font-weight: 600;
}

.markdown-content :deep(em) {
  font-style: italic;
}

.markdown-content :deep(del) {
  text-decoration: line-through;
  color: var(--color-text-3);
}

/* 移除列表项的默认样式，使用自定义 */
.markdown-content :deep(ul) {
  list-style-type: disc;
}

.markdown-content :deep(ol) {
  list-style-type: decimal;
}

.markdown-content :deep(ul ul) {
  list-style-type: circle;
}

.markdown-content :deep(ul ul ul) {
  list-style-type: square;
}

/* ========== 媒体预览 ========== */
.media-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 1;
  background: var(--color-fill-1);
  border-radius: 8px;
  overflow: hidden;
  position: relative;
}

/* PDF 预览从顶部开始，不居中 */
.media-preview-pdf {
  justify-content: flex-start;
}

.preview-image {
  max-width: 100%;
  max-height: 600px;
  object-fit: contain;
}

.preview-video {
  max-width: 100%;
  max-height: 500px;
}

.preview-audio {
  width: 100%;
  margin-top: 20px;
}

.preview-pdf {
  width: 100%;
  height: 100%;
  min-height: 500px;
  border: none;
  border-radius: 4px;
}

.media-loading {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

.media-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  /*padding: 8px;*/
  font-size: 12px;
  color: var(--color-text-3);
  width: 100%;
  justify-content: center;
}

.media-meta .file-name {
  font-weight: 500;
  color: var(--color-text-2);
}

.media-meta .image-dimensions {
  padding: 2px 8px;
  background: var(--color-fill-2);
  border-radius: 4px;
  font-family: monospace;
}

/* ========== 文本编辑器 ========== */
.text-editor-wrapper {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 200px;
  position: relative;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  overflow: hidden;
}

.code-editor {
  flex: 1;
  height: 100%;
  border: none;
}

.resize-handle-v {
  height: 6px;
  background: var(--color-fill-2);
  cursor: ns-resize;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.2s;
  flex-shrink: 0;
}

.resize-handle-v:hover {
  background: var(--color-fill-3);
}

.resize-dots {
  width: 40px;
  height: 3px;
  background: repeating-linear-gradient(
    90deg,
    var(--color-border-3),
    var(--color-border-3) 3px,
    transparent 3px,
    transparent 6px
  );
  border-radius: 2px;
}

/* ========== 编辑器工具栏 ========== */
.editor-toolbar {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

/* ========== 滚动条优化 ========== */
:deep(.arco-scrollbar-track) {
  background: transparent;
}

:deep(.arco-scrollbar-bar) {
  background: var(--color-fill-3);
  border-radius: 3px;
}

:deep(.arco-scrollbar-bar:hover) {
  background: var(--color-fill-4);
}

/* ========== ZIP 面包屑导航 ========== */
.zip-breadcrumb {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 4px 0;
}

.zip-path-text {
  flex: 1;
  font-size: 13px;
  color: var(--color-text-2);
  background: var(--color-fill-2);
  padding: 6px 12px;
  border-radius: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: 'Consolas', 'Monaco', monospace;
}

/* ========== 右键菜单 ========== */
.context-menu {
  position: fixed;
  background: var(--color-bg-2);
  border: 1px solid var(--color-border-2);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  padding: 4px 0;
  min-width: 200px;
  z-index: 9999;
  font-size: 13px;
}

.context-menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  cursor: pointer;
  transition: background-color 0.15s;
  user-select: none;
  color: var(--color-text-1);
}

.context-menu-item:hover {
  background: var(--color-fill-2);
}

.context-menu-item.danger {
  color: rgb(var(--danger-6));
}

.context-menu-item.danger:hover {
  background: rgb(var(--danger-1));
}

.context-menu-icon {
  font-size: 16px;
  width: 20px;
  text-align: center;
}

.context-menu-shortcut {
  margin-left: auto;
  font-size: 11px;
  color: var(--color-text-3);
  background: var(--color-fill-2);
  padding: 2px 6px;
  border-radius: 3px;
}

.context-menu-divider {
  height: 1px;
  background: var(--color-border-2);
  margin: 4px 0;
}
</style>
