<script setup lang="ts">
defineOptions({ name: 'SftpView' })
import { ref, computed, watch, onBeforeUnmount } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useToast } from 'vue-toast-notification'
import { useConnectionsStore } from '../stores/connections'
import { useSftpTabsStore } from '../stores/sftpTabs'
import * as sftpApi from '../api/sftp'
import type { SftpFileInfo } from '../api/sftp'
import {
  ArrowLeft,
  FolderOpen,
  File,
  Upload,
  FolderPlus,
  RefreshCw,
  Eye,
  EyeOff,
  Download,
  Trash2,
  ChevronRight,
  Copy,
  CheckCircle,
  AlertCircle,
  Loader,
} from 'lucide-vue-next'

const route = useRoute()
const router = useRouter()
const toast = useToast()
const store = useConnectionsStore()
const sftpTabsStore = useSftpTabsStore()

const connectionId = computed(() => Number(route.params.id))
const conn = ref(store.getConnection(connectionId.value))

const currentPath = ref('.')
const pathParts = ref<string[]>([])
const files = ref<SftpFileInfo[]>([])
const loading = ref(false)
const error = ref('')
const uploading = ref(false)
const selectedFile = ref<string | null>(null)
const fileInputRef = ref<HTMLInputElement | null>(null)

const showHiddenFiles = ref(false)
const searchQuery = ref('')
let searchDebounceTimer = 0
const filteredFiles = ref<SftpFileInfo[]>([])

function applyFileFilters() {
  const q = searchQuery.value.trim().toLowerCase()
  const base = showHiddenFiles.value ? files.value : files.value.filter((f) => !f.name.startsWith('.'))
  filteredFiles.value = q ? base.filter((f) => f.name.toLowerCase().includes(q)) : base
}

watch([searchQuery, showHiddenFiles, files], () => {
  clearTimeout(searchDebounceTimer)
  searchDebounceTimer = window.setTimeout(() => {
    applyFileFilters()
  }, 300)
}, { immediate: true })

onBeforeUnmount(() => {
  invalidateUploadContext()
  clearTimeout(searchDebounceTimer)
  stopTransferProgress()
})

const showUploadProgress = ref(false)
const uploadProgressList = ref<{ id: string; name: string; size: number; uploaded: number; total: number; status: 'pending' | 'uploading' | 'success' | 'error'; message?: string }[]>([])
let activeUploadContextId = 0

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

function invalidateUploadContext() {
  activeUploadContextId += 1
}

const totalProgress = 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 === 'uploading') return sum + item.uploaded
    return sum
  }, 0)
  return totalSize > 0 ? Math.round((uploadedSize / totalSize) * 100) : 0
})

const currentUploadingFile = computed(() => {
  return uploadProgressList.value.find(item => item.status === 'uploading')?.name || ''
})

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()
}

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

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

function stopTransferProgress() {
  transferPollAbort = true
}

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

async function waitForTransferTask(taskId: string) {
  transferPollAbort = false
  transferTaskId.value = taskId
  while (!transferPollAbort) {
    const res = await sftpApi.getRemoteTransferTask(taskId)
    const task = res.data
    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)
    if (task.cancelRequested) {
      transferError.value = '已请求取消传输'
    } else {
      transferError.value = task.message || '当前传输正在收尾，稍后会结束'
    }
  } catch (err: unknown) {
    const res = err as { response?: { data?: { error?: string } } }
    transferError.value = res?.response?.data?.error ?? '取消传输失败'
  }
}

let routeInitRequestId = 0

function isStaleRouteInit(requestId?: number, isCancelled?: () => boolean) {
  return (isCancelled?.() ?? false) || (requestId != null && requestId !== routeInitRequestId)
}

function resetVolatileSftpState() {
  invalidateUploadContext()
  conn.value = undefined
  currentPath.value = '.'
  pathParts.value = []
  files.value = []
  filteredFiles.value = []
  loading.value = false
  error.value = ''
  selectedFile.value = null
  searchQuery.value = ''

  showUploadProgress.value = false
  uploadProgressList.value = []
  uploading.value = false

  stopTransferProgress()
  showTransferModal.value = false
  transferFile.value = null
  transferTargetConnectionId.value = null
  transferTargetPath.value = ''
  transferError.value = ''
  transferring.value = false
  resetTransferProgress()
}

watch(
  () => route.params.id,
  async (routeId, _oldRouteId, onCleanup) => {
    const requestId = ++routeInitRequestId
    let cleanedUp = false
    onCleanup(() => {
      cleanedUp = true
    })

    const isRouteInitCancelled = () => cleanedUp

    const rawId = Array.isArray(routeId) ? routeId[0] : routeId
    const parsedId = Number(rawId)

    // 只在切换到不同连接时重置状态
    // 使用 store 中的状态跟踪，确保在组件重建后仍能正确判断
    const lastLoaded = sftpTabsStore.getLastLoadedConnectionId(parsedId)
    const isConnectionChanged = lastLoaded !== parsedId
    if (isConnectionChanged) {
      resetVolatileSftpState()
    }

    if (!rawId || !Number.isInteger(parsedId) || parsedId <= 0) {
      if (isStaleRouteInit(requestId, isRouteInitCancelled)) return
      conn.value = undefined
      await redirectToConnections('连接参数无效，请从连接列表重新进入', requestId, isRouteInitCancelled)
      return
    }

    if (store.connections.length === 0) {
      try {
        await store.fetchConnections()
      } catch {
        if (isStaleRouteInit(requestId, isRouteInitCancelled)) return
        await redirectToConnections('加载连接列表失败，请稍后重试', requestId, isRouteInitCancelled)
        return
      }

      if (isStaleRouteInit(requestId, isRouteInitCancelled)) return
    }

    const targetConnection = store.getConnection(parsedId)
    if (!targetConnection) {
      if (isStaleRouteInit(requestId, isRouteInitCancelled)) return
      conn.value = undefined
      await redirectToConnections('连接不存在或已删除', requestId, isRouteInitCancelled)
      return
    }

    if (isStaleRouteInit(requestId, isRouteInitCancelled)) return
    conn.value = targetConnection
    sftpTabsStore.openOrFocus(targetConnection)

    // 只在切换到不同连接时初始化路径
    if (isConnectionChanged) {
      sftpTabsStore.setConnectionLoaded(parsedId)
      await initPath(parsedId, requestId, isRouteInitCancelled)
    }
  },
  { immediate: true }
)

async function redirectToConnections(
  message: string,
  requestId = routeInitRequestId,
  isCancelled?: () => boolean
) {
  if (isStaleRouteInit(requestId, isCancelled)) return
  toast.error(message)
  if (isStaleRouteInit(requestId, isCancelled)) return
  await router.replace('/connections')
}

async function initPath(
  targetConnectionId = connectionId.value,
  requestId = routeInitRequestId,
  isCancelled?: () => boolean
) {
  try {
    const res = await sftpApi.getPwd(targetConnectionId)
    if (isStaleRouteInit(requestId, isCancelled)) return

    const p = res.data.path || '/'
    currentPath.value = p === '/' ? '/' : p
    pathParts.value = p === '/' ? [''] : p.split('/').filter(Boolean)
    await loadPath(targetConnectionId, requestId, isCancelled)
  } catch (err) {
    if (isStaleRouteInit(requestId, isCancelled)) return

    const typedErr = err as { response?: { data?: { error?: string } } }
    error.value = typedErr?.response?.data?.error ?? '获取当前路径失败，请检查连接与认证'
    currentPath.value = '.'
    pathParts.value = []
    await loadPath(targetConnectionId, requestId, isCancelled)
  }
}

async function loadPath(
  targetConnectionId = connectionId.value,
  requestId = routeInitRequestId,
  isCancelled?: () => boolean
) {
  if (isStaleRouteInit(requestId, isCancelled)) return

  loading.value = true
  error.value = ''
  searchQuery.value = ''

  try {
    const res = await sftpApi.listFiles(targetConnectionId, currentPath.value)
    if (isStaleRouteInit(requestId, isCancelled)) return

    files.value = res.data.sort((a, b) => {
      if (a.directory !== b.directory) return a.directory ? -1 : 1
      return a.name.localeCompare(b.name)
    })
  } catch (err) {
    if (isStaleRouteInit(requestId, isCancelled)) return

    const typedErr = err as { response?: { data?: { error?: string } } }
    error.value = typedErr?.response?.data?.error ?? '获取文件列表失败'
  } finally {
    if (!isStaleRouteInit(requestId, isCancelled)) {
      loading.value = false
    }
  }
}

function navigateToDir(name: string) {
  if (loading.value) return
  const base = currentPath.value === '.' || currentPath.value === '' ? '' : currentPath.value
  currentPath.value = base ? (base.endsWith('/') ? base + name : base + '/' + name) : name
  pathParts.value = currentPath.value === '/' ? [''] : currentPath.value.split('/').filter(Boolean)
  loadPath()
}

function navigateToIndex(i: number) {
  if (loading.value) return
  if (i < 0) {
    currentPath.value = '.'
  } else {
    currentPath.value = pathParts.value.length ? '/' + pathParts.value.slice(0, i + 1).join('/') : '/'
  }
  pathParts.value = currentPath.value === '/' ? [''] : currentPath.value.split('/').filter(Boolean)
  loadPath()
}

function goUp() {
  if (loading.value) return
  if (currentPath.value === '.' || currentPath.value === '' || currentPath.value === '/') {
    return
  }
  const parts = currentPath.value.split('/').filter(Boolean)
  if (parts.length <= 1) {
    currentPath.value = '/'
    pathParts.value = ['']
  } else {
    parts.pop()
    currentPath.value = '/' + parts.join('/')
    pathParts.value = parts
  }
  loadPath()
}

function handleFileClick(file: SftpFileInfo) {
  if (file.directory) {
    navigateToDir(file.name)
  } else {
    selectedFile.value = file.name
  }
}

function handleDownload(file: SftpFileInfo) {
  if (file.directory) return
  const base = currentPath.value === '.' || !currentPath.value ? '' : currentPath.value
  const path = base ? base + '/' + file.name : file.name
  sftpApi.downloadFile(connectionId.value, path).catch(() => {
    error.value = '下载失败'
  })
}

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

async function handleFileSelect(e: Event) {
  const uploadContextId = createUploadContext()
  const isUploadStale = () => uploadContextId !== activeUploadContextId

  const input = e.target as HTMLInputElement
  const selected = input.files
  if (!selected?.length || isUploadStale()) return

  const targetConnectionId = connectionId.value
  uploading.value = true
  error.value = ''
  const path = currentPath.value === '.' ? '' : currentPath.value
  
  const uploadTasks: { id: string; file: File; taskId?: string }[] = []
  for (let i = 0; i < selected.length; i++) {
    const file = selected[i]
    if (!file) continue
    uploadTasks.push({ id: `${Date.now()}-${i}`, file })
  }
  
  uploadProgressList.value = uploadTasks.map(({ id, file }) => ({
    id,
    name: file.name,
    size: file.size,
    uploaded: 0,
    total: file.size,
    status: 'pending',
  }))

  showUploadProgress.value = true

  const MAX_PARALLEL = 5
  
  for (let i = 0; i < uploadTasks.length; i += MAX_PARALLEL) {
    if (isUploadStale()) return

    const batch = uploadTasks.slice(i, i + MAX_PARALLEL)
    const batchPromises = batch.map(async task => {
      if (!task || isUploadStale()) return

      const { id, file } = task
      const item = uploadProgressList.value.find(item => item.id === id)
      if (!item || isUploadStale()) return
      
      item.status = 'uploading'
      
      try {
        if (isUploadStale()) return

        // Start upload and get taskId
        const uploadRes = await sftpApi.uploadFile(targetConnectionId, path, file)
        if (isUploadStale()) return

        const taskId = uploadRes.data.taskId
        
        // Poll for progress
        while (true) {
          if (isUploadStale()) return

          const statusRes = await sftpApi.getUploadTask(taskId)
          if (isUploadStale()) return

          const taskStatus = statusRes.data
          
          item.uploaded = taskStatus.transferredBytes
          item.total = taskStatus.totalBytes
          
          if (taskStatus.status === 'success') {
            item.status = 'success'
            break
          }
          if (taskStatus.status === 'error') {
            item.status = 'error'
            item.message = taskStatus.error || 'Upload failed'
            break
          }

          await new Promise(resolve => setTimeout(resolve, 200))
        }
      } catch (err: any) {
        if (isUploadStale()) return

        item.status = 'error'
        item.message = err?.response?.data?.error || 'Upload failed'
      }
    })
    await Promise.allSettled(batchPromises)
  }

  if (isUploadStale()) return

  await loadPath(targetConnectionId)
  if (isUploadStale()) return

  const successCount = uploadProgressList.value.filter(item => item.status === 'success').length
  showUploadProgress.value = false
  uploadProgressList.value = []
  uploading.value = false
  if (fileInputRef.value) {
    fileInputRef.value.value = ''
  }

  if (isUploadStale()) return
  toast.success(`成功上传 ${successCount} 个文件`)
}

function handleMkdir() {
  const name = prompt('文件夹名称：')
  if (!name?.trim()) return
  const base = currentPath.value === '.' || !currentPath.value ? '' : currentPath.value
  const path = base ? base + '/' + name : name
  sftpApi.createDir(connectionId.value, path).then(() => loadPath()).catch(() => {
    error.value = '创建文件夹失败'
  })
}

function handleDelete(file: SftpFileInfo) {
  if (!confirm(`确定删除「${file.name}」？`)) return
  const base = currentPath.value === '.' || !currentPath.value ? '' : currentPath.value
  const path = base ? base + '/' + file.name : file.name
  sftpApi.deleteFile(connectionId.value, path, file.directory).then(() => loadPath()).catch(() => {
    error.value = '删除失败'
  })
}

const targetConnectionOptions = computed(() => {
  const list = store.connections.filter((c) => c.id !== connectionId.value)
  return list
})

async function openTransferModal(file: SftpFileInfo) {
  if (file.directory) return
  if (store.connections.length === 0) await store.fetchConnections()
  transferFile.value = file
  transferTargetConnectionId.value = targetConnectionOptions.value[0]?.id ?? null
  transferTargetPath.value = currentPath.value === '.' || !currentPath.value ? '/' : currentPath.value
  if (!transferTargetPath.value.endsWith('/')) transferTargetPath.value += '/'
  transferError.value = ''
  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 base = currentPath.value === '.' || !currentPath.value ? '' : currentPath.value
  const sourcePath = base ? base + '/' + file.name : 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(connectionId.value, sourcePath, targetId, targetPath)
    await waitForTransferTask(created.data.taskId)
    transferProgress.value = 100
    await loadPath()
    closeTransferModal()
  } catch (err: unknown) {
    const res = err as { response?: { data?: { error?: string } } }
    transferError.value = res?.response?.data?.error ?? (err as Error)?.message ?? '传输失败'
  } finally {
    stopTransferProgress()
    transferring.value = false
  }
}
</script>

<template>
  <div class="h-full flex flex-col">
    <div class="flex items-center gap-4 p-4 border-b border-slate-700 bg-slate-800/50">
      <button
        @click="router.push('/connections')"
        class="p-2 rounded-lg text-slate-400 hover:bg-slate-700 hover:text-slate-100 transition-colors duration-200 cursor-pointer"
        aria-label="返回"
      >
        <ArrowLeft class="w-5 h-5" aria-hidden="true" />
      </button>
      <h2 class="text-lg font-semibold text-slate-100">
        {{ conn?.name || '文件' }} - {{ conn?.username }}@{{ conn?.host }}
      </h2>
    </div>

    <div class="flex-1 overflow-auto p-4">
      <div class="bg-slate-800 rounded-xl border border-slate-700 overflow-hidden">
        <div class="flex flex-col sm:flex-row sm:items-center gap-2 p-3 border-b border-slate-700 bg-slate-800/80">
          <nav class="flex items-center gap-1 text-sm text-slate-400 min-w-0 w-full sm:flex-1">
            <button
              @click="navigateToIndex(-1)"
              class="px-2 py-1 rounded hover:bg-slate-700 hover:text-slate-100 transition-colors duration-200 cursor-pointer truncate"
            >
              /
            </button>
            <template v-for="(part, i) in pathParts" :key="i">
              <ChevronRight class="w-4 h-4 flex-shrink-0 text-slate-600" aria-hidden="true" />
              <button
                @click="navigateToIndex(i)"
                class="px-2 py-1 rounded hover:bg-slate-700 hover:text-slate-100 transition-colors duration-200 cursor-pointer truncate max-w-[120px]"
              >
                {{ part || '/' }}
              </button>
            </template>
          </nav>
          <div class="w-full sm:w-auto flex items-center gap-2 justify-end">
            <div class="flex-1 sm:flex-none">
              <input
                v-model="searchQuery"
                type="text"
                class="w-full sm:w-56 rounded-lg border border-slate-600 bg-slate-900/30 px-3 py-2 text-sm text-slate-100 placeholder-slate-500 focus:border-cyan-500 focus:outline-none focus:ring-1 focus:ring-cyan-500"
                placeholder="搜索文件..."
                aria-label="搜索文件"
              />
            </div>
            <button
              @click="showHiddenFiles = !showHiddenFiles"
              class="p-2 rounded-lg text-slate-400 hover:bg-slate-700 hover:text-slate-100 transition-colors duration-200 cursor-pointer"
              :aria-label="showHiddenFiles ? '隐藏隐藏文件' : '显示隐藏文件'"
              :title="showHiddenFiles ? '隐藏隐藏文件' : '显示隐藏文件'"
            >
              <component :is="showHiddenFiles ? EyeOff : Eye" class="w-4 h-4" aria-hidden="true" />
            </button>
            <button
              @click="triggerUpload"
              :disabled="uploading"
              class="p-2 rounded-lg text-slate-400 hover:bg-slate-700 hover:text-slate-100 transition-colors duration-200 cursor-pointer disabled:opacity-50"
              aria-label="上传"
            >
              <Upload class="w-4 h-4" aria-hidden="true" />
            </button>
            <button
              @click="handleMkdir"
              class="p-2 rounded-lg text-slate-400 hover:bg-slate-700 hover:text-slate-100 transition-colors duration-200 cursor-pointer"
              aria-label="新建文件夹"
            >
              <FolderPlus class="w-4 h-4" aria-hidden="true" />
            </button>
            <button
              @click="loadPath()"
              :disabled="loading"
              class="p-2 rounded-lg text-slate-400 hover:bg-slate-700 hover:text-slate-100 transition-colors duration-200 cursor-pointer disabled:opacity-50"
              aria-label="刷新"
            >
              <RefreshCw class="w-4 h-4" :class="{ 'animate-spin': loading }" aria-hidden="true" />
            </button>
          </div>
          <input
            ref="fileInputRef"
            type="file"
            multiple
            class="hidden"
            @change="handleFileSelect"
          />
        </div>

        <div v-if="showUploadProgress" class="bg-slate-800/50 border-b border-slate-700 p-4 space-y-3">
          <div class="flex items-center justify-between">
            <span class="text-sm text-slate-300">上传进度: {{ totalProgress }}%</span>
            <span class="text-sm text-slate-400">{{ currentUploadingFile || '准备上传...' }}</span>
          </div>
          <div class="w-full bg-slate-700 rounded-full h-2 overflow-hidden">
            <div 
              class="bg-cyan-600 h-full transition-all duration-300" 
              :style="{ width: totalProgress + '%' }"
            ></div>
          </div>
          <div class="grid grid-cols-1 gap-2 max-h-40 overflow-y-auto">
            <div 
              v-for="item in uploadProgressList" 
              :key="item.id" 
              class="flex items-center gap-3 text-sm"
            >
              <CheckCircle v-if="item.status === 'success'" class="w-4 h-4 flex-shrink-0 text-green-500" aria-hidden="true" />
              <AlertCircle v-else-if="item.status === 'error'" class="w-4 h-4 flex-shrink-0 text-red-500" aria-hidden="true" />
              <Loader v-else-if="item.status === 'uploading'" class="w-4 h-4 flex-shrink-0 text-cyan-500 animate-spin" aria-hidden="true" />
              <File v-else class="w-4 h-4 flex-shrink-0 text-slate-500" aria-hidden="true" />
              <span class="flex-1 truncate text-slate-300">{{ item.name }}</span>
              <span class="text-slate-400 text-xs">
                {{ formatSize(item.size) }}
                <template v-if="item.status === 'uploading'">
                  ({{ Math.round((item.uploaded / item.total) * 100) }}%)
                </template>
                <template v-else-if="item.status === 'success'">
                  ✓
                </template>
              </span>
            </div>
          </div>
        </div>

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

        <div v-if="loading" class="p-8 text-center text-slate-400">
          加载中...
        </div>

        <div v-else class="divide-y divide-slate-700">
          <button
            v-if="currentPath !== '.' && pathParts.length > 1"
            @click="goUp"
            class="w-full flex items-center gap-3 px-4 py-3 hover:bg-slate-700/50 transition-colors duration-200 cursor-pointer text-left"
          >
            <FolderOpen class="w-5 h-5 text-slate-500" aria-hidden="true" />
            <span class="text-slate-400">..</span>
          </button>
          <button
            v-for="file in filteredFiles"
            :key="file.name"
            @click="handleFileClick(file)"
            @dblclick="!file.directory && handleDownload(file)"
            class="w-full flex items-center gap-3 px-4 py-3 hover:bg-slate-700/50 transition-colors duration-200 cursor-pointer text-left group"
          >
            <component
              :is="file.directory ? FolderOpen : File"
              class="w-5 h-5 flex-shrink-0 text-slate-400"
              aria-hidden="true"
            />
            <span class="flex-1 truncate text-slate-200">{{ file.name }}</span>
            <span v-if="!file.directory" class="text-sm text-slate-500 flex-shrink-0">
              {{ formatSize(file.size) }}
            </span>
            <span class="text-sm text-slate-500 flex-shrink-0">{{ formatDate(file.mtime) }}</span>
            <div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
              <button
                v-if="!file.directory"
                @click.stop="handleDownload(file)"
                class="p-1.5 rounded text-slate-400 hover:bg-slate-600 hover:text-cyan-400 transition-colors duration-200 cursor-pointer"
                aria-label="下载"
              >
                <Download class="w-4 h-4" aria-hidden="true" />
              </button>
              <button
                v-if="!file.directory"
                @click.stop="openTransferModal(file)"
                class="p-1.5 rounded text-slate-400 hover:bg-slate-600 hover:text-cyan-400 transition-colors duration-200 cursor-pointer"
                aria-label="复制到远程"
              >
                <Copy class="w-4 h-4" aria-hidden="true" />
              </button>
              <button
                @click.stop="handleDelete(file)"
                class="p-1.5 rounded text-slate-400 hover:bg-red-900/50 hover:text-red-400 transition-colors duration-200 cursor-pointer"
                aria-label="删除"
              >
                <Trash2 class="w-4 h-4" aria-hidden="true" />
              </button>
            </div>
          </button>
          <div v-if="filteredFiles.length === 0 && !loading" class="p-8 text-center text-slate-500">
            {{ files.length === 0 ? '空目录' : (searchQuery.trim() ? '未找到匹配文件' : '无可见文件（已隐藏文件）') }}
          </div>
        </div>
      </div>
    </div>

    <Teleport to="body">
      <div
        v-if="showTransferModal"
        class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4"
        role="dialog"
        aria-modal="true"
        aria-labelledby="transfer-modal-title"
      >
        <div class="w-full max-w-md rounded-xl border border-slate-700 bg-slate-800 p-5 shadow-xl">
          <h3 id="transfer-modal-title" class="text-lg font-semibold text-slate-100 mb-4">
            复制到远程
          </h3>
          <p v-if="transferFile" class="text-sm text-slate-400 mb-3">
            文件：{{ transferFile.name }}
          </p>
          <div class="space-y-3">
            <div>
              <label for="transfer-target-conn" class="block text-sm text-slate-400 mb-1">目标连接</label>
              <select
                id="transfer-target-conn"
                v-model.number="transferTargetConnectionId"
                class="w-full rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-slate-100 focus:border-cyan-500 focus:outline-none focus:ring-1 focus:ring-cyan-500"
              >
                <option v-for="c in targetConnectionOptions" :key="c.id" :value="c.id">
                  {{ c.name }} ({{ c.username }}@{{ c.host }})
                </option>
              </select>
              <p v-if="targetConnectionOptions.length === 0" class="mt-1 text-xs text-amber-400">
                暂无其他连接，请先在连接管理中添加
              </p>
            </div>
            <div>
              <label for="transfer-target-path" class="block text-sm text-slate-400 mb-1">目标路径（目录以 / 结尾）</label>
              <input
                id="transfer-target-path"
                v-model="transferTargetPath"
                type="text"
                class="w-full rounded-lg border border-slate-600 bg-slate-700 px-3 py-2 text-slate-100 placeholder-slate-500 focus:border-cyan-500 focus:outline-none focus:ring-1 focus:ring-cyan-500"
                placeholder="/"
              />
            </div>
          </div>
          <p v-if="transferError" class="mt-3 text-sm text-red-400">{{ transferError }}</p>
          <div v-if="transferring" class="mt-3 space-y-2">
            <div class="flex items-center justify-between text-xs text-slate-400">
              <span>传输进度</span>
              <span>{{ transferProgress }}%</span>
            </div>
            <div class="h-2 overflow-hidden rounded-full bg-slate-700">
              <div
                class="h-full bg-cyan-500 transition-all duration-300"
                :style="{ width: transferProgress + '%' }"
              ></div>
            </div>
            <div class="flex items-center justify-between text-[11px] text-slate-500">
              <span>{{ formatTransferBytes(transferTransferredBytes) }}</span>
              <span>{{ transferTotalBytes > 0 ? formatTransferBytes(transferTotalBytes) : '--' }}</span>
            </div>
          </div>
          <div class="mt-5 flex justify-end gap-2">
            <button
              type="button"
              @click="transferring ? cancelTransfer() : closeTransferModal()"
              class="rounded-lg border border-slate-600 px-4 py-2 text-slate-300 hover:bg-slate-700 cursor-pointer"
            >
              {{ transferring ? '取消传输' : '取消' }}
            </button>
            <button
              type="button"
              @click="submitTransfer"
              :disabled="transferring || transferTargetConnectionId == null"
              class="rounded-lg bg-cyan-600 px-4 py-2 text-white hover:bg-cyan-500 disabled:opacity-50 cursor-pointer"
            >
              {{ transferring ? '传输中...' : '确定' }}
            </button>
          </div>
        </div>
      </div>
    </Teleport>
  </div>
</template>
