export interface RateLimitDecision {
  ok: boolean;
  retryAfterMs?: number;
}

/**
 * Tiny token-bucket rate limiter.
 *
 * This is intentionally minimal: enough to prevent obvious flooding and to
 * have deterministic tests.
 */
export class TokenBucketRateLimiter {
  private buckets = new Map<string, { tokens: number; lastMs: number }>();

  constructor(
    private readonly capacity: number,
    private readonly refillPerSec: number,
    private readonly nowMs: () => number = () => Date.now(),
  ) {
    if (capacity <= 0) throw new Error('capacity must be > 0');
    if (refillPerSec < 0) throw new Error('refillPerSec must be >= 0');
  }

  allow(key: string, cost: number = 1): RateLimitDecision {
    const now = this.nowMs();
    const b = this.buckets.get(key) ?? { tokens: this.capacity, lastMs: now };
    const elapsedSec = Math.max(0, (now - b.lastMs) / 1000);
    b.tokens = Math.min(this.capacity, b.tokens + elapsedSec * this.refillPerSec);
    b.lastMs = now;

    if (b.tokens >= cost) {
      b.tokens -= cost;
      this.buckets.set(key, b);
      return { ok: true };
    }

    this.buckets.set(key, b);
    const missing = cost - b.tokens;
    const retryAfterMs = this.refillPerSec === 0 ? undefined : Math.ceil((missing / this.refillPerSec) * 1000);
    return { ok: false, retryAfterMs };
  }
}
