/**
 * Tiny in-process fixed-window rate limiter for the Smart Gallery AI routes.
 *
 * ---------------------------------------------------------------------------
 * SCOPE AND LIMITATIONS — READ BEFORE RELYING ON THIS
 * ---------------------------------------------------------------------------
 * State lives in a plain `Map` in THIS process's memory. That means:
 *
 *   - PER-INSTANCE, NOT GLOBAL. With N app instances behind a load balancer a
 *     caller gets up to N x the configured budget. On serverless platforms each
 *     cold start begins with an empty map, so the effective limit is weaker
 *     still.
 *   - NOT DURABLE. A restart or redeploy clears every counter.
 *   - FIXED WINDOW, NOT SLIDING. A caller can burst `max` at the very end of one
 *     window and `max` again at the start of the next — up to 2x `max` across a
 *     window boundary. Acceptable here; the goal is to bound runaway cost, not
 *     to meter precisely.
 *
 * It exists because the routes it guards spend real money on GPU inference and
 * shipping them with NO limit at all is worse than shipping an imperfect one.
 *
 * REPLACE WITH REDIS (or the platform's rate limiter) BEFORE RUNNING MORE THAN
 * ONE INSTANCE. The `limit()` signature is deliberately narrow so a Redis-backed
 * implementation can drop straight in — the only change needed is making it
 * async at the call sites.
 *
 * This is a throttle, not an authorization check. See src/lib/server/session.ts.
 */

interface Window {
  /** Requests counted so far in the current window. */
  count: number;
  /** Epoch ms at which the current window ends. */
  resetAt: number;
}

const windows = new Map<string, Window>();

/** Drop expired entries so the map cannot grow without bound. */
const SWEEP_INTERVAL_MS = 60_000;
let lastSweep = 0;

function sweep(now: number): void {
  if (now - lastSweep < SWEEP_INTERVAL_MS) return;
  lastSweep = now;
  for (const [key, w] of windows) {
    if (w.resetAt <= now) windows.delete(key);
  }
}

export interface LimitResult {
  ok: boolean;
  /** Seconds until the window resets. Send as `Retry-After` when `ok` is false. */
  retryAfter: number;
}

/**
 * Count one request against `key` and report whether it is allowed.
 *
 * @param key      Caller identity — use `rateLimitKey()` from ./session.
 * @param max      Requests allowed per window.
 * @param windowMs Window length in ms.
 */
export function limit(key: string, max: number, windowMs: number): LimitResult {
  const now = Date.now();
  sweep(now);

  const existing = windows.get(key);
  if (!existing || existing.resetAt <= now) {
    windows.set(key, { count: 1, resetAt: now + windowMs });
    return { ok: true, retryAfter: 0 };
  }

  if (existing.count >= max) {
    return { ok: false, retryAfter: Math.max(1, Math.ceil((existing.resetAt - now) / 1000)) };
  }

  existing.count += 1;
  return { ok: true, retryAfter: 0 };
}

/** Test/maintenance helper — clears all counters. */
export function resetAllLimits(): void {
  windows.clear();
  lastSweep = 0;
}
