<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'

import * as sftpApi from '../api/sftp'
import type { SftpFileInfo } from '../api/sftp'
import { X, FolderOpen, File, ChevronRight, RefreshCw, Eye, EyeOff } from 'lucide-vue-next'

const props = defineProps<{ open: boolean; connectionId: number | null }>()
const emit = defineEmits<{
  (e: 'close'): void
  (e: 'select', path: string): void
}>()

const currentPath = ref('.')
const pathParts = ref<string[]>([])
const files = ref<SftpFileInfo[]>([])
const loading = ref(false)
const error = ref('')

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

const canInteract = computed(() => props.open && props.connectionId != null)

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

async function initPath() {
  if (!canInteract.value || props.connectionId == null) return
  error.value = ''
  try {
    const res = await sftpApi.getPwd(props.connectionId)
    const p = res.data.path || '/'
    currentPath.value = p === '/' ? '/' : p
    pathParts.value = p === '/' ? [''] : p.split('/').filter(Boolean)
  } catch (e: unknown) {
    currentPath.value = '.'
    pathParts.value = []
  }
}

async function load() {
  if (!canInteract.value || props.connectionId == null) return
  loading.value = true
  error.value = ''
  try {
    const res = await sftpApi.listFiles(props.connectionId, currentPath.value)
    files.value = res.data
      .slice()
      .sort((a, b) => {
        if (a.directory !== b.directory) return a.directory ? -1 : 1
        return a.name.localeCompare(b.name)
      })
  } catch (e: unknown) {
    const err = e as { response?: { data?: { error?: string } } }
    error.value = err?.response?.data?.error ?? '获取文件列表失败'
  } finally {
    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)
  load()
}

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

function filePath(file: SftpFileInfo) {
  const base = currentPath.value === '.' || !currentPath.value ? '' : currentPath.value
  return base ? base.replace(/\/$/, '') + '/' + file.name : file.name
}

function handleClick(file: SftpFileInfo) {
  if (file.directory) {
    navigateToDir(file.name)
    return
  }
  emit('select', filePath(file))
  emit('close')
}

watch(
  () => [props.open, props.connectionId] as const,
  async ([open]) => {
    if (!open) return
    await initPath()
    await load()
  }
)

function onKeyDown(e: KeyboardEvent) {
  if (!props.open) return
  if (e.key === 'Escape') emit('close')
}

onMounted(() => window.addEventListener('keydown', onKeyDown))
onBeforeUnmount(() => {
  window.removeEventListener('keydown', onKeyDown)
  clearTimeout(searchDebounceTimer)
})
</script>

<template>
  <Teleport to="body">
    <div v-if="open" class="fixed inset-0 z-50 bg-black/60 p-4 flex items-center justify-center" role="dialog" aria-modal="true">
      <div class="w-full max-w-3xl rounded-2xl border border-slate-700 bg-slate-900/70 backdrop-blur shadow-2xl overflow-hidden">
        <div class="flex items-center justify-between px-4 py-3 border-b border-slate-700">
          <div class="min-w-0">
            <h3 class="text-slate-100 font-semibold truncate">选择源文件</h3>
            <p class="text-xs text-slate-400 truncate">双击文件不需要，单击即选择</p>
          </div>
          <div class="flex items-center gap-2">
            <button
              @click="load"
              :disabled="loading || !canInteract"
              class="min-h-[44px] px-3 rounded-lg border border-slate-700 bg-slate-800/60 text-slate-200 hover:bg-slate-800 disabled:opacity-50 cursor-pointer transition-colors"
              aria-label="刷新"
            >
              <span class="inline-flex items-center gap-2">
                <RefreshCw class="w-4 h-4" :class="{ 'animate-spin': loading }" aria-hidden="true" />
                刷新
              </span>
            </button>
            <button
              @click="emit('close')"
              class="min-h-[44px] w-11 grid place-items-center rounded-lg border border-slate-700 bg-slate-800/60 text-slate-200 hover:bg-slate-800 cursor-pointer transition-colors"
              aria-label="关闭"
            >
              <X class="w-5 h-5" aria-hidden="true" />
            </button>
          </div>
        </div>

        <div class="px-4 py-3 border-b border-slate-700 bg-slate-900/40">
          <nav class="flex items-center gap-1 text-sm text-slate-400 min-w-0">
            <button
              @click="navigateToIndex(-1)"
              class="px-2 py-1 rounded hover:bg-slate-800 hover:text-slate-100 transition-colors 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-800 hover:text-slate-100 transition-colors cursor-pointer truncate max-w-[140px]"
              >
                {{ part || '/' }}
              </button>
            </template>
          </nav>
          <div class="mt-3 flex items-center gap-2">
            <input
              v-model="searchQuery"
              type="text"
              class="flex-1 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="搜索文件"
            />
            <button
              @click="showHiddenFiles = !showHiddenFiles"
              class="min-h-[44px] p-2 rounded-lg text-slate-400 hover:bg-slate-700 hover:text-slate-100 transition-colors cursor-pointer"
              :aria-label="showHiddenFiles ? '隐藏隐藏文件' : '显示隐藏文件'"
              :title="showHiddenFiles ? '隐藏隐藏文件' : '显示隐藏文件'"
            >
              <component :is="showHiddenFiles ? EyeOff : Eye" class="w-4 h-4" aria-hidden="true" />
            </button>
          </div>
          <p v-if="error" class="mt-2 text-sm text-red-400">{{ error }}</p>
        </div>

        <div class="max-h-[60vh] overflow-auto divide-y divide-slate-800">
          <button
            v-for="file in filteredFiles"
            :key="file.name"
            @click="handleClick(file)"
            class="w-full flex items-center gap-3 px-4 py-3 text-left hover:bg-slate-800/40 transition-colors cursor-pointer min-h-[44px]"
            :aria-label="file.directory ? '打开目录' : '选择文件'"
          >
            <component
              :is="file.directory ? FolderOpen : File"
              class="w-5 h-5 flex-shrink-0"
              :class="file.directory ? 'text-cyan-300' : 'text-slate-300'"
              aria-hidden="true"
            />
            <span class="flex-1 min-w-0 truncate text-slate-100">{{ file.name }}</span>
            <span v-if="!file.directory" class="text-xs text-slate-500">{{ Math.round(file.size / 1024) }} KB</span>
          </button>

          <div v-if="filteredFiles.length === 0 && !loading" class="px-4 py-10 text-center text-slate-500">
            {{ files.length === 0 ? '空目录' : (searchQuery.trim() ? '未找到匹配文件' : '无可见文件（已隐藏文件）') }}
          </div>
        </div>
      </div>
    </div>
  </Teleport>
</template>
