export interface RetryOnceOptions {
  /** Upper bound of the random pre-retry delay, in ms. Default 3000. */
  maxDelayMs?: number
  /** Injectable for tests. Default Math.random. */
  random?: () => number
  /** Injectable for tests. Default setTimeout-based wait. */
  wait?: (ms: number) => Promise<void>
}

const defaultWait = (ms: number): Promise<void> =>
  new Promise((resolve) => setTimeout(resolve, ms))

/**
 * Runs requestFn; if it rejects, waits a random 0..maxDelayMs and tries
 * exactly once more. A second rejection propagates as-is. Multiple
 * concurrent DC instances (e.g. VA multi-page reports) can cause the
 * startup request to time out even though a second attempt succeeds.
 */
export const retryOnce = async <T>(
  requestFn: () => Promise<T>,
  options?: RetryOnceOptions
): Promise<T> => {
  const maxDelayMs = options?.maxDelayMs ?? 3000
  const random = options?.random ?? Math.random
  const wait = options?.wait ?? defaultWait

  try {
    return await requestFn()
  } catch {
    await wait(random() * maxDelayMs)
    return requestFn()
  }
}
