<template>
  <div class="git-changes">
    <!-- 分支信息(变更 + 历史 Tab 均显示当前分支) -->
    <div v-if="branchName" class="git-branch-bar">
      <span class="branch-icon">⎇</span>
      <span class="branch-name">{{ branchName }}</span>
      <span v-if="!showHistory" class="branch-count">{{ gitStatus?.changed_files.length ?? 0 }} {{ $t('gitChanges.changedFiles') }}</span>
      <span v-else class="branch-count">{{ totalCommits }} {{ $t('gitChanges.commitsUnit') }}</span>
      <button v-if="!showHistory" class="branch-switch-btn" @click="onSwitchBranch" :title="$t('gitChanges.switchBranch')">
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>
      </button>
    </div>

    <!-- 分支列表弹窗(只读查看,切换走 AI 工具) -->
    <div v-if="showBranchPicker" class="gc-branch-picker">
      <div class="gc-branch-picker-header">
        <span>{{ $t('gitChanges.branches') }}</span>
        <button class="gc-branch-picker-close" @click="closeBranchPicker">✕</button>
      </div>
      <div v-if="branchLoading" class="gc-loading"><span class="spinner"></span></div>
      <div v-else class="gc-branch-list">
        <div
          v-for="b in branchList"
          :key="b.name"
          class="gc-branch-item"
          :class="{ current: b.is_current }"
        >
          <span class="gc-branch-icon">{{ b.is_current ? '●' : '○' }}</span>
          <span class="gc-branch-name">{{ b.name }}</span>
          <span v-if="b.is_current" class="gc-branch-tag">HEAD</span>
        </div>
        <div v-if="branchList.length === 0" class="gc-empty">{{ $t('gitChanges.noBranches') }}</div>
      </div>
      <div class="gc-branch-picker-hint">{{ $t('gitChanges.branchSwitchHint') }}</div>
    </div>

    <div v-if="loading && !gitStatus" class="gc-skeleton">
      <div class="gc-skeleton-row" v-for="i in 6" :key="i"></div>
    </div>

    <!-- 非 Git 仓库 -->
    <div v-else-if="gitStatus && !gitStatus.is_git_repo" class="gc-empty">
      {{ $t('gitChanges.notGitRepo') }}
    </div>

    <div v-else-if="gitStatus" class="gc-body">
      <!-- Tab: 变更 / 历史 -->
      <nav class="gc-tabs">
        <button class="gc-tab" :class="{ active: !showHistory }" @click="showHistory = false">
          {{ $t('gitChanges.changes') }} ({{ gitStatus.changed_files.length }})
        </button>
        <button class="gc-tab" :class="{ active: showHistory }" @click="switchToHistory">
          {{ $t('gitChanges.history') }} ({{ totalCommits }})
        </button>
      </nav>

      <!-- ====== 变更列表 ====== -->
      <div v-if="!showHistory" class="gc-changes">
        <template v-if="gitStatus.changed_files.length === 0">
          <div class="gc-empty">{{ $t('gitChanges.noChanges') }}</div>
        </template>

        <!-- 按目录树分组的变更文件 -->
        <div v-for="group in groupedChanges" :key="group.label" class="gc-group">
          <div class="gc-group-header">{{ group.label }} ({{ group.files.length }})</div>
          <div v-for="f in group.files" :key="f.path"
            class="gc-file-row"
            :class="{ selected: selectedFile === f.path }"
            :style="{ paddingLeft: filePathDepth(f.path) * 16 + 14 + 'px' }"
            @click="selectFile(f.path)"
          >
            <span class="gc-file-status" :class="'gc-st-' + group.key">{{ statusLabel(f.status) }}</span>
            <span class="gc-file-name">{{ filePathLeaf(f.path) }}</span>
          </div>
        </div>
      </div>

      <!-- ====== 提交历史(列表始终可见,详情侧边/底部) ====== -->
      <div v-else class="gc-history">
        <!-- 提交搜索框(前端过滤已加载提交:subject/author) -->
        <div class="gc-search">
          <svg class="gc-search-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
          <input
            v-model="commitSearch"
            class="gc-search-input"
            :placeholder="$t('gitChanges.searchPlaceholder')"
            type="text"
          />
          <button v-if="commitSearch" class="gc-search-clear" @click="commitSearch = ''">✕</button>
        </div>

        <!-- 提交列表(始终可见,双行布局) -->
        <div class="gc-history-list">
          <div v-if="filteredCommits.length === 0" class="gc-empty gc-empty-inline">
            {{ commitSearch ? $t('gitChanges.noSearchResult') : $t('gitChanges.noCommits') }}
          </div>
          <div
            v-for="c in filteredCommits"
            :key="c.hash"
            class="gc-commit-row"
            :class="{ selected: selectedCommit?.hash === c.hash }"
            @click="selectCommit(c)"
          >
            <div class="gc-commit-line1">
              <span class="gc-commit-hash">{{ c.hash.slice(0, 8) }}</span>
              <span class="gc-commit-author">{{ c.author }}</span>
              <span class="gc-commit-time">{{ formatTime(c.timestamp) }}</span>
            </div>
            <div class="gc-commit-msg">{{ c.subject }}</div>
          </div>
        </div>

        <!-- 加载更多 -->
        <div v-if="hasMoreCommits && !loadingMore" class="gc-load-more" @click="loadMoreCommits">
          {{ $t('gitChanges.loadMore') }}
        </div>
        <div v-if="loadingMore" class="gc-loading"><span class="spinner"></span></div>
      </div>
    </div>

    <!-- ====== 提交详情(从底部/侧边滑出,列表始终可见) ====== -->
    <transition name="gc-detail-slide">
      <div v-if="selectedCommit" class="gc-commit-detail">
        <div class="gc-commit-detail-header">
          <span class="gc-commit-detail-hash">{{ selectedCommit.hash }}</span>
          <span class="gc-commit-detail-author">{{ selectedCommit.author }}</span>
          <span class="gc-commit-detail-time">{{ formatTime(selectedCommit.timestamp) }}</span>
          <span class="gc-commit-detail-msg">{{ selectedCommit.subject }}</span>
          <button class="gc-detail-close" @click="closeCommitDetail">✕</button>
        </div>
        <div v-if="commitDetailLoading" class="gc-loading"><span class="spinner"></span></div>
        <template v-else-if="commitDetail">
          <!-- 变更文件列表 -->
          <div class="gc-commit-files">
            <div
              v-for="f in commitDetail.files"
              :key="f.path"
              class="gc-commit-file-row"
              :class="{ active: commitSelectedFile === f.path }"
              @click="showCommitFileDiff(f.path)"
            >
              <span class="gc-file-status" :class="'gc-st-' + statusKey(f.status)">{{ statusLabel(f.status) }}</span>
              <span class="gc-file-path">{{ f.path }}</span>
            </div>
          </div>
          <!-- Diff 预览(可收起,占更宽空间) -->
          <div v-if="commitSelectedFile" class="gc-commit-diff">
            <div class="gc-commit-diff-header">
              <span class="gc-commit-diff-path">{{ commitSelectedFile }}</span>
              <button class="gc-commit-diff-toggle" @click="commitDiffCollapsed = !commitDiffCollapsed">
                {{ commitDiffCollapsed ? $t('gitChanges.expand') : $t('gitChanges.collapse') }}
              </button>
            </div>
            <div v-show="!commitDiffCollapsed" class="gc-diff-content">
              <div v-if="!commitDiff" class="gc-diff-empty">{{ $t('gitChanges.noDiff') }}</div>
              <div v-for="(ln, idx) in commitDiffLines" :key="idx" class="diff-line" :class="'diff-' + ln.type">
                <span class="diff-hdr-text" v-if="ln.type === 'hdr'">{{ ln.text }}</span>
                <template v-else>
                  <span class="diff-line-prefix">{{ ln.prefix }}</span>
                  <span class="diff-line-text">{{ ln.text }}</span>
                </template>
              </div>
            </div>
          </div>
        </template>
      </div>
    </transition>
  </div>
</template>

<script setup lang="ts">
/**
 * Git 变更查看器 — 变更文件列表(按目录树分组) + 提交历史(分页/搜索) + 文件 diff 预览。
 *
 * 历史 Tab 布局:列表始终可见,选中提交的详情从底部滑出(可收起),diff 在更宽容器渲染,
 * 避免几百行 diff 把列表挤走、或在 30% 窄栏内挤压。
 */
import { ref, computed, watch } from 'vue'
import { moduleApi, type GitStatusResult } from '@/api/module'
import { useI18n } from 'vue-i18n'

const { t } = useI18n()

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

const emit = defineEmits<{
  (e: 'select-file', path: string): void
}>()

const loading = ref(false)
const gitStatus = ref<GitStatusResult | null>(null)
const showHistory = ref(false)
const selectedFile = ref('')
const diffContent = ref('')
const diffLoading = ref(false)

/** 单条提交记录形状(对齐 getModuleCommits / GitRecentCommit)。 */
interface CommitItem {
  hash: string
  subject: string
  timestamp: number
  author: string
}

// 提交历史(分页)
const commits = ref<CommitItem[]>([])
const totalCommits = ref(0)
const hasMoreCommits = ref(false)
const loadingMore = ref(false)
const commitSkip = ref(0)
const COMMIT_PAGE_SIZE = 50

// 提交搜索(前端过滤已加载提交:按 subject 关键词或作者)
const commitSearch = ref('')

/** 已加载提交按搜索词过滤(subject 包含或 author 包含)。 */
const filteredCommits = computed<CommitItem[]>(() => {
  const q = commitSearch.value.trim().toLowerCase()
  if (!q) return commits.value
  return commits.value.filter(
    (c) => c.subject.toLowerCase().includes(q) || c.author.toLowerCase().includes(q),
  )
})

// 已选提交详情
const selectedCommit = ref<CommitItem | null>(null)
const commitDetail = ref<{ files: { status: string; path: string }[]; diff: string } | null>(null)
const commitDetailLoading = ref(false)
const commitSelectedFile = ref('')
const commitDiff = ref('')
const commitDiffCollapsed = ref(false)

const branchName = computed(() => gitStatus.value?.branch || '')

interface FileGroup {
  key: string
  label: string
  files: { path: string; status: string }[]
}

function statusKey(s: string): string {
  const x = s.trim()
  if (x.startsWith('M') || x === 'R') return 'modified'
  if (x.startsWith('A')) return 'added'
  if (x.startsWith('D')) return 'deleted'
  if (x === '??') return 'untracked'
  return 'modified'
}

/** 按状态分组 */
const groupedChanges = computed<FileGroup[]>(() => {
  if (!gitStatus.value) return []
  const groups: FileGroup[] = [
    { key: 'modified', label: t('gitChanges.modified'), files: [] },
    { key: 'added', label: t('gitChanges.added'), files: [] },
    { key: 'deleted', label: t('gitChanges.deleted'), files: [] },
    { key: 'untracked', label: t('gitChanges.untracked'), files: [] },
  ]
  for (const f of gitStatus.value.changed_files) {
    const s = f.status.trim()
    if (s.startsWith('M') || s === 'R') groups[0].files.push(f)
    else if (s.startsWith('A')) groups[1].files.push(f)
    else if (s.startsWith('D')) groups[2].files.push(f)
    else if (s === '??') groups[3].files.push(f)
    else groups[0].files.push(f)
  }
  return groups.filter(g => g.files.length > 0)
})

interface DiffLine {
  type: 'add' | 'del' | 'ctx' | 'hdr'
  prefix: string
  text: string
}

const commitDiffLines = computed<DiffLine[]>(() => parseDiff(commitDiff.value))

function parseDiff(text: string): DiffLine[] {
  if (!text) return []
  const lines: DiffLine[] = []
  for (const raw of text.split('\n')) {
    if (raw.startsWith('@@')) {
      lines.push({ type: 'hdr', prefix: '', text: raw })
    } else if (raw.startsWith('+')) {
      lines.push({ type: 'add', prefix: '+', text: raw.slice(1) })
    } else if (raw.startsWith('-')) {
      lines.push({ type: 'del', prefix: '-', text: raw.slice(1) })
    } else if (raw.startsWith('\\')) {
      continue
    } else {
      lines.push({ type: 'ctx', prefix: ' ', text: raw })
    }
  }
  return lines
}

function statusLabel(s: string): string {
  const x = s.trim()
  if (x === '??') return 'U'
  if (x.startsWith('A')) return 'A'
  if (x.startsWith('M')) return 'M'
  if (x.startsWith('D')) return 'D'
  if (x.startsWith('R')) return 'R'
  return x.charAt(0) || '?'
}

/** 格式化 Unix 时间戳为相对时间或具体时间 */
function formatTime(ts: number): string {
  if (!ts) return ''
  const now = Math.floor(Date.now() / 1000)
  const diffSec = now - ts
  if (diffSec < 60) return t('common.justNow')
  if (diffSec < 3600) return t('common.minutesAgo', { n: Math.floor(diffSec / 60) })
  if (diffSec < 86400) return t('common.hoursAgo', { n: Math.floor(diffSec / 3600) })
  if (diffSec < 172800) return t('common.yesterday')
  if (diffSec < 2592000) return t('common.dayAgo', { n: Math.floor(diffSec / 86400) })
  // 超过 30 天显示具体日期
  const d = new Date(ts * 1000)
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}

/** 计算文件路径的目录深度(用于缩进)。root 级文件返回 0。 */
function filePathDepth(path: string): number {
  const segs = path.split('/').filter(Boolean)
  return Math.max(0, segs.length - 1)
}

/** 提取文件名(最后一段)。 */
function filePathLeaf(path: string): string {
  const segs = path.split('/').filter(Boolean)
  return segs[segs.length - 1] || path
}

/**
 * 从一次提交的全量 diff 文本中,精确提取指定 path 对应的那一块。
 *
 * 按 `diff --git a/<path> b/<path>` 头切块,避免子串误配
 * (例:`line.includes("src/app.rs")` 会误匹配 `src/app.rs.bak`)。
 * 解析 a/ 与 b/ 路径,任一等于目标 path 即命中;切块持续到下一个 `diff --git` 或文本末尾。
 */
function extractFileDiff(allDiff: string, targetPath: string): string {
  if (!allDiff) return ''
  const lines = allDiff.split('\n')
  const chunks: string[][] = []
  const chunkPaths: string[] = []
  // 第一行若不是 diff 头(罕见:全局元信息),归入一个无 path 的前置块,不参与匹配。
  let current: string[] | null = null

  for (const line of lines) {
    if (line.startsWith('diff --git ')) {
      // 起新块
      if (current) chunks.push(current)
      current = [line]
      chunkPaths.push(parseDiffGitPath(line))
    } else {
      if (!current) current = [] // 兜底:头之前的行
      current.push(line)
    }
  }
  if (current) chunks.push(current)

  // 定位目标块:精确匹配 a/ 或 b/ 路径(支持重命名,两边任一命中即取该块)
  for (let i = 0; i < chunks.length; i++) {
    const p = chunkPaths[i]
    if (p && (p === targetPath || pathEqualsAnySide(p, targetPath))) {
      return chunks[i].join('\n')
    }
  }
  return ''
}

/** 解析 `diff --git a/<path> b/<path>` 头,返回 "a/path|b/path" 复合串(用于后续精确比对)。 */
function parseDiffGitPath(header: string): string {
  // 形如 "diff --git a/foo/bar.rs b/foo/bar.rs",空格分隔;path 内不含空格时成立。
  // 取第 3、4 段(a/.. b/..),用 "|" 拼接便于 pathEqualsAnySide 判定。
  const parts = header.split(/\s+/)
  const a = parts[2] || ''
  const b = parts[3] || ''
  return `${a}|${b}`
}

/** 给定 parseDiffGitPath 产物与目标 path,判断目标是否等于 a/ 或 b/ 路径。 */
function pathEqualsAnySide(parsed: string, targetPath: string): boolean {
  const [a, b] = parsed.split('|')
  const aPath = a.startsWith('a/') ? a.slice(2) : a
  const bPath = b.startsWith('b/') ? b.slice(2) : b
  return aPath === targetPath || bPath === targetPath
}

async function loadStatus() {
  if (!props.moduleId) return
  loading.value = true
  try {
    gitStatus.value = await moduleApi.getModuleGitStatus(props.moduleId)
    // 复用 getModuleGitStatus 内部已返回的 recent_commits(避免再发一次 getModuleCommits
    // 造成首次 git log -50 重复请求 + recent_commits 被丢弃)。
    const recent = gitStatus.value?.recent_commits ?? []
    commits.value = recent.map((c) => ({
      hash: c.hash,
      subject: c.subject,
      timestamp: c.timestamp,
      author: c.author,
    }))
    // 全量提交数读后端 total_commits(git rev-list --count HEAD),非 commits.length(后者受分页限制)。
    const backendTotal = gitStatus.value?.total_commits ?? 0
    totalCommits.value = backendTotal
    commitSkip.value = commits.value.length
    // has_more:后端无 total 时(commits 仍可能未全载)用页大小推断;有 total 时按已载 < total 判定。
    hasMoreCommits.value =
      backendTotal > 0
        ? commits.value.length < backendTotal
        : commits.value.length >= COMMIT_PAGE_SIZE
  } catch {
    gitStatus.value = null
    commits.value = []
  } finally {
    loading.value = false
  }
}

/** 占位:分支切换后续接入(需拉取分支列表 + checkout IPC)。 */
const showBranchPicker = ref(false)
const branchList = ref<{ name: string; is_current: boolean }[]>([])
const branchLoading = ref(false)
const currentBranch = ref('')

async function onSwitchBranch() {
  if (showBranchPicker.value) {
    showBranchPicker.value = false
    return
  }
  showBranchPicker.value = true
  branchLoading.value = true
  try {
    const res = await moduleApi.listBranches(props.moduleId)
    branchList.value = res.branches
    currentBranch.value = res.current
  } catch {
    branchList.value = []
  } finally {
    branchLoading.value = false
  }
}

function closeBranchPicker() {
  showBranchPicker.value = false
}

async function loadMoreCommits() {
  if (loadingMore.value || !hasMoreCommits.value) return
  loadingMore.value = true
  try {
    const res = await moduleApi.getModuleCommits(props.moduleId, commitSkip.value, COMMIT_PAGE_SIZE)
    // 按哈希去重(防止分页边界与 recent_commits 重叠)
    const exist = new Set(commits.value.map((c) => c.hash))
    for (const c of res.commits) {
      if (!exist.has(c.hash)) commits.value.push(c)
    }
    hasMoreCommits.value = res.has_more
    commitSkip.value += res.commits.length
    // 全量提交数读后端 total(git rev-list --count HEAD),非 commits.length(后者受分页限制)。
    totalCommits.value = res.total
  } catch {
    // 静默失败
  } finally {
    loadingMore.value = false
  }
}

async function switchToHistory() {
  showHistory.value = true
  // recent_commits 已填首页,无需在此补页(原 < 50 补页逻辑已无意义,留 loadMore 手动触发)
}

async function selectFile(path: string) {
  selectedFile.value = path
  emit('select-file', path)
  diffContent.value = ''
  diffLoading.value = true
  try {
    const res = await moduleApi.getModuleFileDiff(props.moduleId, path)
    diffContent.value = res.diff || ''
  } catch {
    diffContent.value = ''
  } finally {
    diffLoading.value = false
  }
}

/** 点击提交行 → 加载该提交的变更文件列表 */
async function selectCommit(c: CommitItem) {
  // 点击已选提交 → 收起详情(二次点击切换)
  if (selectedCommit.value?.hash === c.hash) {
    closeCommitDetail()
    return
  }
  selectedCommit.value = c
  commitDetail.value = null
  commitSelectedFile.value = ''
  commitDiff.value = ''
  commitDiffCollapsed.value = false
  commitDetailLoading.value = true
  try {
    const res = await moduleApi.getCommitDetail(props.moduleId, c.hash)
    commitDetail.value = res
  } catch {
    commitDetail.value = null
  } finally {
    commitDetailLoading.value = false
  }
}

function closeCommitDetail() {
  selectedCommit.value = null
  commitDetail.value = null
  commitSelectedFile.value = ''
  commitDiff.value = ''
}

/** 在提交详情中点击文件 → 精确提取该文件的 diff 块(按 diff --git 头切块) */
function showCommitFileDiff(path: string) {
  commitSelectedFile.value = path
  commitDiffCollapsed.value = false
  if (!commitDetail.value) {
    commitDiff.value = ''
    return
  }
  commitDiff.value = extractFileDiff(commitDetail.value.diff, path)
}

watch(() => props.moduleId, loadStatus, { immediate: true })
</script>

<style scoped>
.git-changes {
  display: flex;
  flex-direction: column;
  height: 100%;
  font-size: 13px;
  color: var(--df-text);
}
.git-branch-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-bottom: 0.5px solid var(--df-border);
  background: var(--df-bg-card);
  flex-shrink: 0;
}
.branch-icon { font-size: 16px; }
.branch-name { font-weight: 600; color: var(--df-accent); }
.branch-count { margin-left: auto; font-size: 12px; color: var(--df-text-dim); }
.branch-switch-btn {
  background: none;
  border: none;
  color: var(--df-text-dim);
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 3px;
  display: flex;
  align-items: center;
  transition: all 0.15s;
}
.branch-switch-btn:hover {
  color: var(--df-text);
  background: rgba(255,255,255,0.05);
}

/* Tabs */
.gc-tabs { display: flex; border-bottom: 0.5px solid var(--df-border); flex-shrink: 0; }
.gc-tab {
  flex: 1; padding: 8px; background: none; border: none;
  border-bottom: 2px solid transparent; color: var(--df-text-dim);
  cursor: pointer; font-size: 12px; transition: all 0.15s;
}
.gc-tab.active { color: var(--df-text); border-bottom-color: var(--df-accent); }
.gc-tab:hover { color: var(--df-text); }

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

/* 分组 */
.gc-group-header {
  padding: 6px 14px; font-size: 11px; font-weight: 600;
  color: var(--df-text-dim); text-transform: uppercase; letter-spacing: 0.5px;
  background: rgba(255,255,255,0.02); border-bottom: 0.5px solid var(--df-border);
  position: sticky; top: 0; z-index: 1;
}

/* Diff 预览 */
.gc-diff-content {
  font-family: var(--df-font-mono, Consolas, monospace);
  font-size: 12px; line-height: 1.5; overflow: auto; scrollbar-width: thin;
}
.diff-line { display: flex; padding: 0 14px; }
.diff-line-prefix { width: 16px; flex-shrink: 0; text-align: center; user-select: none; }
.diff-line-text { flex: 1; white-space: pre; overflow: hidden; }
.diff-hdr-text {
  padding: 4px 14px; background: rgba(60,140,220,0.08);
  color: var(--df-text-dim); font-weight: 500; font-size: 11px; display: block;
}
.diff-add { background: rgba(40,160,70,0.12); }
.diff-add .diff-line-prefix { color: #4caf50; }
.diff-del { background: rgba(220,60,60,0.12); }
.diff-del .diff-line-prefix { color: #e05050; }
.diff-ctx { color: var(--df-text); }

/* ====== 提交历史 ====== */
.gc-history { display: flex; flex-direction: column; min-height: 0; }

/* 搜索框 */
.gc-search {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 10px; border-bottom: 0.5px solid var(--df-border);
  background: var(--df-bg-card); flex-shrink: 0;
}
.gc-search-icon { color: var(--df-text-dim); flex-shrink: 0; }
.gc-search-input {
  flex: 1; min-width: 0;
  background: rgba(255,255,255,0.04); border: 0.5px solid var(--df-border);
  border-radius: 4px; padding: 4px 8px;
  color: var(--df-text); font-size: 12px; outline: none;
}
.gc-search-input:focus { border-color: var(--df-accent); }
.gc-search-input::placeholder { color: var(--df-text-dim); }
.gc-search-clear {
  background: none; border: none; color: var(--df-text-dim);
  cursor: pointer; font-size: 12px; padding: 0 4px;
}
.gc-search-clear:hover { color: var(--df-text); }

/* 提交列表 */
.gc-history-list { flex: 1; min-height: 0; overflow-y: auto; scrollbar-width: thin; }
.gc-commit-row {
  display: flex; flex-direction: column; gap: 2px;
  padding: 6px 14px; border-bottom: 0.5px solid rgba(255,255,255,0.03);
  cursor: pointer; transition: background 0.1s;
}
.gc-commit-row:hover { background: rgba(255,255,255,0.04); }
.gc-commit-row.selected { background: rgba(255,255,255,0.06); }
.gc-commit-line1 {
  display: flex; align-items: center; gap: 8px;
  font-size: 11px; color: var(--df-text-dim);
}
.gc-commit-hash {
  font-family: var(--df-font-mono, Consolas, monospace);
  font-size: 11px; color: var(--df-accent); flex-shrink: 0;
}
.gc-commit-author {
  font-size: 11px; color: var(--df-text-dim);
  flex-shrink: 0; max-width: 140px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gc-commit-time { margin-left: auto; font-size: 11px; color: var(--df-text-dim); flex-shrink: 0; }
.gc-commit-msg {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px;
  color: var(--df-text);
}

/* ====== 提交详情(从底部滑出,列表始终可见) ====== */
.gc-commit-detail {
  flex-shrink: 0;
  border-top: 0.5px solid var(--df-border);
  background: var(--df-bg-card);
  max-height: 55%;
  display: flex; flex-direction: column;
  box-shadow: 0 -4px 12px rgba(0,0,0,0.18);
}
.gc-commit-detail-header {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 14px; background: rgba(255,255,255,0.03);
  border-bottom: 0.5px solid var(--df-border);
}
.gc-commit-detail-hash {
  font-family: var(--df-font-mono, Consolas, monospace);
  font-size: 11px; color: var(--df-accent); flex-shrink: 0;
}
.gc-commit-detail-author {
  font-size: 11px; color: var(--df-text-dim); flex-shrink: 0;
}
.gc-commit-detail-time { font-size: 11px; color: var(--df-text-dim); flex-shrink: 0; }
.gc-commit-detail-msg {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px;
}
.gc-detail-close {
  background: none; border: none; color: var(--df-text-dim);
  cursor: pointer; font-size: 14px; padding: 0 4px; flex-shrink: 0;
}
.gc-detail-close:hover { color: var(--df-text); }

/* 提交的文件列表 */
.gc-commit-files {
  border-bottom: 0.5px solid var(--df-border);
  overflow-y: auto; max-height: 140px; scrollbar-width: thin; flex-shrink: 0;
}
.gc-commit-file-row {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 14px 5px 28px; cursor: pointer; transition: background 0.1s;
  border-bottom: 0.5px solid rgba(255,255,255,0.02);
}
.gc-commit-file-row:hover { background: rgba(255,255,255,0.04); }
.gc-commit-file-row.active { background: rgba(255,255,255,0.06); }

/* 提交 diff(可收起,占更宽空间) */
.gc-commit-diff {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column;
  overflow: hidden;
}
.gc-commit-diff-header {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 14px; background: rgba(255,255,255,0.03);
  border-bottom: 0.5px solid var(--df-border); flex-shrink: 0;
}
.gc-commit-diff-path {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--df-font-mono, Consolas, monospace); font-size: 11px;
  color: var(--df-text);
}
.gc-commit-diff-toggle {
  background: none; border: 0.5px solid var(--df-border);
  color: var(--df-text-dim); cursor: pointer; font-size: 11px;
  padding: 2px 8px; border-radius: 3px;
}
.gc-commit-diff-toggle:hover { color: var(--df-text); border-color: var(--df-accent); }
.gc-commit-diff .gc-diff-content { flex: 1; min-height: 0; }

/* 滑出动画 */
.gc-detail-slide-enter-active, .gc-detail-slide-leave-active {
  transition: transform 0.18s ease, opacity 0.18s ease;
}
.gc-detail-slide-enter-from, .gc-detail-slide-leave-to {
  transform: translateY(12px); opacity: 0;
}

.gc-file-status { width: 20px; text-align: center; font-size: 11px; font-weight: 700; flex-shrink: 0; }
.gc-st-modified { color: #f0a020; }
.gc-st-added { color: #4caf50; }
.gc-st-deleted { color: #e05050; }
.gc-st-untracked { color: #999; }
.gc-file-path {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--df-font-mono, Consolas, monospace); font-size: 12px;
}
.gc-file-name {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--df-font-mono, Consolas, monospace); font-size: 12px;
}
.gc-file-row {
  display: flex; align-items: center; gap: 8px;
  cursor: pointer; transition: background 0.1s;
}
.gc-file-row:hover { background: rgba(255,255,255,0.04); }
.gc-file-row.selected { background: rgba(255,255,255,0.06); }

/* 加载更多 */
.gc-load-more {
  text-align: center; padding: 12px; cursor: pointer;
  color: var(--df-accent); font-size: 12px;
  transition: background 0.1s; flex-shrink: 0;
}
.gc-load-more:hover { background: rgba(255,255,255,0.04); }

/* 分支列表弹窗 */
.gc-branch-picker {
  border-bottom: 0.5px solid var(--df-border);
  background: var(--df-bg);
}
.gc-branch-picker-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 14px;
  font-size: 11px;
  font-weight: 600;
  color: var(--df-text-dim);
  text-transform: uppercase;
}
.gc-branch-picker-close {
  background: none; border: none; color: var(--df-text-dim);
  cursor: pointer; font-size: 14px; padding: 0 4px;
}
.gc-branch-list {
  max-height: 200px;
  overflow-y: auto;
  scrollbar-width: thin;
}
.gc-branch-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 14px;
  font-size: 12px;
  cursor: default;
}
.gc-branch-item.current { color: var(--df-accent); }
.gc-branch-icon { font-size: 10px; flex-shrink: 0; }
.gc-branch-name { font-family: var(--df-font-mono, Consolas, monospace); }
.gc-branch-tag {
  font-size: 9px;
  padding: 1px 5px;
  border-radius: 3px;
  background: rgba(108,99,255,0.15);
  color: var(--df-accent);
}
.gc-branch-picker-hint {
  padding: 6px 14px;
  font-size: 11px;
  color: var(--df-text-dim);
  border-top: 0.5px solid var(--df-border);
}

/* 骨架屏 */
.gc-skeleton {
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.gc-skeleton-row {
  height: 24px;
  border-radius: 4px;
  background: linear-gradient(90deg, rgba(255,255,255,0.04) 0%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.04) 100%);
  background-size: 200% 100%;
  animation: gc-shimmer 1.5s infinite linear;
}
@keyframes gc-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* 通用 */
.gc-loading, .gc-empty {
  display: flex; justify-content: center; align-items: center;
  padding: 40px 0; color: var(--df-text-dim); font-size: 13px;
}
.gc-empty-inline { padding: 24px 0; }
.gc-diff-loading { display: flex; justify-content: center; padding: 20px; }
.gc-diff-empty { padding: 20px 14px; color: var(--df-text-dim); font-size: 12px; text-align: center; }
.spinner {
  width: 16px; height: 16px;
  border: 2px solid var(--df-border); border-top-color: var(--df-accent);
  border-radius: 50%; animation: df-spin 0.8s linear infinite; display: inline-block;
}
@keyframes df-spin { to { transform: rotate(360deg); } }
</style>
