// Background dictionary preload for offline readiness. An installed PWA with a confirmed email
// warms the dictionaries for the player's enabled variants while online, so a later switch to
// deliberate offline mode has the data it needs. The pure preloadDicts here takes its side effects
// (getDawg, the session miss-breaker, sleep) as dependencies, so it unit-tests in the node env. The
// eligibility and once/online guard live in offline.svelte.ts (kickDictPreload); the browser
// orchestration that supplies the real side effects and raises the in-lobby warning lives in
// preloadrun.ts, which offline.svelte.ts imports dynamically so neither the loader nor the
// generator is pulled into the main bundle.

import type { Variant } from '../model';
import type { Dawg } from './dawg';

/** PreloadDeps injects preloadDicts's side effects so the logic stays pure and testable. */
export interface PreloadDeps {
  /** getDawg resolves the (variant, version) reader, serving memory/IndexedDB before the network,
   *  or null on any miss — mirrors the in-game loader. */
  getDawg: (variant: Variant, version: string) => Promise<Dawg | null>;
  /** disabled reports whether the session dictionary miss-breaker has tripped (too many network
   *  misses this session); when it has, a network fetch will not recover, so retries stop. */
  disabled: () => boolean;
  /** sleep waits between retries; defaults to a real timer. */
  sleep?: (ms: number) => Promise<void>;
  /** retries is the number of extra attempts after the first (default 2). */
  retries?: number;
  /** backoffMs is the base linear backoff between attempts (default 800). */
  backoffMs?: number;
}

/** PreloadResult reports which enabled variants ended up available (ok) and which are still
 *  missing (failed) after the preload — the caller surfaces a warning when failed is non-empty. */
export interface PreloadResult {
  ok: Variant[];
  failed: Variant[];
}

/**
 * preloadDicts fetches, via getDawg, the dictionary for each enabled variant that has a known
 * version, retrying transient misses with linear backoff. A variant with no known version, or one
 * still missing after the retry budget, lands in failed; the rest in ok. It never throws and stops
 * retrying a variant once the session miss-breaker (disabled) trips, since the network will not
 * recover this session — a cached dictionary is still served by getDawg regardless.
 */
export async function preloadDicts(
  versions: Partial<Record<Variant, string>>,
  enabled: readonly Variant[],
  deps: PreloadDeps,
): Promise<PreloadResult> {
  const sleep = deps.sleep ?? ((ms) => new Promise<void>((r) => setTimeout(r, ms)));
  const retries = deps.retries ?? 2;
  const backoffMs = deps.backoffMs ?? 800;
  const ok: Variant[] = [];
  const failed: Variant[] = [];
  for (const variant of enabled) {
    const version = versions[variant];
    if (!version) {
      failed.push(variant);
      continue;
    }
    let dawg: Dawg | null = null;
    for (let attempt = 0; attempt <= retries; attempt++) {
      dawg = await deps.getDawg(variant, version);
      if (dawg || deps.disabled()) break;
      if (attempt < retries) await sleep(backoffMs * (attempt + 1));
    }
    (dawg ? ok : failed).push(variant);
  }
  return { ok, failed };
}
