/**
 * Composable for making authenticated API calls to the libreDecision backend.
 *
 * Uses the runtime config `apiBase` and automatically injects the Bearer token
 * from the auth store when available.
 *
 * Production-grade features:
 * - Error interceptor with user-friendly French messages
 * - Retry logic with exponential backoff for 5xx errors
 * - Request timeout (30s default)
 * - AbortController support for cancellation
 */

/** Map of HTTP status codes to user-friendly French error messages. */
const HTTP_ERROR_MESSAGES: Record<number, string> = {
  401: 'Session expirée, veuillez vous reconnecter',
  403: 'Accès non autorisé',
  404: 'Ressource introuvable',
  409: 'Conflit avec une ressource existante',
  422: 'Données invalides',
  429: 'Trop de requêtes, veuillez patienter',
  500: 'Erreur serveur, veuillez réessayer',
  502: 'Service temporairement indisponible',
  503: 'Service en maintenance, veuillez réessayer',
  504: 'Délai de réponse dépassé',
}

/** Default request timeout in milliseconds. */
const DEFAULT_TIMEOUT_MS = 30_000

/** Maximum number of retry attempts for 5xx errors. */
const MAX_RETRIES = 3

/** Base delay for exponential backoff in milliseconds. */
const BASE_BACKOFF_MS = 1_000

export interface ApiOptions extends Record<string, any> {
  /** Custom timeout in milliseconds (default: 30000). */
  timeout?: number
  /** External AbortController for request cancellation. */
  signal?: AbortSignal
  /** Disable automatic retry for this request. */
  noRetry?: boolean
}

export interface ApiError {
  status: number
  message: string
  detail?: string
}

/**
 * Resolve a user-friendly error message from an HTTP status code.
 */
function resolveErrorMessage(status: number, fallback?: string): string {
  return HTTP_ERROR_MESSAGES[status] || fallback || 'Une erreur inattendue est survenue'
}

/**
 * Wait for the specified duration in milliseconds.
 */
function delay(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms))
}

/**
 * Determine whether an HTTP status code is retryable (5xx server errors).
 */
function isRetryable(status: number): boolean {
  return status >= 500 && status < 600
}

export function useApi() {
  const config = useRuntimeConfig()
  const auth = useAuthStore()
  const orgsStore = useOrganizationsStore()

  /**
   * Perform a typed fetch against the backend API.
   *
   * @param path - API path relative to apiBase, e.g. "/documents"
   * @param options - $fetch options (method, body, query, headers, timeout, signal, noRetry)
   * @returns Typed response
   * @throws ApiError with status, message, and optional detail
   */
  async function $api<T>(path: string, options: ApiOptions = {}): Promise<T> {
    const {
      timeout = DEFAULT_TIMEOUT_MS,
      signal: externalSignal,
      noRetry = false,
      ...fetchOptions
    } = options

    const headers: Record<string, string> = {}
    if (auth.token) {
      headers.Authorization = `Bearer ${auth.token}`
    }
    if (orgsStore.activeSlug) {
      headers['X-Organization'] = orgsStore.activeSlug
    }

    const maxAttempts = noRetry ? 1 : MAX_RETRIES
    let lastError: any = null

    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      // Create an AbortController for timeout management
      const controller = new AbortController()
      const timeoutId = setTimeout(() => controller.abort(), timeout)

      // If an external signal is provided, forward its abort
      let externalAbortHandler: (() => void) | null = null
      if (externalSignal) {
        if (externalSignal.aborted) {
          clearTimeout(timeoutId)
          throw _createApiError(0, 'Requête annulée')
        }
        externalAbortHandler = () => controller.abort()
        externalSignal.addEventListener('abort', externalAbortHandler, { once: true })
      }

      try {
        const result = await $fetch<T>(`${config.public.apiBase}${path}`, {
          ...fetchOptions,
          headers: { ...headers, ...fetchOptions.headers },
          signal: controller.signal,
        })

        return result
      } catch (err: any) {
        lastError = err

        const status = err?.response?.status || err?.status || 0

        // Handle 401: auto-logout for expired sessions
        if (status === 401 && auth.token) {
          auth.token = null
          auth.identity = null
          auth._clearToken()
          navigateTo('/login')
        }

        // Handle abort (timeout or external cancellation)
        if (err?.name === 'AbortError' || controller.signal.aborted) {
          if (externalSignal?.aborted) {
            throw _createApiError(0, 'Requête annulée')
          }
          throw _createApiError(0, 'Délai de réponse dépassé')
        }

        // Retry only for 5xx errors and if we have remaining attempts
        if (isRetryable(status) && attempt < maxAttempts) {
          const backoffMs = BASE_BACKOFF_MS * Math.pow(2, attempt - 1)
          await delay(backoffMs)
          continue
        }

        // Build and throw a structured error
        const detail = err?.data?.detail || err?.message || undefined
        const message = resolveErrorMessage(status, detail)
        throw _createApiError(status, message, detail)
      } finally {
        clearTimeout(timeoutId)
        if (externalAbortHandler && externalSignal) {
          externalSignal.removeEventListener('abort', externalAbortHandler)
        }
      }
    }

    // Fallback: should not reach here, but handle gracefully
    const fallbackStatus = lastError?.response?.status || lastError?.status || 0
    const fallbackDetail = lastError?.data?.detail || lastError?.message || undefined
    throw _createApiError(fallbackStatus, resolveErrorMessage(fallbackStatus, fallbackDetail), fallbackDetail)
  }

  /**
   * Create a structured ApiError object.
   */
  function _createApiError(status: number, message: string, detail?: string): ApiError {
    const error: ApiError = { status, message }
    if (detail) error.detail = detail
    return error
  }

  /**
   * Create an AbortController for manual request cancellation.
   * Useful for component unmount cleanup.
   *
   * @example
   * const { controller, signal } = createAbortController()
   * await $api('/documents', { signal })
   * // On unmount:
   * controller.abort()
   */
  function createAbortController() {
    const controller = new AbortController()
    return { controller, signal: controller.signal }
  }

  return { $api, createAbortController }
}
