<template>
  <!--
    文件浏览器主容器(Batch 10)。
    布局:顶部工具栏(路径面包屑 + 刷新 + 多工程切换) + 左侧文件树 + 右侧文件预览。
    单工程不显工程选择器,多工程显示下拉切换。
  -->
  <div class="file-explorer">
    <!-- 顶部工具栏 -->
    <div class="explorer-toolbar">
      <div class="toolbar-left">
        <!-- 多工程下拉(单工程隐藏) -->
        <div v-if="modules.length > 1" class="module-dropdown">
          <button class="module-select" @click="moduleDropdownOpen = !moduleDropdownOpen">
            <span class="module-select-name">{{ currentModule?.name ?? '...' }}</span>
            <span class="module-select-arrow" :class="{ open: moduleDropdownOpen }">▾</span>
          </button>
          <div v-if="moduleDropdownOpen" class="module-dropdown-menu" @click.stop>
            <div
              v-for="m in modules"
              :key="m.id"
              class="module-dropdown-item"
              :class="{ active: m.id === currentModuleId }"
              @click="onModuleSelect(m.id)"
            >
              <span class="module-dropdown-name">{{ m.name }}</span>
              <span v-if="m.path" class="module-dropdown-path" :title="m.path">{{ m.path }}</span>
            </div>
            <div class="module-dropdown-divider"></div>
            <div class="module-dropdown-item module-dropdown-action" @click="onScanSubmodules">
              <span class="module-dropdown-name">🔍 {{ $t('fileExplorer.scanSubmodules') }}</span>
            </div>
          </div>
        </div>

        <!-- 路径面包屑 -->
        <nav class="breadcrumb">
          <span class="crumb crumb-root" :title="currentModule?.path ?? ''" @click="goRoot">
            {{ currentModule?.name ?? '...' }}
          </span>
          <template v-for="(seg, idx) in breadcrumbSegments" :key="idx">
            <span class="crumb-sep">/</span>
            <span class="crumb" @click="goBreadcrumb(seg.path)">{{ seg.name }}</span>
          </template>
        </nav>
      </div>

      <div class="toolbar-right">
        <button class="btn btn-ghost btn-sm" type="button" title="编辑当前工程" @click="openEditModule">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
        </button>
        <button class="btn btn-ghost btn-sm" type="button" title="删除当前工程" @click="confirmRemoveModule">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
        </button>
        <button class="btn btn-ghost btn-sm" type="button" title="添加工程" @click="openAddModule">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
        </button>
        <button class="btn btn-ghost btn-sm" type="button" :disabled="refreshing" @click="refresh">
          <span v-if="refreshing" class="spinner"></span>
          {{ refreshing ? $t('fileExplorer.refreshing') : $t('fileExplorer.refresh') }}
        </button>
        <button class="btn btn-ghost btn-sm" type="button" title="弹出到独立窗口" @click="onDetach">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
            <path d="M15 3h6v6"/>
            <path d="M10 14L21 3"/>
            <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
          </svg>
        </button>
      </div>
    </div>

    <!-- 主体:文件树/变更列表 + 预览 -->
    <div class="explorer-body">
      <!-- 左:视图切换按钮(文件树 / 变更) -->
      <div class="explorer-sidebar">
        <div class="explorer-view-tabs">
          <button class="view-tab" :class="{ active: viewMode === 'tree' }" @click="viewMode = 'tree'">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
          </button>
          <button class="view-tab" :class="{ active: viewMode === 'changes' }" @click="switchToChanges">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
            <span v-if="changedCount > 0" class="changed-badge">{{ changedCount }}</span>
          </button>
        </div>

        <!-- 文件树视图 -->
        <div v-show="viewMode === 'tree'" class="explorer-tree">
          <div v-if="!currentModuleId" class="tree-loading">
            <span class="spinner"></span>
          </div>
          <FileTree
            v-else
            :module-id="currentModuleId"
            sub-path=""
            :indent="12"
            :expanded-paths="expandedPaths"
            :loaded-children="loadedChildren"
            :selected-path="selectedFilePath"
            @toggle-dir="onToggleDir"
            @file-select="onFileSelect"
            @load-children="onLoadChildren"
          />
        </div>

        <!-- Git 变更视图 -->
        <div v-show="viewMode === 'changes'" class="explorer-changes">
          <GitChanges
            v-if="currentModuleId"
            :module-id="currentModuleId"
            @select-file="onChangeFileSelect"
          />
        </div>
      </div>

      <!-- 右:文件预览(moduleId 为空时不渲染,避免空 moduleId IPC 调用报错) -->
      <div class="explorer-preview">
        <FilePreview
          v-if="currentModuleId"
          :module-id="currentModuleId"
          :file-path="selectedFilePath"
          :module-root-path="currentModule?.path ?? ''"
          :git-status="selectedFileGitStatus"
        />
        <div v-else class="preview-placeholder-inline">{{ $t('fileExplorer.selectFileHint') }}</div>
      </div>
    </div>
  </div>

  <!-- 新增/编辑工程弹窗 -->
  <div v-if="showModuleModal" class="modal-overlay" @click.self="showModuleModal = false">
    <div class="modal-box modal-box--sm">
      <h3 class="modal-title">{{ editingModule ? $t('fileExplorer.editModule') : $t('fileExplorer.addModule') }}</h3>
      <div class="modal-field">
        <label>{{ $t('fileExplorer.moduleName') }}</label>
        <input v-model="moduleFormName" :placeholder="$t('fileExplorer.moduleNamePlaceholder')" />
      </div>
      <div class="modal-field">
        <label>{{ $t('fileExplorer.modulePath') }}</label>
        <input v-model="moduleFormPath" :placeholder="$t('fileExplorer.modulePathPlaceholder')" />
      </div>
      <div class="modal-field">
        <label>{{ $t('fileExplorer.moduleGitUrl') }} <span class="opt-label">{{ $t('common.optional') }}</span></label>
        <input v-model="moduleFormGitUrl" :placeholder="$t('fileExplorer.moduleGitUrlPlaceholder')" />
      </div>
      <div class="modal-actions">
        <button class="btn btn-ghost" @click="showModuleModal = false">{{ $t('common.cancel') }}</button>
        <button class="btn btn-primary" :disabled="savingModule || !moduleFormName.trim() || !moduleFormPath.trim()" @click="onSaveModule">
          {{ savingModule ? $t('common.loading') : (editingModule ? $t('common.save') : $t('fileExplorer.addModule')) }}
        </button>
      </div>
    </div>
  </div>

  <!-- 删除工程确认弹窗 -->
  <div v-if="showRemoveConfirm" class="modal-overlay" @click.self="showRemoveConfirm = false">
    <div class="modal-box modal-box--sm">
      <h3 class="modal-title">{{ $t('fileExplorer.removeModule') }}</h3>
      <p style="margin: 12px 0; color: var(--df-text-secondary);">
        {{ $t('fileExplorer.removeConfirm', { name: currentModule?.name ?? '' }) }}
      </p>
      <div class="modal-actions">
        <button class="btn btn-ghost" @click="showRemoveConfirm = false">{{ $t('common.cancel') }}</button>
        <button class="btn btn-danger" :disabled="removingModule" @click="onRemoveModule">
          {{ removingModule ? $t('common.loading') : $t('common.delete') }}
        </button>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
/**
 * FileExplorer — 文件浏览器主容器。
 *
 * 状态持有(子组件 FileTree 是递归的,状态集中在此避免分散):
 * - expandedPaths:Set<string>,展开的目录相对路径集合
 * - loadedChildren:Map<path, entries>,目录条目缓存(展开秒开,避免重复请求)
 * - selectedFilePath / selectedFileGitStatus:当前选中文件(传给 FilePreview)
 *
 * 父组件(ProjectDetail)传入 projectId,本组件按 id 拉工程列表(moduleApi.listProjectModules),
 * 单工程自动选中;多工程默认选首个 + 提供下拉切换。
 */
import { ref, computed, watch, reactive, onMounted, onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import { moduleApi, type ProjectModuleRecord, type FileTreeEntry, type GitStatusResult } from '@/api/module'
import FileTree from './FileTree.vue'
import FilePreview from './FilePreview.vue'
import GitChanges from './GitChanges.vue'
import { detachFileExplorer } from '@/composables/project/useFileExplorerWindow'

const props = defineProps<{
  projectId: string
}>()

const { t } = useI18n()

const modules = ref<ProjectModuleRecord[]>([])
const currentModuleId = ref<string>('')
const refreshing = ref(false)
const moduleDropdownOpen = ref(false)

// 工程 CRUD 弹窗
const showModuleModal = ref(false)
const editingModule = ref<ProjectModuleRecord | null>(null)
const savingModule = ref(false)
const moduleFormName = ref('')
const moduleFormPath = ref('')
const moduleFormGitUrl = ref('')

// 删除确认
const showRemoveConfirm = ref(false)
const removingModule = ref(false)

// 树状态(顶层持有,递归子树共享 props)。
const expandedPaths = reactive(new Set<string>())
const loadedChildren = reactive(new Map<string, FileTreeEntry[]>())
const selectedFilePath = ref<string | null>(null)
const selectedFileGitStatus = ref<string | undefined>(undefined)

const currentModule = computed(
  () => modules.value.find((m) => m.id === currentModuleId.value) ?? null,
)

/** 面包屑分段(由当前选中文件路径或最后展开目录派生)。 */
const breadcrumbSegments = computed(() => {
  // 面包屑跟随选中文件所在目录(更贴合"我在看哪里");未选文件时显示根。
  const ref = selectedFilePath.value ?? ''
  if (!ref) return []
  const parts = ref.split('/').filter(Boolean)
  const segs: { name: string; path: string }[] = []
  let acc = ''
  for (const p of parts) {
    acc = acc ? `${acc}/${p}` : p
    segs.push({ name: p, path: acc })
  }
  return segs
})

/** 拉工程列表并初始化选中(记忆上次选中)。 */
async function loadModules() {
  try {
    const list = await moduleApi.listProjectModules(props.projectId)
    modules.value = list
    if (list.length > 0) {
      const lastSelected = localStorage.getItem('df-module-' + props.projectId)
      const target = list.find(m => m.id === lastSelected)
      if (target) {
        currentModuleId.value = target.id
      } else if (!list.some((m) => m.id === currentModuleId.value)) {
        currentModuleId.value = list[0].id
      }
    }
    resetTreeState()
  } catch (e) {
    console.error('[FileExplorer] 加载工程列表失败', e)
    modules.value = []
  }
}

/** 重置树状态(切工程时调用,清展开/缓存/选中文件;刷新时保留选中文件由 refresh 单独处理)。 */
function resetTreeState() {
  expandedPaths.clear()
  loadedChildren.clear()
  selectedFilePath.value = null
  selectedFileGitStatus.value = undefined
}

/** 自定义下拉选择工程。 */
function onModuleSelect(id: string) {
  currentModuleId.value = id
  localStorage.setItem('df-module-' + props.projectId, id)
  moduleDropdownOpen.value = false
  resetTreeState()
}

/** 点击文件:记录选中 + git 状态(从已加载条目查)。 */
function onFileSelect(path: string) {
  selectedFilePath.value = path
  // 从 loadedChildren 反查 git_status(任意层目录的条目都可能含此文件)。
  selectedFileGitStatus.value = findEntryGitStatus(path)
}

/** 递归在 loadedChildren 缓存中查指定路径条目的 git_status。 */
function findEntryGitStatus(path: string): string | undefined {
  for (const entries of loadedChildren.values()) {
    const hit = entries.find((e) => e.path === path)
    if (hit) return hit.git_status
  }
  return undefined
}

/** 文件夹展开/收起(子组件 emit;同步顶层 expandedPaths)。 */
function onToggleDir(path: string, _entries: FileTreeEntry[]) {
  if (expandedPaths.has(path)) {
    expandedPaths.delete(path)
  } else {
    expandedPaths.add(path)
  }
}

/** 子目录懒加载触发(子组件 emit;顶层仅记录已请求,实际拉取由 FileTree 自身完成)。 */
function onLoadChildren(_path: string) {
  // 占位:FileTree 内部已自拉并缓存到 loadedChildren;此处保留事件入口便于未来扩展(如统一节流)。
}

/** 刷新根目录(清展开/缓存,保留已打开的文件预览不变)。 */
async function refresh() {
  refreshing.value = true
  // 仅清树状态(展开目录 + 缓存条目),不清 selectedFilePath/selectedFileGitStatus,
  // 让用户刷新的同时仍能看到正在查看的文件内容。
  expandedPaths.clear()
  loadedChildren.clear()
  // 等一个 tick 让 FileTree watch 触发重拉;实际拉取在子组件,这里只做状态清空。
  await new Promise((r) => setTimeout(r, 50))
  refreshing.value = false
}

/** 面包屑 → 根(仅清展开状态,保留当前打开的预览文件)。 */
function goRoot() {
  expandedPaths.clear()
}

/** 面包屑 → 某段(仅展开父链使该目录可见,不改变当前预览文件)。 */
function goBreadcrumb(path: string) {
  // 确保父链展开(否则面包屑跳转后看不到该目录)。
  const parts = path.split('/').filter(Boolean)
  let acc = ''
  for (let i = 0; i < parts.length - 1; i++) {
    acc = acc ? `${acc}/${parts[i]}` : parts[i]
    expandedPaths.add(acc)
  }
}

/** 弹出文件浏览器到独立窗口。 */
function onDetach() {
  detachFileExplorer(props.projectId, currentModule.value?.name || t('fileExplorer.detachTitle'))
}

// ── 视图模式切换:文件树 / Git 变更 ──
const viewMode = ref<'tree' | 'changes'>('tree')
const gitStatusData = ref<GitStatusResult | null>(null)

const changedCount = computed(() => gitStatusData.value?.changed_files.length ?? 0)

async function switchToChanges() {
  viewMode.value = 'changes'
  if (!currentModuleId.value || gitStatusData.value) return
  try {
    gitStatusData.value = await moduleApi.getModuleGitStatus(currentModuleId.value)
  } catch {
    gitStatusData.value = null
  }
}

/** Git 变更视图中选择文件 → 在右侧预览显示 diff。 */
function onChangeFileSelect(path: string) {
  selectedFilePath.value = path
  selectedFileGitStatus.value = gitStatusData.value?.changed_files.find(f => f.path === path)?.status
}

watch(() => props.projectId, loadModules, { immediate: true })

// 点击外部关闭工程下拉
function closeDropdown() { moduleDropdownOpen.value = false }
onMounted(() => document.addEventListener('click', closeDropdown))
onUnmounted(() => document.removeEventListener('click', closeDropdown))

// AI 工具写入文件后(df-data-changed entity=file)自动跳转变更视图 + 刷新
let _unlistenDataChanged: UnlistenFn | null = null
onMounted(async () => {
  _unlistenDataChanged = await listen<{ entity: string; action: string }>('df-data-changed', (e) => {
    if (e.payload.entity !== 'file') return
    // 刷新 git 状态缓存,使变更计数角标即时更新
    gitStatusData.value = null
    if (viewMode.value === 'changes') {
      // 已在变更视图,触发 GitChanges 重新拉取(切一次 viewMode 触发 watch)
      viewMode.value = 'tree'
      viewMode.value = 'changes'
    } else {
      // 不在变更视图,自动切换到变更视图,让用户看到 AI 写入的内容
      viewMode.value = 'changes'
    }
  })
})
onUnmounted(() => { _unlistenDataChanged?.() })

// 自动扫描子仓库
async function onScanSubmodules() {
  moduleDropdownOpen.value = false
  try {
    const n = await moduleApi.scanProjectModules(props.projectId)
    await loadModules()
    if (n > 0) {
      console.log('[FileExplorer] 扫描到新工程:', n)
    }
  } catch (e) {
    console.error('[FileExplorer] 扫描子仓库失败:', e)
  }
}

/** 打开新增工程弹窗。 */
function openAddModule() {
  editingModule.value = null
  moduleFormName.value = ''
  moduleFormPath.value = ''
  moduleFormGitUrl.value = ''
  showModuleModal.value = true
}

/** 打开编辑当前工程弹窗。 */
function openEditModule() {
  if (!currentModule.value) return
  editingModule.value = currentModule.value
  moduleFormName.value = currentModule.value.name
  moduleFormPath.value = currentModule.value.path
  moduleFormGitUrl.value = currentModule.value.git_url ?? ''
  showModuleModal.value = true
}

/** 弹出删除当前工程确认。 */
function confirmRemoveModule() {
  if (!currentModule.value) return
  showRemoveConfirm.value = true
}

/** 保存工程(新增/编辑)。 */
async function onSaveModule() {
  if (savingModule.value) return
  savingModule.value = true
  try {
    if (editingModule.value) {
      await moduleApi.updateProjectModule({
        id: editingModule.value.id,
        name: moduleFormName.value.trim(),
        path: moduleFormPath.value.trim(),
        gitUrl: moduleFormGitUrl.value.trim() || null,
      })
    } else {
      await moduleApi.addProjectModule({
        projectId: props.projectId,
        name: moduleFormName.value.trim(),
        path: moduleFormPath.value.trim(),
        gitUrl: moduleFormGitUrl.value.trim() || null,
      })
    }
    showModuleModal.value = false
    await loadModules()
  } catch (e) {
    console.error('[FileExplorer] 保存工程失败:', e)
  } finally {
    savingModule.value = false
  }
}

/** 删除当前工程。 */
async function onRemoveModule() {
  if (removingModule.value || !currentModule.value) return
  removingModule.value = true
  try {
    await moduleApi.removeProjectModule(currentModule.value.id)
    showRemoveConfirm.value = false
    currentModuleId.value = ''
    await loadModules()
  } catch (e) {
    console.error('[FileExplorer] 删除工程失败:', e)
  } finally {
    removingModule.value = false
  }
}

</script>

<style scoped>
.file-explorer {
  display: flex;
  flex-direction: column;
  height: 100%;
  max-height: 100%;
  background: var(--df-bg);
  border: 0.5px solid var(--df-border);
  border-radius: var(--df-radius-lg, 8px);
  overflow: hidden;
}

/* 工具栏 */
.explorer-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  border-bottom: 0.5px solid var(--df-border);
  background: var(--df-bg-card);
  flex-shrink: 0;
}

.toolbar-left,
.toolbar-right {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.module-select {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  background: var(--df-bg);
  border: 0.5px solid var(--df-border);
  border-radius: var(--df-radius-sm, 4px);
  color: var(--df-text);
  font-size: 12px;
  cursor: pointer;
  max-width: 180px;
}

.module-select:hover { border-color: var(--df-accent); }
.module-select-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.module-select-arrow {
  font-size: 10px;
  color: var(--df-text-dim);
  transition: transform 0.15s;
}
.module-select-arrow.open { transform: rotate(180deg); }

.module-dropdown { position: relative; flex-shrink: 0; }

.module-dropdown-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 220px;
  max-width: 320px;
  background: var(--df-bg-card);
  border: 0.5px solid var(--df-border);
  border-radius: var(--df-radius-sm, 4px);
  box-shadow: 0 6px 16px rgba(0,0,0,0.25);
  z-index: 20;
  max-height: 280px;
  overflow-y: auto;
  scrollbar-width: thin;
}

.module-dropdown-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 12px;
  cursor: pointer;
  border-bottom: 0.5px solid rgba(255,255,255,0.04);
}
.module-dropdown-item:last-child { border-bottom: none; }
.module-dropdown-item:hover { background: rgba(255,255,255,0.04); }
.module-dropdown-item.active {
  background: rgba(255,255,255,0.06);
  border-left: 2px solid var(--df-accent);
}
.module-dropdown-divider {
  height: 0.5px;
  background: var(--df-border);
  margin: 4px 0;
}
.module-dropdown-action .module-dropdown-name {
  color: var(--df-accent);
  font-size: 11px;
}
.module-dropdown-name { font-size: 12px; color: var(--df-text); }
.module-dropdown-path {
  font-size: 10px;
  color: var(--df-text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 面包屑 */
.breadcrumb {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--df-text-dim);
  overflow: hidden;
  min-width: 0;
}

.crumb {
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 200px;
  transition: background 0.12s;
}

.crumb:hover {
  background: var(--df-bg);
  color: var(--df-text);
}

.crumb-root {
  font-weight: 500;
  color: var(--df-text);
}

.crumb-sep {
  color: var(--df-text-dim);
  opacity: 0.6;
}

/* 按钮 */
.btn {
  padding: 6px 12px;
  border: none;
  border-radius: var(--df-radius-sm, 4px);
  font-size: 12px;
  cursor: pointer;
  transition: all 0.15s;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.btn-ghost {
  background: transparent;
  color: var(--df-text-secondary);
  border: 0.5px solid var(--df-border);
}

.btn-ghost:hover:not(:disabled) {
  background: var(--df-bg);
  color: var(--df-text);
}

.btn-ghost:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-sm {
  padding: 4px 10px;
  font-size: 11px;
}

/* 主体两栏 — 自适应比例 30% / 70% */
.explorer-body {
  flex: 1;
  display: flex;
  overflow: hidden;
  min-height: 0;
}

/* 左侧边栏(视图切换 + 内容) */
.explorer-sidebar {
  width: 30%;
  min-width: 200px;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  border-right: 0.5px solid var(--df-border);
  flex-shrink: 0;
  overflow: hidden;
}

/* 视图切换 Tab */
.explorer-view-tabs {
  display: flex;
  border-bottom: 0.5px solid var(--df-border);
  flex-shrink: 0;
}

.view-tab {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 6px 0;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--df-text-dim);
  cursor: pointer;
  font-size: 11px;
  transition: all 0.15s;
  position: relative;
}

.view-tab:hover {
  color: var(--df-text);
  background: rgba(255,255,255,0.03);
}

.view-tab.active {
  color: var(--df-text);
  border-bottom-color: var(--df-accent);
}

.changed-badge {
  position: absolute;
  top: 3px;
  right: 8px;
  min-width: 14px;
  height: 14px;
  padding: 0 4px;
  border-radius: 7px;
  background: var(--df-accent);
  color: #fff;
  font-size: 9px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

.explorer-tree {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 8px 4px 8px 0;
  scrollbar-width: thin;
}

.explorer-changes {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  min-height: 0;
  scrollbar-width: thin;
}

.explorer-changes .gc-body {
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin;
}

.tree-loading {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
}

.tree-loading .spinner {
  width: 20px;
  height: 20px;
  border: 2px solid var(--df-border, #444);
  border-top-color: var(--df-accent, #3a8);
  border-radius: 50%;
  animation: df-spin 0.8s linear infinite;
}

.explorer-preview {
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

.preview-placeholder-inline {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--df-text-dim);
  font-size: 13px;
  min-height: 200px;
}

/* 全局滚动条样式(薄) */
.explorer-sidebar ::-webkit-scrollbar {
  width: 4px;
  height: 4px;
}

.explorer-sidebar ::-webkit-scrollbar-thumb {
  background: var(--df-border);
  border-radius: 2px;
}

.explorer-sidebar ::-webkit-scrollbar-track {
  background: transparent;
}

/* 窄窗口自适应 */
@media (max-width: 900px) {
  .explorer-sidebar {
    width: 240px;
    min-width: 160px;
    max-width: 280px;
  }
}

@media (max-width: 600px) {
  .explorer-toolbar {
    flex-wrap: wrap;
    gap: 6px;
  }
  .explorer-sidebar {
    width: 180px;
    min-width: 120px;
  }
}

/* spinner(刷新按钮内) */
.spinner {
  width: 10px;
  height: 10px;
  border: 1.5px solid var(--df-border, #444);
  border-top-color: var(--df-accent, #3a8);
  border-radius: 50%;
  animation: df-spin 0.8s linear infinite;
  display: inline-block;
}

@keyframes df-spin {
  to {
    transform: rotate(360deg);
  }
}
</style>
