// Detection of a planned edge maintenance window. During a prod deploy roll the edge
// caddy returns HTTP 503 with an `X-Scrabble-Maintenance: 1` header (and a `Retry-After`)
// for every gated route. The SPA keys STRICTLY on that marker — not on any transport
// `unavailable`, which the "Connecting…" indicator already covers — so a planned window
// raises the maintenance overlay while an ordinary blip stays a soft reconnect.
//
// These helpers are pure (no DOM, no runes) so they unit-test in the node vitest env. The
// maintenance marker + Retry-After ride on the raw ConnectError's `metadata`, which
// toGatewayError (retry.ts) discards — so detection must run on the raw error.

import { ConnectError } from '@connectrpc/connect';

const DEFAULT_RETRY_MS = 15_000;
const MIN_RETRY_MS = 3_000;
const MAX_RETRY_MS = 120_000;

/**
 * parseRetryAfterMs converts a `Retry-After` header (delta-seconds — the edge emits
 * seconds, never an HTTP-date) into a millisecond hint clamped to a sane band. An absent
 * or unparseable value falls back to a default.
 */
export function parseRetryAfterMs(header: string | null | undefined): number {
  const secs = header == null ? NaN : Number(header);
  if (!Number.isFinite(secs) || secs <= 0) return DEFAULT_RETRY_MS;
  return Math.min(MAX_RETRY_MS, Math.max(MIN_RETRY_MS, Math.round(secs * 1000)));
}

/**
 * maintenanceRetryMs returns the maintenance `Retry-After` hint in milliseconds when the
 * thrown transport error is the edge maintenance 503 (carries `X-Scrabble-Maintenance: 1`),
 * or null for any other error. Header lookup is case-insensitive (Headers.get).
 */
export function maintenanceRetryMs(e: unknown): number | null {
  if (!(e instanceof ConnectError)) return null;
  if (e.metadata?.get('x-scrabble-maintenance') !== '1') return null;
  return parseRetryAfterMs(e.metadata.get('retry-after'));
}
