import { ApiException, type ApiError } from './types'

const BASE = '' // 同源；dev 走 vite proxy
const DEFAULT_TIMEOUT_MS = 30_000

interface RequestOptions {
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
  body?: unknown
  signal?: AbortSignal
}

let authTokenProvider: () => string | null = () => null
let onUnauthorized: () => void = () => {}
let onForbidden: () => void = () => {}

export function configure(opts: {
  tokenProvider: () => string | null
  onUnauthorized: () => void
  onForbidden?: () => void
}) {
  authTokenProvider = opts.tokenProvider
  onUnauthorized = opts.onUnauthorized
  if (opts.onForbidden) onForbidden = opts.onForbidden
}

export async function request<T>(path: string, opts: RequestOptions = {}): Promise<T> {
  const isForm = opts.body instanceof FormData
  const headers: Record<string, string> = {
    'X-Client-Request-ID': crypto.randomUUID(),
  }
  if (!isForm) headers['Content-Type'] = 'application/json'
  const token = authTokenProvider()
  if (token) headers['Authorization'] = `Bearer ${token}`

  const controller = new AbortController()
  const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS)
  let onAbort: (() => void) | undefined
  if (opts.signal) {
    if (opts.signal.aborted) {
      controller.abort()
    } else {
      onAbort = () => controller.abort()
      opts.signal.addEventListener('abort', onAbort, { once: true })
    }
  }

  try {
    const body =
      opts.body === undefined
        ? undefined
        : isForm
          ? (opts.body as FormData)
          : JSON.stringify(opts.body)
    const resp = await fetch(BASE + path, {
      method: opts.method || 'GET',
      headers,
      body,
      signal: controller.signal,
    })

    if (resp.status === 401) {
      onUnauthorized()
    } else if (resp.status === 403) {
      onForbidden()
    }

    if (resp.status === 204) {
      return undefined as T
    }

    const text = await resp.text()
    const data = text ? JSON.parse(text) : null

    if (!resp.ok) {
      throw new ApiException(
        resp.status,
        (data as ApiError) || { code: 'unknown', message: resp.statusText },
      )
    }
    return data as T
  } finally {
    clearTimeout(timer)
    if (opts.signal && onAbort) {
      opts.signal.removeEventListener('abort', onAbort)
    }
  }
}

// buildQuery 把任意对象拼成 "?k=v&..." 形式；undefined / null / '' 跳过；
// 数组与对象用 String() 退化（业务侧目前没有数组 query 需求）。
export function buildQuery(params: object): string {
  const q = new URLSearchParams()
  for (const [k, v] of Object.entries(params)) {
    if (v === undefined || v === null || v === '') continue
    q.set(k, String(v))
  }
  const s = q.toString()
  return s ? '?' + s : ''
}
