import { computed, type Ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useRoute, useRouter } from 'vue-router'
import { ApiError, fetchJson, http } from '@/api'
import { useSnackbar } from '@/composables/useSnackbar'
import { useFilesStore } from '@/stores/files'
import type { BrowseResponse, FileEntry } from '@/types/api'
import { formatApiError } from '@/utils/apiError'
import { parseBrowseResponse } from '@/utils/fileBrowse'
import { preloadMonacoCore } from '@/monaco/preloadMonaco'
import {
  clearFilePreloadCache,
  preloadDirectoryFiles,
  pruneFilePreloadCache,
} from '@/utils/filePreload'
import { DEFAULT_FILES_ROOT, normalizeRemotePath, parentRemotePath } from '@/utils/remotePath'

const LOADING_DEBOUNCE_MS = 200

let _browseSeq = 0
let _browseInFlight: Promise<void> | null = null
let _browseInFlightKey = ''
let _loadingDebounceTimer: ReturnType<typeof setTimeout> | null = null

export function isNotFoundError(err: string): boolean {
  return err.includes('No such file or directory') || err.includes('not a directory')
}

export function friendlyBrowseError(err: string, path: string): string {
  if (isNotFoundError(err)) {
    return `路径不存在：${path}，请检查服务器「target_path」设置`
  }
  if (err.includes('Permission denied') || err.includes('Operation not permitted')) {
    return `无读取权限（${path}）`
  }
  if (err.includes('connection_error') || err.includes('SSH pool at capacity')) {
    return `SSH 连接失败或连接池繁忙，请稍后点「刷新」重试`
  }
  if (err.includes('timed out') || err.includes('timeout')) {
    return `远程命令超时（${path}），请检查目标机负载或网络`
  }
  return err
}

function beginLoading(store: ReturnType<typeof useFilesStore>) {
  store.loading = true
  if (_loadingDebounceTimer) return
  _loadingDebounceTimer = setTimeout(() => {
    _loadingDebounceTimer = null
    if (store.loading) {
      store.loadingVisible = true
    }
  }, LOADING_DEBOUNCE_MS)
}

function endLoading(store: ReturnType<typeof useFilesStore>) {
  store.loading = false
  if (_loadingDebounceTimer) {
    clearTimeout(_loadingDebounceTimer)
    _loadingDebounceTimer = null
  }
  store.loadingVisible = false
}

type BrowseFetchResult = {
  status: number
  data?: BrowseResponse | FileEntry[]
  etag: string | null
}

/** GET + ETag；失败时回退 POST /sync/browse（兼容旧网关/部署） */
async function fetchBrowseListing(
  serverId: number,
  path: string,
  ifNoneMatch?: string,
): Promise<BrowseFetchResult> {
  const headers: Record<string, string> = {}
  if (ifNoneMatch) {
    headers['If-None-Match'] = ifNoneMatch
  }
  try {
    return await fetchJson<BrowseResponse | FileEntry[]>('/files/browse', {
      method: 'GET',
      params: { server_id: serverId, path },
      headers,
    })
  } catch (e) {
    const retry =
      e instanceof ApiError
      && (e.status >= 500 || e.status === 404 || e.status === 405)
    if (!retry) throw e
    const data = await http.post<BrowseResponse | FileEntry[]>('/sync/browse', {
      server_id: serverId,
      path,
    })
    return { status: 200, data, etag: null }
  }
}

export function useFilesBrowse(fileFilter?: Ref<string>) {
  const snackbar = useSnackbar()
  const route = useRoute()
  const router = useRouter()
  const store = useFilesStore()
  const {
    selectedServer,
    currentPath,
    files,
    loading,
    loadingVisible,
    browseError,
    pageReady,
    filesCapability,
  } = storeToRefs(store)

  function syncRouteQuery() {
    const query: Record<string, string | undefined> = {
      ...(route.query as Record<string, string>),
    }
    if (selectedServer.value) {
      query.server_id = String(selectedServer.value)
      query.path =
        currentPath.value === DEFAULT_FILES_ROOT ? undefined : currentPath.value
    } else {
      delete query.server_id
      delete query.path
    }
    router.replace({ query })
  }

  const emptyHint = computed(() => {
    if (!pageReady.value) return '正在加载…'
    if (!selectedServer.value) return '请先选择服务器'
    if (browseError.value) return browseError.value
    if (fileFilter?.value.trim()) return '无匹配文件'
    const cap = filesCapability.value
    if (cap?.is_root) {
      return `目录为空（${currentPath.value}），可在上方修改路径或到「服务器」核对 target_path`
    }
    return `目录为空（${currentPath.value}）`
  })

  function applyBrowsePayload(
    serverId: number,
    path: string,
    res: BrowseResponse | FileEntry[] | undefined,
    etag: string | null,
    seq: number,
  ): boolean {
    if (seq !== _browseSeq) return false

    const { items, error } = parseBrowseResponse(res ?? { items: [] })

    if (error && isNotFoundError(error) && path !== '/') {
      browseError.value = ''
      const parent = parentRemotePath(path)
      if (parent) {
        snackbar(`路径 "${path}" 不存在，已自动跳转到上级目录`, 'info')
        currentPath.value = parent
        void browse()
        return true
      }
    }

    browseError.value = error ? friendlyBrowseError(error, path) : ''
    if (error && !isNotFoundError(error)) {
      snackbar(friendlyBrowseError(error, path), 'warning')
    }
    files.value = items
    if (!error) {
      store.setCached(serverId, path, items, etag ?? undefined)
    }
    if (serverId && items.length) {
      pruneFilePreloadCache(serverId, path)
      preloadDirectoryFiles(serverId, path, items)
      preloadMonacoCore()
    }
    return true
  }

  async function browse(options?: { force?: boolean; silent?: boolean }) {
    if (!selectedServer.value) return
    const path = normalizeRemotePath(currentPath.value)
    const serverId = selectedServer.value
    const browseKey = `${serverId}:${path}`

    const cached = !options?.force ? store.getCached(serverId, path) : null
    if (cached) {
      files.value = cached
      browseError.value = ''
    }

    if (_browseInFlight && _browseInFlightKey === browseKey) {
      return _browseInFlight
    }
    _browseInFlightKey = browseKey

    const seq = ++_browseSeq
    if (!options?.silent) beginLoading(store)
    currentPath.value = path

    const ifNoneMatch = options?.force ? undefined : store.getCachedEtag(serverId, path)

    _browseInFlight = (async () => {
      try {
        let result = await fetchBrowseListing(serverId, path, ifNoneMatch)
        if (seq !== _browseSeq) return

        if (result.status === 304) {
          const entry = store.getCacheEntry(serverId, path)
          if (entry) {
            files.value = entry.items
            store.touchCache(serverId, path)
            browseError.value = ''
            return
          }
          // 304 但本地无缓存（TTL 过期后仍带 etag）→ 重新拉全量
          result = await fetchBrowseListing(serverId, path)
          if (seq !== _browseSeq) return
        }

        applyBrowsePayload(serverId, path, result.data, result.etag, seq)
      } catch (e) {
        if (seq !== _browseSeq) return
        const msg = formatApiError(e, '浏览目录失败')
        browseError.value = msg
        snackbar(msg, 'error')
      } finally {
        if (seq === _browseSeq) {
          if (options?.silent) {
            store.loading = false
            store.loadingVisible = false
          } else {
            endLoading(store)
          }
          syncRouteQuery()
        }
        if (_browseInFlightKey === browseKey) {
          _browseInFlight = null
          _browseInFlightKey = ''
        }
      }
    })()

    return _browseInFlight
  }

  function browseCurrent() {
    if (!selectedServer.value) {
      snackbar('请先选择服务器', 'warning')
      return
    }
    currentPath.value = normalizeRemotePath(currentPath.value)
    store.invalidateCache(selectedServer.value, currentPath.value)
    void browse({ force: true })
  }

  function invalidateAfterMutation(parentPath?: string) {
    if (!selectedServer.value) return
    const p = parentPath ?? currentPath.value
    store.invalidateCache(selectedServer.value, normalizeRemotePath(p))
    clearFilePreloadCache()
  }

  return {
    emptyHint,
    browse,
    browseCurrent,
    invalidateAfterMutation,
    loadingVisible,
  }
}
