/**
 * Pure utility functions extracted from the WebSocket module for testability.
 */

/** Known gateway connect error detail codes (structured error codes sent by newer gateways) */
export const ConnectErrorDetailCodes = {
  AUTH_TOKEN_MISSING: 'AUTH_TOKEN_MISSING',
  AUTH_PASSWORD_MISSING: 'AUTH_PASSWORD_MISSING',
  AUTH_PASSWORD_MISMATCH: 'AUTH_PASSWORD_MISMATCH',
  AUTH_RATE_LIMITED: 'AUTH_RATE_LIMITED',
  AUTH_TOKEN_MISMATCH: 'AUTH_TOKEN_MISMATCH',
  ORIGIN_NOT_ALLOWED: 'ORIGIN_NOT_ALLOWED',
  DEVICE_SIGNATURE_INVALID: 'DEVICE_SIGNATURE_INVALID',
} as const

/** Error detail shape from gateway frames. */
export interface GatewayErrorDetail {
  message?: string
  code?: string
  details?: { code?: string; [key: string]: any }
  [key: string]: any
}

/** Extract structured error code from a gateway error frame, if present. */
export function readErrorDetailCode(error: GatewayErrorDetail | null | undefined): string | null {
  if (!error || typeof error !== 'object') return null
  // Newer gateways include a structured details object with a code field
  const detailsCode = error.details?.code
  if (typeof detailsCode === 'string' && detailsCode.length > 0) return detailsCode
  // Some frames carry code at the top level
  const topCode = error.code
  if (typeof topCode === 'string' && topCode.length > 0) return topCode
  return null
}

/** Error codes that should never trigger auto-reconnect. */
export const NON_RETRYABLE_ERROR_CODES = new Set<string>([
  ConnectErrorDetailCodes.AUTH_TOKEN_MISSING,
  ConnectErrorDetailCodes.AUTH_PASSWORD_MISSING,
  ConnectErrorDetailCodes.AUTH_PASSWORD_MISMATCH,
  ConnectErrorDetailCodes.AUTH_RATE_LIMITED,
  ConnectErrorDetailCodes.ORIGIN_NOT_ALLOWED,
  ConnectErrorDetailCodes.DEVICE_SIGNATURE_INVALID,
])

/** Check whether a given error code is non-retryable. */
export function isNonRetryableErrorCode(code: string): boolean {
  return NON_RETRYABLE_ERROR_CODES.has(code)
}

/**
 * Retry once without browser device identity when a valid gateway auth token
 * exists but the browser's cached device credentials appear invalid.
 */
export function shouldRetryWithoutDeviceIdentity(
  message: string,
  error: GatewayErrorDetail | null | undefined,
  hasAuthToken: boolean,
  alreadyRetried: boolean,
): boolean {
  if (!hasAuthToken || alreadyRetried) return false

  const code = readErrorDetailCode(error)
  if (code === ConnectErrorDetailCodes.DEVICE_SIGNATURE_INVALID) return true

  const normalized = message.toLowerCase()
  return (
    normalized.includes('device_auth_signature_invalid') ||
    normalized.includes('device signature invalid') ||
    normalized.includes('invalid device token') ||
    normalized.includes('device token invalid')
  )
}

/**
 * Calculate exponential backoff delay for reconnect attempts.
 * Uses base * 1.7^attempt, capped at 15000ms.
 * Returns only the deterministic base (without jitter) for testability.
 */
export function calculateBackoff(attempt: number): number {
  return Math.min(1000 * Math.pow(1.7, attempt), 15000)
}

/**
 * Detect a gap in event sequence numbers.
 * Returns info about the gap, or null if there is no gap.
 */
export function detectSequenceGap(
  lastSeq: number | null,
  currentSeq: number,
): { from: number; to: number; count: number } | null {
  if (lastSeq === null) return null
  if (currentSeq <= lastSeq + 1) return null
  const from = lastSeq + 1
  const to = currentSeq - 1
  return { from, to, count: to - from + 1 }
}
