// src/lib/seranking/rate-limit.ts — file d'attente process-wide devant l'API
// SE-Ranking. La clé plateforme est PARTAGÉE par toutes les orgs : N server
// actions concurrentes doivent se partager le budget (10 RPS par clé côté
// SE-Ranking) — plafond prudent à 5 RPS, marge pour les slices futures.
// Prod = process unique (systemd) ; multi-instance ⇒ limiteur à déplacer
// côté broker (hors périmètre, noté dans la spec).

export interface RateLimiter {
  /** Exécute `fn` dès qu'un slot est libre. Les appels sont SÉRIALISÉS (un
   *  seul en vol) et espacés d'au moins `minIntervalMs` entre deux départs. */
  schedule<T>(fn: () => Promise<T>): Promise<T>;
}

export function createRateLimiter({
  minIntervalMs,
  now = Date.now,
  sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms)),
}: {
  minIntervalMs: number;
  now?: () => number;
  sleep?: (ms: number) => Promise<void>;
}): RateLimiter {
  let tail: Promise<unknown> = Promise.resolve();
  let lastStart = Number.NEGATIVE_INFINITY;
  return {
    schedule<T>(fn: () => Promise<T>): Promise<T> {
      const run = tail.then(async () => {
        const wait = lastStart + minIntervalMs - now();
        if (wait > 0) await sleep(wait);
        lastStart = now();
        return fn();
      });
      // La chaîne ne doit jamais rester rejetée, sinon tout appel suivant
      // hériterait de l'erreur — l'appelant, lui, reçoit `run` tel quel.
      tail = run.catch(() => {});
      return run;
    },
  };
}

/** Singleton process-wide — TOUT appel à l'API SE-Ranking passe par lui. */
export const serankingLimiter = createRateLimiter({ minIntervalMs: 200 });
