<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useToast } from 'vue-toast-notification'
import { useWorkspaceStore } from '../stores/workspace'
import { useConnectionsStore } from '../stores/connections'
import { useSettingsStore } from '../stores/settings'
import * as sftpApi from '../api/sftp'
import type { RemoteTransferTask, SftpFileInfo } from '../api/sftp'
import {
  getParentPath,
  getPathParts,
  isAbsoluteServerPath,
  joinAbsolutePath,
  normalizeAbsoluteServerPath,
  resolveInputPath,
} from '../utils/sftpPath'
import {
  AlertCircle,
  CheckCircle,
  ChevronRight,
  Copy,
  Download,
  Eye,
  EyeOff,
  File,
  FolderOpen,
  FolderPlus,
  Loader,
  RefreshCw,
  Search,
  Trash2,
  Upload,
  X,
} from 'lucide-vue-next'

interface Props {
  workspaceId: string
  connectionId: number
  active?: boolean
}

interface UploadProgressItem {
  id: string
  name: string
  size: number
  uploaded: number
  total: number
  status: 'pending' | 'uploading' | 'success' | 'error' | 'skipped'
  message?: string
}

type UploadConflictAction = 'overwrite' | 'skip'

const props = defineProps<Props>()

const toast = useToast()
const workspaceStore = useWorkspaceStore()
const connectionsStore = useConnectionsStore()
const settingsStore = useSettingsStore()

const conn = computed(() => connectionsStore.getConnection(props.connectionId))
const currentPath = computed(() => {
  return workspaceStore.workspaces[props.workspaceId]?.currentPath || '/'
})
const selectedFiles = computed(() => {
  return workspaceStore.workspaces[props.workspaceId]?.selectedFiles || []
})
const pathParts = computed(() => getPathParts(currentPath.value))
const filteredFiles = computed(() => {
  const q = searchQuery.value.trim().toLowerCase()
  const base = showHiddenFiles.value ? files.value : files.value.filter((file) => !file.name.startsWith('.'))
  return q ? base.filter((file) => file.name.toLowerCase().includes(q)) : base
})
const selectedFile = computed(() => {
  const fileName = selectedFiles.value[0]
  return fileName ? filteredFiles.value.find((file) => file.name === fileName) || null : null
})
const targetConnectionOptions = computed(() => {
  return connectionsStore.connections.filter((connection) => connection.id !== props.connectionId)
})

const files = ref<SftpFileInfo[]>([])
const loading = ref(false)
const error = ref('')
const uploading = ref(false)
const fileInputRef = ref<HTMLInputElement | null>(null)
const searchInputRef = ref<HTMLInputElement | null>(null)
const pathInputRef = ref<HTMLInputElement | null>(null)
const pathDraft = ref('/')
const showHiddenFiles = ref(false)
const searchQuery = ref('')
const pathReady = ref(false)

const showUploadProgress = ref(false)
const uploadProgressList = ref<UploadProgressItem[]>([])
let activeUploadContextId = 0
const showUploadConflictModal = ref(false)
const pendingUploadConflictItem = ref<UploadProgressItem | null>(null)
const applyUploadConflictToRemaining = ref(false)
const uploadConflictBatchAction = ref<UploadConflictAction | null>(null)
let pendingUploadConflictResolve: ((result: { action: UploadConflictAction; applyToRemaining: boolean }) => void) | null = null

const showMkdirModal = ref(false)
const mkdirName = ref('')

const showDeleteModal = ref(false)
const pendingDeleteFile = ref<SftpFileInfo | null>(null)

const showTransferModal = ref(false)
const transferFile = ref<SftpFileInfo | null>(null)
const transferTargetConnectionId = ref<number | null>(null)
const transferTargetPath = ref('')
const transferring = ref(false)
const transferError = ref('')
const transferProgress = ref(0)
const transferTransferredBytes = ref(0)
const transferTotalBytes = ref(0)
const transferTaskId = ref('')
let transferPollAbort = false
let skipNextPathLoad = false
let pathInitializationPromise: Promise<boolean> | null = null

function resetTransientState() {
  invalidateUploadContext()
  stopTransferProgress()
  searchQuery.value = ''
  error.value = ''
  files.value = []
  pathReady.value = false
  pathInitializationPromise = null
  skipNextPathLoad = false
  showUploadProgress.value = false
  uploadProgressList.value = []
  showMkdirModal.value = false
  mkdirName.value = ''
  showDeleteModal.value = false
  pendingDeleteFile.value = null
  showTransferModal.value = false
  transferFile.value = null
  transferTargetConnectionId.value = null
  transferTargetPath.value = ''
  transferError.value = ''
  transferProgress.value = 0
  transferTransferredBytes.value = 0
  transferTotalBytes.value = 0
  transferTaskId.value = ''
}

watch(currentPath, (path, previousPath) => {
  pathDraft.value = isAbsoluteServerPath(path) ? normalizeAbsoluteServerPath(path) : path || '/'

  if (path === previousPath) return
  if (skipNextPathLoad) {
    skipNextPathLoad = false
    return
  }

  if (props.active && pathReady.value) {
    void loadFiles()
  }
}, { immediate: true })

watch(
  () => [props.workspaceId, props.connectionId] as const,
  () => {
    resetTransientState()
    if (props.active) {
      void loadFiles()
    }
  }
)

watch(
  () => props.active,
  (active) => {
    if (active) {
      void loadFiles()
    }
  },
  { immediate: true }
)

watch(filteredFiles, (nextFiles) => {
  if (nextFiles.length === 0) {
    workspaceStore.updateSelectedFiles(props.workspaceId, [])
    return
  }

  const firstSelected = selectedFiles.value[0]
  if (!firstSelected || !nextFiles.some((file) => file.name === firstSelected)) {
    const firstFile = nextFiles[0]
    if (firstFile) {
      workspaceStore.updateSelectedFiles(props.workspaceId, [firstFile.name])
    }
  }
}, { immediate: true })

onMounted(() => {
  window.addEventListener('keydown', handleGlobalShortcuts)
})

onBeforeUnmount(() => {
  invalidateUploadContext()
  stopTransferProgress()
  window.removeEventListener('keydown', handleGlobalShortcuts)
})

function handleGlobalShortcuts(event: KeyboardEvent) {
  if (!props.active) return
  if (showTransferModal.value || showDeleteModal.value || showMkdirModal.value || showUploadConflictModal.value) return

  const key = event.key.toLowerCase()
  if ((event.ctrlKey || event.metaKey) && key === 'f') {
    event.preventDefault()
    searchInputRef.value?.focus()
    searchInputRef.value?.select()
    return
  }

  if ((event.ctrlKey || event.metaKey) && key === 'l') {
    event.preventDefault()
    pathInputRef.value?.focus()
    pathInputRef.value?.select()
    return
  }

  if (event.key === 'F5') {
    event.preventDefault()
    void loadFiles()
    return
  }

  if (event.altKey && event.key === 'ArrowUp') {
    event.preventDefault()
    goUp()
  }
}

function createUploadContext() {
  activeUploadContextId += 1
  resetUploadConflictState()
  return activeUploadContextId
}

function invalidateUploadContext() {
  activeUploadContextId += 1
  resetUploadConflictState()
}

function closeUploadConflictPrompt() {
  showUploadConflictModal.value = false
  pendingUploadConflictItem.value = null
  applyUploadConflictToRemaining.value = false
  pendingUploadConflictResolve = null
}

function resetUploadConflictState() {
  uploadConflictBatchAction.value = null
  if (!pendingUploadConflictResolve) {
    closeUploadConflictPrompt()
    return
  }

  const resolve = pendingUploadConflictResolve
  closeUploadConflictPrompt()
  resolve({ action: 'skip', applyToRemaining: false })
}

function resetTransferProgress() {
  transferProgress.value = 0
  transferTransferredBytes.value = 0
  transferTotalBytes.value = 0
  transferTaskId.value = ''
}

function stopTransferProgress() {
  transferPollAbort = true
}

function formatSize(bytes: number): string {
  if (bytes < 1024) return bytes + ' B'
  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
  return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
}

function formatDate(ts: number): string {
  return new Date(ts).toLocaleString()
}

function formatTransferBytes(bytes: number) {
  return formatSize(Math.max(0, bytes || 0))
}

function resolveSftpError(errorLike: unknown, fallback: string) {
  const typedError = errorLike as { response?: { data?: { error?: string; message?: string } } }
  return typedError?.response?.data?.error ?? typedError?.response?.data?.message ?? fallback
}

function sortFiles(list: SftpFileInfo[]) {
  return list.slice().sort((a, b) => {
    if (a.directory !== b.directory) return a.directory ? -1 : 1
    return a.name.localeCompare(b.name)
  })
}

function isMissingPathError(message: string) {
  return /no such file or directory/i.test(message)
}

function updatePath(path: string, options: { skipLoad?: boolean } = {}) {
  const normalizedPath = normalizeAbsoluteServerPath(path)
  if (options.skipLoad) {
    skipNextPathLoad = true
  }
  workspaceStore.updateSftpPath(props.workspaceId, normalizedPath)
}

async function ensurePathReady() {
  if (pathReady.value) return true
  if (pathInitializationPromise) return pathInitializationPromise

  pathInitializationPromise = (async () => {
    const storedPath = (workspaceStore.workspaces[props.workspaceId]?.currentPath || '').trim()

    if (isAbsoluteServerPath(storedPath)) {
      const normalizedPath = normalizeAbsoluteServerPath(storedPath)
      pathReady.value = true
      if (normalizedPath !== storedPath) {
        updatePath(normalizedPath, { skipLoad: true })
      }
      return true
    }

    try {
      const res = await sftpApi.getPwd(props.connectionId)
      const basePath = normalizeAbsoluteServerPath(res.data.path || '/')
      const resolvedPath = resolveInputPath(storedPath || '.', basePath)
      pathReady.value = true
      updatePath(resolvedPath, { skipLoad: true })
      return true
    } catch (err) {
      error.value = resolveSftpError(err, '获取当前目录失败')
      pathReady.value = false
      return false
    }
  })()

  try {
    return await pathInitializationPromise
  } finally {
    pathInitializationPromise = null
  }
}

async function loadFiles() {
  if (!props.active) return
  if (!(await ensurePathReady())) return

  loading.value = true
  error.value = ''
  const path = normalizeAbsoluteServerPath(currentPath.value)

  try {
    const res = await sftpApi.listFiles(props.connectionId, path)
    files.value = sortFiles(res.data)
  } catch (err) {
    const message = resolveSftpError(err, '获取文件列表失败')
    const fallbackPath = getParentPath(path)

    if (path !== '/' && fallbackPath !== path && isMissingPathError(message)) {
      try {
        const fallbackRes = await sftpApi.listFiles(props.connectionId, fallbackPath)
        files.value = sortFiles(fallbackRes.data)
        error.value = ''
        updatePath(fallbackPath, { skipLoad: true })
        return
      } catch {
        // Ignore fallback failure and surface the original error.
      }
    }

    error.value = message
  } finally {
    loading.value = false
  }
}

function navigateToDir(name: string) {
  if (loading.value) return
  updatePath(joinAbsolutePath(currentPath.value, name))
}

function navigateToIndex(index: number) {
  if (loading.value) return
  if (index < 0) {
    updatePath('/')
    return
  }
  const nextPath = pathParts.value.length > 0 ? '/' + pathParts.value.slice(0, index + 1).join('/') : '/'
  updatePath(nextPath)
}

function navigateToTypedPath() {
  if (loading.value) return
  const raw = pathDraft.value.trim()
  if (!raw) return
  updatePath(resolveInputPath(raw, currentPath.value))
}

function goUp() {
  if (loading.value) return
  if (normalizeAbsoluteServerPath(currentPath.value) === '/') return
  updatePath(getParentPath(currentPath.value))
}

function selectFile(file: SftpFileInfo) {
  workspaceStore.updateSelectedFiles(props.workspaceId, [file.name])
}

function handleFileClick(file: SftpFileInfo) {
  selectFile(file)
  if (file.directory) {
    navigateToDir(file.name)
  }
}

function handleDownload(file: SftpFileInfo) {
  if (file.directory) return
  const path = joinAbsolutePath(currentPath.value, file.name)
  const safeConnectionName = conn.value?.name?.replace(/[\\/:*?"<>|]+/g, '-').trim()
  const downloadName = settingsStore.downloadNamingStrategy === 'connectionPrefix' && conn.value?.name
    ? `${safeConnectionName || 'connection'}-${file.name}`
    : file.name
  sftpApi.downloadFile(props.connectionId, path, downloadName).catch(() => {
    toast.error('下载失败')
  })
}

function triggerUpload() {
  fileInputRef.value?.click()
}

async function pollUploadTask(taskId: string, itemId: string, uploadContextId: number) {
  while (uploadContextId === activeUploadContextId) {
    const res = await sftpApi.getUploadTask(taskId)
    if (uploadContextId !== activeUploadContextId) return

    const target = uploadProgressList.value.find((item) => item.id === itemId)
    if (!target) return

    target.uploaded = res.data.transferredBytes
    target.total = res.data.totalBytes

    if (res.data.status === 'success') {
      target.status = 'success'
      return
    }

    if (res.data.status === 'error') {
      target.status = 'error'
      target.message = res.data.error || '上传失败'
      return
    }

    await new Promise((resolve) => setTimeout(resolve, 250))
  }
}

function markUploadItemSkipped(item: UploadProgressItem, message: string) {
  item.status = 'skipped'
  item.message = message
  item.uploaded = item.size
  item.total = item.size
}

async function promptUploadConflict(item: UploadProgressItem, uploadContextId: number) {
  if (uploadContextId !== activeUploadContextId) {
    return { action: 'skip' as UploadConflictAction, applyToRemaining: false }
  }

  return await new Promise<{ action: UploadConflictAction; applyToRemaining: boolean }>((resolve) => {
    pendingUploadConflictItem.value = item
    applyUploadConflictToRemaining.value = false
    showUploadConflictModal.value = true
    pendingUploadConflictResolve = resolve
  })
}

function resolveUploadConflict(action: UploadConflictAction) {
  if (!pendingUploadConflictResolve) return

  const resolve = pendingUploadConflictResolve
  const applyToRemaining = applyUploadConflictToRemaining.value
  closeUploadConflictPrompt()
  resolve({ action, applyToRemaining })
}

async function resolveUploadConflictAction(item: UploadProgressItem, uploadContextId: number) {
  if (settingsStore.uploadConflictStrategy === 'overwrite') {
    return 'overwrite' as UploadConflictAction
  }

  if (settingsStore.uploadConflictStrategy === 'skip') {
    return 'skip' as UploadConflictAction
  }

  if (uploadConflictBatchAction.value) {
    return uploadConflictBatchAction.value
  }

  const result = await promptUploadConflict(item, uploadContextId)
  if (uploadContextId !== activeUploadContextId) {
    return 'skip' as UploadConflictAction
  }

  if (result.applyToRemaining) {
    uploadConflictBatchAction.value = result.action
  }

  return result.action
}

async function handleFileSelect(event: Event) {
  const uploadContextId = createUploadContext()
  const input = event.target as HTMLInputElement
  const selected = input.files
  if (!selected?.length) return

  uploading.value = true
  showUploadProgress.value = true
  error.value = ''

  const path = normalizeAbsoluteServerPath(currentPath.value)
  uploadProgressList.value = Array.from(selected).map((file, index) => ({
    id: `${Date.now()}-${index}`,
    name: file.name,
    size: file.size,
    uploaded: 0,
    total: file.size,
    status: 'pending',
  }))

  try {
    for (const item of uploadProgressList.value) {
      if (uploadContextId !== activeUploadContextId) return
      const file = Array.from(selected).find((entry) => entry.name === item.name && entry.size === item.size)
      if (!file) continue

      const existingFile = files.value.find((entry) => !entry.directory && entry.name === file.name)
      if (existingFile) {
        const action = await resolveUploadConflictAction(item, uploadContextId)
        if (uploadContextId !== activeUploadContextId) return

        if (action === 'skip') {
          markUploadItemSkipped(
            item,
            settingsStore.uploadConflictStrategy === 'ask' && !uploadConflictBatchAction.value
              ? '已取消覆盖'
              : '同名文件已跳过',
          )
          continue
        }
      }

      item.status = 'uploading'
      const uploadRes = await sftpApi.uploadFile(props.connectionId, path, file)
      await pollUploadTask(uploadRes.data.taskId, item.id, uploadContextId)
    }

    toast.success(`成功上传 ${uploadProgressList.value.filter((item) => item.status === 'success').length} 个文件`)
    await loadFiles()
  } catch (err) {
    toast.error(resolveSftpError(err, '上传失败'))
  } finally {
    uploading.value = false
    resetUploadConflictState()
    if (fileInputRef.value) {
      fileInputRef.value.value = ''
    }
  }
}

function openMkdirModal() {
  mkdirName.value = ''
  showMkdirModal.value = true
}

async function submitMkdir() {
  const name = mkdirName.value.trim()
  if (!name) return

  const path = joinAbsolutePath(currentPath.value, name)

  try {
    await sftpApi.createDir(props.connectionId, path)
    showMkdirModal.value = false
    toast.success('文件夹已创建')
    await loadFiles()
  } catch (err) {
    toast.error(resolveSftpError(err, '创建文件夹失败'))
  }
}

function openDeleteModal(file: SftpFileInfo) {
  pendingDeleteFile.value = file
  showDeleteModal.value = true
}

async function confirmDelete() {
  const file = pendingDeleteFile.value
  if (!file) return

  const path = joinAbsolutePath(currentPath.value, file.name)

  try {
    await sftpApi.deleteFile(props.connectionId, path, file.directory)
    showDeleteModal.value = false
    pendingDeleteFile.value = null
    toast.success('删除成功')
    await loadFiles()
  } catch (err) {
    toast.error(resolveSftpError(err, '删除失败'))
  }
}

async function waitForTransferTask(taskId: string) {
  transferPollAbort = false
  transferTaskId.value = taskId

  while (!transferPollAbort) {
    const res = await sftpApi.getRemoteTransferTask(taskId)
    const task = res.data as RemoteTransferTask
    transferProgress.value = Math.max(0, Math.min(100, task.progress || 0))
    transferTransferredBytes.value = Math.max(0, task.transferredBytes || 0)
    transferTotalBytes.value = Math.max(0, task.totalBytes || 0)

    if (task.status === 'success') return task
    if (task.status === 'error') throw new Error(task.error || '传输失败')
    if (task.status === 'cancelled') throw new Error('传输已取消')

    await new Promise((resolve) => setTimeout(resolve, 300))
  }

  throw new Error('传输已取消')
}

async function cancelTransfer() {
  const taskId = transferTaskId.value
  stopTransferProgress()
  if (!taskId) {
    transferring.value = false
    transferError.value = '传输已取消'
    return
  }

  try {
    const res = await sftpApi.cancelRemoteTransferTask(taskId)
    const task = res.data
    transferProgress.value = Math.max(0, Math.min(100, task.progress || transferProgress.value))
    transferTransferredBytes.value = Math.max(0, task.transferredBytes || transferTransferredBytes.value)
    transferTotalBytes.value = Math.max(0, task.totalBytes || transferTotalBytes.value)
    transferError.value = task.cancelRequested ? '已请求取消传输' : (task.message || '传输正在收尾')
  } catch (err) {
    transferError.value = resolveSftpError(err, '取消传输失败')
  }
}

async function openTransferModal(file: SftpFileInfo) {
  if (file.directory) return
  if (connectionsStore.connections.length === 0) {
    await connectionsStore.fetchConnections().catch(() => {})
  }

  transferFile.value = file
  transferTargetConnectionId.value = targetConnectionOptions.value[0]?.id ?? null
  transferTargetPath.value = normalizeAbsoluteServerPath(currentPath.value)
  if (!transferTargetPath.value.endsWith('/')) {
    transferTargetPath.value += '/'
  }
  transferError.value = ''
  resetTransferProgress()
  showTransferModal.value = true
}

function closeTransferModal() {
  if (transferring.value) return
  stopTransferProgress()
  showTransferModal.value = false
  transferFile.value = null
  transferTargetConnectionId.value = null
  transferTargetPath.value = ''
  transferError.value = ''
  resetTransferProgress()
}

async function submitTransfer() {
  const file = transferFile.value
  const targetId = transferTargetConnectionId.value
  if (!file || targetId == null) return

  const sourcePath = joinAbsolutePath(currentPath.value, file.name)
  let targetPath = transferTargetPath.value.trim()
  if (targetPath.endsWith('/') || !targetPath) {
    targetPath = targetPath + file.name
  }

  transferring.value = true
  transferError.value = ''
  resetTransferProgress()

  try {
    const created = await sftpApi.createRemoteTransferTask(props.connectionId, sourcePath, targetId, targetPath)
    await waitForTransferTask(created.data.taskId)
    transferProgress.value = 100
    toast.success('远程传输完成')
    await loadFiles()
    closeTransferModal()
  } catch (err) {
    transferError.value = resolveSftpError(err, (err as Error)?.message || '传输失败')
  } finally {
    stopTransferProgress()
    transferring.value = false
  }
}

const totalUploadProgress = computed(() => {
  if (uploadProgressList.value.length === 0) return 0
  const totalSize = uploadProgressList.value.reduce((sum, item) => sum + item.size, 0)
  const uploadedSize = uploadProgressList.value.reduce((sum, item) => {
    if (item.status === 'success') return sum + item.size
    if (item.status === 'skipped') return sum + item.size
    if (item.status === 'uploading') return sum + item.uploaded
    return sum
  }, 0)
  return totalSize > 0 ? Math.round((uploadedSize / totalSize) * 100) : 0
})
</script>

<template>
  <div class="flex h-full flex-col bg-slate-950">
    <div class="border-b border-slate-700 bg-slate-900/80 px-3 py-3">
      <div class="flex flex-col gap-3">
        <div class="flex flex-wrap items-start justify-between gap-3">
          <div class="min-w-0">
            <h3 class="truncate text-sm font-semibold text-slate-100">
              {{ conn?.name || 'SFTP' }}
            </h3>
            <p class="mt-1 truncate text-xs text-slate-500">
              {{ conn?.username }}@{{ conn?.host }}
            </p>
          </div>

          <div class="flex flex-wrap items-center gap-2">
            <button
              type="button"
              @click="triggerUpload"
              class="inline-flex min-h-[40px] items-center gap-1.5 rounded-lg border border-slate-600 bg-slate-800 px-3 py-2 text-xs text-slate-300 transition-colors hover:border-cyan-500/50 hover:text-slate-100"
            >
              <Upload class="h-3.5 w-3.5" />
              <span>上传</span>
            </button>
            <button
              type="button"
              @click="openMkdirModal"
              class="inline-flex min-h-[40px] items-center gap-1.5 rounded-lg border border-slate-600 bg-slate-800 px-3 py-2 text-xs text-slate-300 transition-colors hover:border-cyan-500/50 hover:text-slate-100"
            >
              <FolderPlus class="h-3.5 w-3.5" />
              <span>新建目录</span>
            </button>
            <button
              type="button"
              @click="loadFiles"
              :disabled="loading"
              class="inline-flex min-h-[40px] items-center gap-1.5 rounded-lg border border-slate-600 bg-slate-800 px-3 py-2 text-xs text-slate-300 transition-colors hover:border-cyan-500/50 hover:text-slate-100 disabled:cursor-not-allowed disabled:opacity-50"
            >
              <RefreshCw class="h-3.5 w-3.5" :class="{ 'animate-spin': loading }" />
              <span>刷新</span>
            </button>
            <button
              type="button"
              @click="showHiddenFiles = !showHiddenFiles"
              class="inline-flex min-h-[40px] items-center gap-1.5 rounded-lg border px-3 py-2 text-xs transition-colors"
              :class="showHiddenFiles
                ? 'border-cyan-500/30 bg-cyan-500/10 text-cyan-200'
                : 'border-slate-600 bg-slate-800 text-slate-300 hover:border-cyan-500/50 hover:text-slate-100'"
            >
              <Eye v-if="showHiddenFiles" class="h-3.5 w-3.5" />
              <EyeOff v-else class="h-3.5 w-3.5" />
              <span>{{ showHiddenFiles ? '隐藏隐藏文件' : '显示隐藏文件' }}</span>
            </button>
          </div>
        </div>

        <div class="flex flex-wrap items-stretch gap-2">
          <div class="relative min-w-0 flex-1 basis-64">
            <Search class="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-500" />
            <input
              ref="searchInputRef"
              v-model="searchQuery"
              type="text"
              placeholder="搜索文件... (Ctrl/Cmd+F)"
              class="h-10 w-full rounded-lg border border-slate-700 bg-slate-900 py-2 pl-9 pr-9 text-sm text-slate-100 placeholder:text-slate-500 focus:border-cyan-500 focus:outline-none"
            />
            <button
              v-if="searchQuery"
              type="button"
              class="absolute right-2 top-1/2 inline-flex h-8 w-8 -translate-y-1/2 items-center justify-center rounded text-slate-500 transition-colors hover:bg-slate-800 hover:text-slate-300"
              @click="searchQuery = ''"
              aria-label="清除搜索"
            >
              <X class="h-4 w-4" />
            </button>
          </div>

          <div class="flex h-10 min-w-0 flex-1 basis-72 items-center gap-2 rounded-lg border border-slate-700 bg-slate-900 px-2">
            <input
              ref="pathInputRef"
              v-model="pathDraft"
              type="text"
              class="min-w-0 flex-1 bg-transparent px-1 text-sm text-slate-100 focus:outline-none"
              placeholder="输入路径 (Ctrl/Cmd+L)"
              @keyup.enter="navigateToTypedPath"
            />
            <button
              type="button"
              class="inline-flex h-8 shrink-0 items-center rounded-md bg-slate-800 px-3 text-xs text-slate-200 transition-colors hover:bg-slate-700"
              @click="navigateToTypedPath"
            >
              前往
            </button>
          </div>
        </div>
      </div>
      <input
        ref="fileInputRef"
        type="file"
        multiple
        class="hidden"
        @change="handleFileSelect"
      />
    </div>

    <div class="border-b border-slate-800 bg-slate-900/60 px-3 py-2">
      <nav class="flex items-center gap-1 overflow-x-auto text-sm text-slate-400">
        <button
          type="button"
          @click="navigateToIndex(-1)"
          class="rounded px-2 py-1 transition-colors hover:bg-slate-800 hover:text-slate-100"
        >
          /
        </button>
        <template v-for="(part, index) in pathParts" :key="`${part}-${index}`">
          <ChevronRight class="h-4 w-4 flex-shrink-0 text-slate-600" />
          <button
            type="button"
            @click="navigateToIndex(index)"
            class="max-w-[160px] truncate rounded px-2 py-1 transition-colors hover:bg-slate-800 hover:text-slate-100"
          >
            {{ part || '/' }}
          </button>
        </template>
      </nav>
    </div>

    <div
      v-if="showUploadProgress && uploadProgressList.length > 0"
      class="border-b border-slate-800 bg-slate-900/50 px-3 py-3"
    >
      <div class="flex items-center justify-between gap-3">
        <div>
          <p class="text-sm font-medium text-slate-200">上传进度</p>
          <p class="mt-1 text-xs text-slate-500">
            {{ totalUploadProgress }}% · {{ uploadProgressList.filter((item) => item.status === 'success' || item.status === 'skipped').length }}/{{ uploadProgressList.length }} 完成
          </p>
        </div>
        <button
          type="button"
          class="inline-flex min-h-[36px] items-center rounded-md border border-slate-700 px-2.5 text-xs text-slate-300 transition-colors hover:bg-slate-800"
          @click="showUploadProgress = false"
        >
          收起
        </button>
      </div>
      <div class="mt-3 space-y-2">
        <div
          v-for="item in uploadProgressList"
          :key="item.id"
          class="rounded-lg border border-slate-800 bg-slate-950/70 px-3 py-2"
        >
          <div class="flex items-center justify-between gap-3 text-xs text-slate-300">
            <span class="truncate">{{ item.name }}</span>
            <span class="flex items-center gap-1">
              <Loader v-if="item.status === 'uploading'" class="h-3.5 w-3.5 animate-spin text-cyan-300" />
              <CheckCircle v-else-if="item.status === 'success'" class="h-3.5 w-3.5 text-emerald-300" />
              <CheckCircle v-else-if="item.status === 'skipped'" class="h-3.5 w-3.5 text-amber-300" />
              <AlertCircle v-else-if="item.status === 'error'" class="h-3.5 w-3.5 text-red-300" />
              <span>{{ item.status === 'skipped' ? 'skipped' : item.status }}</span>
            </span>
          </div>
          <div class="mt-2 h-2 overflow-hidden rounded-full bg-slate-800">
            <div
              class="h-full rounded-full bg-cyan-500 transition-all"
              :style="{ width: `${item.total > 0 ? Math.round((item.uploaded / item.total) * 100) : 0}%` }"
            />
          </div>
          <p class="mt-1 text-[11px] text-slate-500">
            {{ formatSize(item.uploaded) }} / {{ formatSize(item.total) }} {{ item.message ? `· ${item.message}` : '' }}
          </p>
        </div>
      </div>
    </div>

    <p v-if="error" class="px-4 py-2 text-sm text-red-400">{{ error }}</p>

    <div v-if="loading" class="flex flex-1 items-center justify-center text-slate-400">
      加载中...
    </div>

    <div v-else class="min-h-0 flex-1 overflow-auto">
      <div class="min-w-[720px]">
        <div class="sticky top-0 grid grid-cols-[minmax(15rem,2.6fr)_7rem_12rem_7rem] gap-2 border-b border-slate-700 bg-slate-900/95 px-4 py-2 text-xs uppercase tracking-wider text-slate-500">
          <span>名称</span>
          <span>大小</span>
          <span>修改时间</span>
          <span class="text-right">操作</span>
        </div>

        <div class="divide-y divide-slate-800">
          <button
            v-if="currentPath !== '/'"
            type="button"
            @click="goUp"
            class="grid w-full grid-cols-[minmax(15rem,2.6fr)_7rem_12rem_7rem] gap-2 items-center px-4 py-3 text-left transition-colors hover:bg-slate-800/60"
          >
            <span class="flex items-center gap-3">
              <FolderOpen class="h-4 w-4 text-slate-500" />
              <span class="text-slate-300">..</span>
            </span>
            <span class="text-slate-600">-</span>
            <span class="text-slate-600">-</span>
            <span />
          </button>

          <button
            v-for="file in filteredFiles"
            :key="file.name"
            type="button"
            @click="handleFileClick(file)"
            @dblclick="!file.directory && handleDownload(file)"
            class="group grid w-full grid-cols-[minmax(15rem,2.6fr)_7rem_12rem_7rem] gap-2 items-center px-4 py-3 text-left transition-colors hover:bg-slate-800/60"
            :class="selectedFile?.name === file.name ? 'bg-slate-800/70' : ''"
          >
            <span class="flex min-w-0 items-center gap-3">
              <component
                :is="file.directory ? FolderOpen : File"
                class="h-4 w-4 flex-shrink-0"
                :class="file.directory ? 'text-amber-300' : 'text-slate-400'"
              />
              <span class="truncate text-sm text-slate-200">{{ file.name }}</span>
            </span>
            <span class="text-sm text-slate-500">{{ file.directory ? '-' : formatSize(file.size) }}</span>
            <span class="text-sm text-slate-500">{{ formatDate(file.mtime) }}</span>
            <div class="flex items-center justify-end gap-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
              <button
                v-if="!file.directory"
                type="button"
                class="rounded p-1.5 text-slate-400 transition-colors hover:bg-slate-700 hover:text-cyan-300"
                @click.stop="handleDownload(file)"
                aria-label="下载文件"
              >
                <Download class="h-4 w-4" />
              </button>
              <button
                v-if="!file.directory"
                type="button"
                class="rounded p-1.5 text-slate-400 transition-colors hover:bg-slate-700 hover:text-cyan-300"
                @click.stop="openTransferModal(file)"
                aria-label="远程传输"
              >
                <Copy class="h-4 w-4" />
              </button>
              <button
                type="button"
                class="rounded p-1.5 text-slate-400 transition-colors hover:bg-red-900/50 hover:text-red-300"
                @click.stop="openDeleteModal(file)"
                aria-label="删除"
              >
                <Trash2 class="h-4 w-4" />
              </button>
            </div>
          </button>

          <div v-if="filteredFiles.length === 0" class="p-8 text-center text-slate-500">
            {{ searchQuery ? '没有匹配的文件' : '空目录' }}
          </div>
        </div>
      </div>
    </div>

    <div
      v-if="showUploadConflictModal && pendingUploadConflictItem"
      class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4"
    >
      <div class="w-full max-w-md rounded-xl border border-slate-700 bg-slate-800 p-5 shadow-xl">
        <h3 class="text-base font-semibold text-slate-100">同名文件已存在</h3>
        <p class="mt-2 text-sm text-slate-300">
          远端已存在同名文件「{{ pendingUploadConflictItem.name }}」，请选择本次处理方式。
        </p>

        <label class="mt-4 flex items-start gap-3 rounded-lg border border-slate-700 bg-slate-900/70 px-3 py-3 text-sm text-slate-300">
          <input
            v-model="applyUploadConflictToRemaining"
            type="checkbox"
            class="mt-0.5 h-4 w-4 rounded border-slate-600 bg-slate-800 text-cyan-500 focus:ring-cyan-500"
          >
          <span>对本次剩余冲突文件全部应用该操作</span>
        </label>

        <div class="mt-5 flex gap-2">
          <button
            type="button"
            class="flex-1 rounded-lg bg-cyan-600 px-3 py-2 text-sm text-white transition-colors hover:bg-cyan-500"
            @click="resolveUploadConflict('overwrite')"
          >
            覆盖
          </button>
          <button
            type="button"
            class="flex-1 rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-sm text-slate-200 transition-colors hover:bg-slate-600"
            @click="resolveUploadConflict('skip')"
          >
            跳过
          </button>
        </div>
      </div>
    </div>

    <div
      v-if="showMkdirModal"
      class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4"
      @click.self="showMkdirModal = false"
    >
      <div class="w-full max-w-sm rounded-xl border border-slate-700 bg-slate-800 p-5 shadow-xl">
        <h3 class="text-base font-semibold text-slate-100">新建目录</h3>
        <input
          v-model="mkdirName"
          type="text"
          placeholder="目录名称"
          class="mt-3 w-full rounded-lg border border-slate-600 bg-slate-900 px-3 py-2 text-sm text-slate-100 focus:border-cyan-500 focus:outline-none"
          @keyup.enter="submitMkdir"
        />
        <div class="mt-4 flex gap-2">
          <button
            type="button"
            class="flex-1 rounded-lg bg-cyan-600 px-3 py-2 text-sm text-white transition-colors hover:bg-cyan-500"
            @click="submitMkdir"
          >
            创建
          </button>
          <button
            type="button"
            class="flex-1 rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-sm text-slate-200 transition-colors hover:bg-slate-600"
            @click="showMkdirModal = false"
          >
            取消
          </button>
        </div>
      </div>
    </div>

    <div
      v-if="showDeleteModal && pendingDeleteFile"
      class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4"
      @click.self="showDeleteModal = false"
    >
      <div class="w-full max-w-sm rounded-xl border border-slate-700 bg-slate-800 p-5 shadow-xl">
        <h3 class="text-base font-semibold text-slate-100">确认删除</h3>
        <p class="mt-2 text-sm text-slate-300">
          确定删除{{ pendingDeleteFile.directory ? '目录' : '文件' }}「{{ pendingDeleteFile.name }}」吗？
        </p>
        <div class="mt-4 flex gap-2">
          <button
            type="button"
            class="flex-1 rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-200 transition-colors hover:bg-red-500/20"
            @click="confirmDelete"
          >
            删除
          </button>
          <button
            type="button"
            class="flex-1 rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-sm text-slate-200 transition-colors hover:bg-slate-600"
            @click="showDeleteModal = false"
          >
            取消
          </button>
        </div>
      </div>
    </div>

    <div
      v-if="showTransferModal && transferFile"
      class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4"
      @click.self="closeTransferModal"
    >
      <div class="w-full max-w-lg rounded-xl border border-slate-700 bg-slate-800 p-5 shadow-xl">
        <h3 class="text-base font-semibold text-slate-100">远程传输</h3>
        <p class="mt-2 text-sm text-slate-400">
          从当前连接复制文件「{{ transferFile.name }}」到另一台机器。
        </p>

        <div class="mt-4 space-y-3">
          <div>
            <label class="mb-1 block text-xs text-slate-400">目标连接</label>
            <select
              v-model="transferTargetConnectionId"
              class="w-full rounded-lg border border-slate-600 bg-slate-900 px-3 py-2 text-sm text-slate-100 focus:border-cyan-500 focus:outline-none"
            >
              <option
                v-for="target in targetConnectionOptions"
                :key="target.id"
                :value="target.id"
              >
                {{ target.name }}
              </option>
            </select>
          </div>

          <div>
            <label class="mb-1 block text-xs text-slate-400">目标路径</label>
            <input
              v-model="transferTargetPath"
              type="text"
              class="w-full rounded-lg border border-slate-600 bg-slate-900 px-3 py-2 text-sm text-slate-100 focus:border-cyan-500 focus:outline-none"
              placeholder="/tmp/"
            />
          </div>

          <div v-if="transferring" class="rounded-lg border border-slate-700 bg-slate-900/70 p-3">
            <div class="flex items-center justify-between text-xs text-slate-300">
              <span>传输进度</span>
              <span>{{ transferProgress }}%</span>
            </div>
            <div class="mt-2 h-2 overflow-hidden rounded-full bg-slate-800">
              <div class="h-full rounded-full bg-cyan-500 transition-all" :style="{ width: `${transferProgress}%` }" />
            </div>
            <p class="mt-2 text-xs text-slate-500">
              {{ formatTransferBytes(transferTransferredBytes) }} / {{ formatTransferBytes(transferTotalBytes) }}
            </p>
          </div>

          <p v-if="transferError" class="text-sm text-red-400">{{ transferError }}</p>
        </div>

        <div class="mt-5 flex gap-2">
          <button
            type="button"
            class="flex-1 rounded-lg bg-cyan-600 px-3 py-2 text-sm text-white transition-colors hover:bg-cyan-500 disabled:cursor-not-allowed disabled:opacity-50"
            :disabled="transferring || transferTargetConnectionId == null"
            @click="submitTransfer"
          >
            开始传输
          </button>
          <button
            v-if="transferring"
            type="button"
            class="rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-200 transition-colors hover:bg-amber-500/20"
            @click="cancelTransfer"
          >
            取消
          </button>
          <button
            v-else
            type="button"
            class="rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-sm text-slate-200 transition-colors hover:bg-slate-600"
            @click="closeTransferModal"
          >
            关闭
          </button>
        </div>
      </div>
    </div>
  </div>
</template>
