import { computed, nextTick, ref, type Ref } from 'vue'
import { storeToRefs } from 'pinia'
import { http } from '@/api'
import { useFilesClipboard } from '@/composables/useFilesClipboard'
import { useSnackbar } from '@/composables/useSnackbar'
import { useFilesStore } from '@/stores/files'
import type { FileEntry } from '@/types/api'
import { isArchiveFile } from '@/utils/fileSort'
import { saveBlobAsFile } from '@/utils/fileDownload'
import { invalidateCachedFile } from '@/utils/filePreload'
import {
  joinRemotePath,
  normalizeRemotePath,
  validatePathSegment,
} from '@/utils/remotePath'
import type { OpenFilePayload } from './useFilesEditor'
import type { useFilesSelection } from './useFilesSelection'

type SelectionApi = ReturnType<typeof useFilesSelection>

export function useFilesActions(options: {
  browse: (opts?: { force?: boolean }) => Promise<void | undefined>
  invalidateAfterMutation: (parentPath?: string) => void
  selection: SelectionApi
  openEntry: (item: FileEntry) => void
  editFile: (item: FileEntry) => Promise<void>
  previewFile: (item: FileEntry) => Promise<void>
  openInTerminal: () => void
  editorWorkbench: Ref<{ openFile: (p: OpenFilePayload) => void } | null>
  actionLoading?: Ref<boolean>
}) {
  const snackbar = useSnackbar()
  const store = useFilesStore()
  const { selectedServer, currentPath } = storeToRefs(store)
  const { selectedFiles, selectedAbsolutePaths, clearSelection } = options.selection
  const {
    setFromSelection,
    canPasteOnServer,
    pasteToDirectory,
  } = useFilesClipboard()

  const dropActive = ref(false)
  const showUpload = ref(false)
  const showNewFile = ref(false)
  const newFileName = ref('')
  const showMkdir = ref(false)
  const showCompress = ref(false)
  const compressDest = ref('')
  const compressFormat = ref<'tar.gz' | 'zip'>('tar.gz')
  const compressPaths = ref<string[]>([])
  const showDecompress = ref(false)
  const decompressSource = ref('')
  const decompressDest = ref('.')
  const showFileDelete = ref(false)
  const uploadFiles = ref<File[]>([])
  const uploading = ref(false)
  const mkdirName = ref('')
  const actionLoading = options.actionLoading ?? ref(false)
  const deletingFile = ref<FileEntry | null>(null)
  const showRename = ref(false)
  const newName = ref('')
  const renamingFile = ref<FileEntry | null>(null)
  const showChmod = ref(false)
  const chmodLoading = ref(false)
  const chmodFile = ref<FileEntry | null>(null)
  const showContextMenu = ref(false)
  const contextX = ref(0)
  const contextY = ref(0)
  const contextFile = ref<FileEntry | null>(null)

  function resolvePasteDestination(explicitDest?: string): string {
    if (explicitDest) return normalizeRemotePath(explicitDest)
    const dirs = selectedFiles.value.filter((f) => f.type === 'directory')
    if (dirs.length === 1 && selectedFiles.value.length === 1) {
      return joinRemotePath(currentPath.value, dirs[0].name)
    }
    return normalizeRemotePath(currentPath.value)
  }

  const pasteDestLabel = computed(() => {
    const dest = resolvePasteDestination()
    const cur = normalizeRemotePath(currentPath.value)
    return dest === cur ? '当前目录' : dest
  })

  function copyToClipboard(items?: FileEntry[]) {
    if (!selectedServer.value) return
    const paths = selectedAbsolutePaths(items)
    if (!paths.length) {
      snackbar('请先选择文件或目录', 'warning')
      return
    }
    if (setFromSelection('copy', selectedServer.value, paths)) {
      snackbar(`已复制 ${paths.length} 项`)
    }
  }

  function cutToClipboard(items?: FileEntry[]) {
    if (!selectedServer.value) return
    const paths = selectedAbsolutePaths(items)
    if (!paths.length) {
      snackbar('请先选择文件或目录', 'warning')
      return
    }
    if (setFromSelection('cut', selectedServer.value, paths)) {
      snackbar(`已剪切 ${paths.length} 项`)
    }
  }

  async function pasteClipboard(explicitDest?: string) {
    if (!selectedServer.value) {
      snackbar('请先选择服务器', 'warning')
      return
    }
    if (!canPasteOnServer(selectedServer.value)) {
      snackbar('剪贴板为空或服务器不一致', 'warning')
      return
    }
    const targetDir = resolvePasteDestination(explicitDest)
    actionLoading.value = true
    try {
      await pasteToDirectory(targetDir)
      clearSelection()
      options.invalidateAfterMutation(targetDir)
      await options.browse({ force: true })
      snackbar(`已粘贴到 ${targetDir}`)
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : '粘贴失败'
      snackbar(msg, 'error')
    } finally {
      actionLoading.value = false
    }
  }

  async function onDropFiles(e: DragEvent) {
    dropActive.value = false
    if (!selectedServer.value) {
      snackbar('请先选择服务器', 'warning')
      return
    }
    const dropped = e.dataTransfer?.files
    if (!dropped?.length) return
    uploadFiles.value = Array.from(dropped)
    await doUpload()
  }

  function contextAction(action: string) {
    showContextMenu.value = false
    if (!contextFile.value) return
    const item = contextFile.value
    switch (action) {
      case 'open':
        options.openEntry(item)
        break
      case 'copy':
        copyToClipboard([item])
        break
      case 'cut':
        cutToClipboard([item])
        break
      case 'paste':
        void pasteClipboard()
        break
      case 'pasteInto':
        if (item.type === 'directory') {
          void pasteClipboard(joinRemotePath(currentPath.value, item.name))
        }
        break
      case 'edit':
        void options.editFile(item)
        break
      case 'preview':
        void options.previewFile(item)
        break
      case 'download':
        void downloadFile(item)
        break
      case 'copyPath':
        void copyRemotePath(item)
        break
      case 'terminal':
        options.openInTerminal()
        break
      case 'compress':
        openCompressDialog([item])
        break
      case 'decompress':
        openDecompressDialog(item)
        break
      case 'rename':
        startRename(item)
        break
      case 'chmod':
        startChmod(item)
        break
      case 'delete':
        void confirmDeleteFile(item)
        break
    }
  }

  async function copyRemotePath(item: FileEntry) {
    const path = joinRemotePath(currentPath.value, item.name)
    try {
      await navigator.clipboard.writeText(path)
      snackbar('路径已复制')
    } catch {
      snackbar('复制失败', 'error')
    }
  }

  function openCompressDialog(items?: FileEntry[]) {
    if (!selectedServer.value) return
    const targets = items?.length
      ? items
      : selectedFiles.value.length
        ? selectedFiles.value
        : []
    if (!targets.length) {
      snackbar('请选择要压缩的文件或目录', 'warning')
      return
    }
    compressPaths.value = targets.map((f) =>
      joinRemotePath(currentPath.value, f.name),
    )
    const base =
      targets.length === 1 ? targets[0].name.replace(/\.[^.]+$/, '') : 'archive'
    compressDest.value = joinRemotePath(currentPath.value, `${base}.tar.gz`)
    compressFormat.value = 'tar.gz'
    showCompress.value = true
  }

  function openDecompressDialog(item: FileEntry) {
    if (!selectedServer.value) return
    if (!isArchiveFile(item.name)) {
      snackbar('仅支持 .tar.gz / .tgz / .zip', 'warning')
      return
    }
    decompressSource.value = joinRemotePath(currentPath.value, item.name)
    decompressDest.value = currentPath.value
    showDecompress.value = true
  }

  async function doCompress() {
    if (!selectedServer.value || !compressPaths.value.length || !compressDest.value) {
      return
    }
    actionLoading.value = true
    try {
      let dest = compressDest.value
      if (compressFormat.value === 'zip' && !dest.endsWith('.zip')) {
        dest = dest.replace(/\.tar\.gz$/i, '.zip')
        if (!dest.endsWith('.zip')) dest += '.zip'
      }
      await http.post('/sync/compress', {
        server_id: selectedServer.value,
        paths: compressPaths.value,
        dest,
        format: compressFormat.value,
      })
      showCompress.value = false
      clearSelection()
      options.invalidateAfterMutation()
      await options.browse({ force: true })
      snackbar('压缩完成')
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : '压缩失败'
      snackbar(msg, 'error')
    } finally {
      actionLoading.value = false
    }
  }

  async function doDecompress() {
    if (!selectedServer.value || !decompressSource.value) return
    actionLoading.value = true
    try {
      await http.post('/sync/decompress', {
        server_id: selectedServer.value,
        path: decompressSource.value,
        dest: decompressDest.value || '.',
      })
      showDecompress.value = false
      options.invalidateAfterMutation()
      await options.browse({ force: true })
      snackbar('解压完成')
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : '解压失败'
      snackbar(msg, 'error')
    } finally {
      actionLoading.value = false
    }
  }

  async function doNewFile() {
    if (!selectedServer.value || !newFileName.value) return
    const check = validatePathSegment(newFileName.value)
    if (check !== true) {
      snackbar(check, 'warning')
      return
    }
    const name = newFileName.value
    const path = joinRemotePath(currentPath.value, name)
    actionLoading.value = true
    try {
      await http.post('/sync/write-file', {
        server_id: selectedServer.value,
        path,
        content: '',
      })
      showNewFile.value = false
      newFileName.value = ''
      invalidateCachedFile(selectedServer.value, path)
      options.invalidateAfterMutation()
      await options.browse({ force: true })
      snackbar('文件已创建')
      await nextTick()
      options.editorWorkbench.value?.openFile({
        path,
        name,
        content: '',
        eol: 'LF',
        etag: null,
        mtime: null,
      })
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : '创建失败'
      snackbar(msg, 'error')
    } finally {
      actionLoading.value = false
    }
  }

  function startRename(item: FileEntry) {
    renamingFile.value = item
    newName.value = item.name
    showRename.value = true
  }

  async function doRename() {
    if (!selectedServer.value || !renamingFile.value || !newName.value) return
    try {
      await http.post('/sync/file-ops', {
        server_id: selectedServer.value,
        operation: 'rename',
        path: joinRemotePath(currentPath.value, renamingFile.value.name),
        new_path: joinRemotePath(currentPath.value, newName.value),
      })
      showRename.value = false
      options.invalidateAfterMutation()
      await options.browse({ force: true })
      snackbar('重命名成功')
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : '重命名失败'
      snackbar(msg, 'error')
    }
  }

  function startChmod(item: FileEntry) {
    chmodFile.value = item
    showChmod.value = true
  }

  async function doChmod(payload: {
    mode: string
    owner?: string
    group?: string
    recursive?: boolean
  }) {
    if (!selectedServer.value || !chmodFile.value) return
    chmodLoading.value = true
    try {
      await http.post('/sync/chmod', {
        server_id: selectedServer.value,
        path: joinRemotePath(currentPath.value, chmodFile.value.name),
        mode: payload.mode,
        owner: payload.owner,
        group: payload.group,
        recursive: payload.recursive ?? false,
      })
      showChmod.value = false
      options.invalidateAfterMutation()
      await options.browse({ force: true })
      snackbar('权限已修改')
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : '修改权限失败'
      snackbar(msg, 'error')
    } finally {
      chmodLoading.value = false
    }
  }

  async function batchDelete() {
    if (!selectedServer.value || !selectedFiles.value.length) return
    if (!confirm(`确定删除 ${selectedFiles.value.length} 个文件？`)) return
    let ok = 0
    let fail = 0
    for (const f of selectedFiles.value) {
      try {
        await http.post('/sync/file-ops', {
          server_id: selectedServer.value,
          operation: 'delete',
          path: joinRemotePath(currentPath.value, f.name),
        })
        ok++
      } catch {
        fail++
      }
    }
    clearSelection()
    options.invalidateAfterMutation()
    await options.browse({ force: true })
    if (fail === 0) snackbar(`已删除 ${ok} 项`)
    else snackbar(`删除完成：成功 ${ok}，失败 ${fail}`, fail > 0 ? 'warning' : 'success')
  }

  async function downloadFile(item: FileEntry) {
    if (!selectedServer.value) return
    try {
      const remotePath = joinRemotePath(currentPath.value, item.name)
      const { blob, filename } = await http.download('/sync/download', {
        server_id: selectedServer.value,
        path: remotePath,
      })
      saveBlobAsFile(blob, filename)
      snackbar(`已下载 ${filename}`)
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : '下载失败'
      snackbar(msg, 'error')
    }
  }

  async function confirmDeleteFile(item: FileEntry) {
    deletingFile.value = item
    showFileDelete.value = true
  }

  async function doDeleteFile() {
    if (!selectedServer.value || !deletingFile.value) return
    actionLoading.value = true
    try {
      await http.post('/sync/file-ops', {
        server_id: selectedServer.value,
        operation: 'delete',
        path: joinRemotePath(currentPath.value, deletingFile.value.name),
      })
      showFileDelete.value = false
      options.invalidateAfterMutation()
      await options.browse({ force: true })
      snackbar('已删除')
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : '删除失败'
      snackbar(msg, 'error')
    } finally {
      actionLoading.value = false
    }
  }

  async function doMkdir() {
    if (!selectedServer.value || !mkdirName.value) return
    const check = validatePathSegment(mkdirName.value)
    if (check !== true) {
      snackbar(check, 'warning')
      return
    }
    actionLoading.value = true
    try {
      await http.post('/sync/file-ops', {
        server_id: selectedServer.value,
        operation: 'mkdir',
        path: joinRemotePath(currentPath.value, mkdirName.value),
      })
      showMkdir.value = false
      mkdirName.value = ''
      options.invalidateAfterMutation()
      await options.browse({ force: true })
      snackbar('目录已创建')
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : '创建失败'
      snackbar(msg, 'error')
    } finally {
      actionLoading.value = false
    }
  }

  async function doUpload() {
    if (!selectedServer.value || !uploadFiles.value.length) return
    uploading.value = true
    try {
      const form = new FormData()
      for (const f of uploadFiles.value) form.append('files', f)
      form.append('server_id', String(selectedServer.value))
      form.append('remote_path', currentPath.value)
      await http.upload('/sync/upload', form)
      showUpload.value = false
      uploadFiles.value = []
      options.invalidateAfterMutation()
      await options.browse({ force: true })
      snackbar('上传成功')
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : '上传失败'
      snackbar(msg, 'error')
    } finally {
      uploading.value = false
    }
  }

  function isRowActionTarget(target: EventTarget | null): boolean {
    if (!(target instanceof HTMLElement)) return false
    return Boolean(target.closest('button, a, input, .v-checkbox, .v-selection-control'))
  }

  function fileRowProps(data: { item: FileEntry }) {
    const item = data.item
    return {
      class: item.type === 'directory' ? 'files-row--dir' : undefined,
      style: {
        cursor:
          item.type === 'directory' || item.type === 'symlink' ? 'pointer' : 'default',
      },
      onClick: (e: MouseEvent) => {
        if (isRowActionTarget(e.target)) return
        if (e.detail !== 1) return
        onRowClick(e, { item })
      },
      onDblclick: (e: MouseEvent) => {
        if (isRowActionTarget(e.target)) return
        onRowDblClick(e, { item })
      },
      onContextmenu: (e: MouseEvent) => {
        if (isRowActionTarget(e.target)) return
        onRowContext(e, { item })
      },
    }
  }

  function onRowClick(_event: Event, { item }: { item: FileEntry }) {
    if (item.type === 'directory' || item.type === 'symlink') {
      options.openEntry(item)
    }
  }

  function onRowDblClick(_event: Event, { item }: { item: FileEntry }) {
    if (item.type === 'file') {
      void options.editFile(item)
    }
  }

  function onRowContext(e: MouseEvent, { item }: { item: FileEntry }) {
    e.preventDefault()
    contextFile.value = item
    contextX.value = e.clientX
    contextY.value = e.clientY
    showContextMenu.value = true
  }

  return {
    dropActive,
    showUpload,
    showNewFile,
    newFileName,
    showMkdir,
    showCompress,
    compressDest,
    compressFormat,
    compressPaths,
    showDecompress,
    decompressSource,
    decompressDest,
    showFileDelete,
    uploadFiles,
    uploading,
    mkdirName,
    actionLoading,
    deletingFile,
    showRename,
    newName,
    renamingFile,
    showChmod,
    chmodLoading,
    chmodFile,
    showContextMenu,
    contextX,
    contextY,
    contextFile,
    copyToClipboard,
    cutToClipboard,
    pasteClipboard,
    onDropFiles,
    contextAction,
    openCompressDialog,
    openDecompressDialog,
    doCompress,
    doDecompress,
    doNewFile,
    startRename,
    doRename,
    startChmod,
    doChmod,
    batchDelete,
    downloadFile,
    confirmDeleteFile,
    doDeleteFile,
    doMkdir,
    doUpload,
    fileRowProps,
    pasteDestLabel,
  }
}
