/**
 * Tiny in-process sliding-window rate limiter.
 *
 * Per Node process — fine for a single Next.js standalone server, which is
 * the only deployment shape we ship. If the app ever runs behind multiple
 * replicas, swap the Map for a shared Redis-backed bucket; the public API
 * here stays the same.
 *
 * No deps: a Map of `key -> sorted timestamps[]`. Each call drops
 * timestamps older than the window, then either records the new request
 * and returns ok, or returns blocked with retry-after seconds.
 *
 * Memory: O(active-keys * limit). At a 5-per-15min ceiling and even
 * thousands of distinct IPs, this is trivially small.
 */

interface Window {
  /** Max events allowed per `windowMs` per `key`. */
  limit: number;
  /** Window length in ms. */
  windowMs: number;
}

const buckets = new Map<string, number[]>();

export function rateLimit(
  key: string,
  { limit, windowMs }: Window,
): { ok: true } | { ok: false; retryAfterSec: number } {
  const now = Date.now();
  const cutoff = now - windowMs;
  const hits = (buckets.get(key) ?? []).filter((t) => t > cutoff);

  if (hits.length >= limit) {
    const oldest = hits[0];
    const retryAfterSec = Math.max(1, Math.ceil((oldest + windowMs - now) / 1000));
    buckets.set(key, hits);
    return { ok: false, retryAfterSec };
  }

  hits.push(now);
  buckets.set(key, hits);
  return { ok: true };
}

/**
 * Best-effort client IP extraction. In a StartOS deployment the Next.js
 * server sits behind a single proxy hop, so the leftmost
 * `x-forwarded-for` entry is the originating client. If headers are
 * absent (direct access in dev), fall back to the literal "unknown" key
 * so the limiter still applies as a global rate cap.
 */
export function clientIpFromHeaders(headers: Headers): string {
  const xff = headers.get('x-forwarded-for');
  if (xff) {
    const first = xff.split(',')[0]?.trim();
    if (first) return first;
  }
  const real = headers.get('x-real-ip');
  if (real) return real;
  return 'unknown';
}
