import {
  computed,
  onBeforeUnmount,
  onMounted,
  ref,
  shallowRef,
  type Ref,
  type ShallowRef
} from 'vue'
import { isAsyncTaskActive, isAsyncTaskTerminal, type AsyncTaskProgress } from '@/types/api'

const POLL_DELAYS = [2000, 3000, 5000]
const MAX_POLL_DELAY = 10000

export interface AsyncTaskPollingOptions<T extends AsyncTaskProgress> {
  storageKey: string
  fetchTask: (taskId: number) => Promise<T>
  isForbidden?: (error: unknown) => boolean
  autoRestore?: boolean
}

export interface AsyncTaskPollingState<T extends AsyncTaskProgress> {
  taskId: Ref<number | null>
  task: ShallowRef<T | null>
  loading: Ref<boolean>
  forbidden: Ref<boolean>
  error: Ref<string | null>
  isActive: Readonly<Ref<boolean>>
  start: (taskId: number) => Promise<void>
  retry: () => Promise<void>
  stop: () => void
  clear: () => void
}

const readStoredTaskId = (storageKey: string): number | null => {
  try {
    const value = Number(localStorage.getItem(storageKey))
    return Number.isInteger(value) && value > 0 ? value : null
  } catch {
    return null
  }
}

const writeStoredTaskId = (storageKey: string, taskId: number | null) => {
  try {
    if (taskId) localStorage.setItem(storageKey, String(taskId))
    else localStorage.removeItem(storageKey)
  } catch {
    // Storage may be unavailable in private browsing or restricted webviews.
  }
}

const getErrorMessage = (error: any) =>
  error?.response?.data?.msg || error?.message || '获取导出任务详情失败'

export function useAsyncTaskPolling<T extends AsyncTaskProgress>(
  options: AsyncTaskPollingOptions<T>
): AsyncTaskPollingState<T> {
  const taskId = ref<number | null>(readStoredTaskId(options.storageKey))
  const task = shallowRef<T | null>(null)
  const loading = ref(false)
  const forbidden = ref(false)
  const error = ref<string | null>(null)
  const isActive = computed(() => isAsyncTaskActive(task.value?.status))

  let timer: number | undefined
  let pollIndex = 0
  let requestInFlight = false

  const clearTimer = () => {
    if (timer !== undefined) {
      window.clearTimeout(timer)
      timer = undefined
    }
  }

  const stop = () => {
    clearTimer()
  }

  const clear = () => {
    stop()
    taskId.value = null
    task.value = null
    error.value = null
    forbidden.value = false
    writeStoredTaskId(options.storageKey, null)
  }

  const schedule = () => {
    clearTimer()
    if (document.hidden || !isActive.value || !taskId.value) return
    const delay = POLL_DELAYS[pollIndex] ?? MAX_POLL_DELAY
    pollIndex += 1
    timer = window.setTimeout(() => {
      timer = undefined
      void refresh()
    }, delay)
  }

  const refresh = async () => {
    if (!taskId.value || requestInFlight || document.hidden) return

    requestInFlight = true
    loading.value = true
    forbidden.value = false
    error.value = null
    try {
      const nextTask = await options.fetchTask(taskId.value)
      task.value = nextTask
      if (isAsyncTaskTerminal(nextTask.status)) {
        stop()
        writeStoredTaskId(options.storageKey, null)
      } else {
        schedule()
      }
    } catch (requestError) {
      if (options.isForbidden?.(requestError) ?? false) {
        forbidden.value = true
        stop()
      } else {
        error.value = getErrorMessage(requestError)
        stop()
      }
    } finally {
      loading.value = false
      requestInFlight = false
    }
  }

  const start = async (nextTaskId: number) => {
    if (!Number.isInteger(nextTaskId) || nextTaskId <= 0) return
    taskId.value = nextTaskId
    task.value = null
    pollIndex = 0
    writeStoredTaskId(options.storageKey, nextTaskId)
    await refresh()
  }

  const retry = async () => {
    if (!taskId.value) return
    await refresh()
  }

  const handleVisibilityChange = () => {
    if (document.hidden) {
      stop()
      return
    }
    if (taskId.value && isActive.value) {
      pollIndex = 0
      void refresh()
    }
  }

  onMounted(() => {
    document.addEventListener('visibilitychange', handleVisibilityChange)
    if (options.autoRestore !== false && taskId.value) void refresh()
  })

  onBeforeUnmount(() => {
    stop()
    document.removeEventListener('visibilitychange', handleVisibilityChange)
  })

  return {
    taskId,
    task: task as ShallowRef<T | null>,
    loading,
    forbidden,
    error,
    isActive,
    start,
    retry,
    stop,
    clear
  }
}
