/**
 * Bounded replay cache for Discord's globally unique native message IDs.
 * Durable ingress idempotency is added with Tess's canonical inbox/outbox work.
 */
export class DiscordReplayProtector {
  private readonly claimedAt = new Map<string, number>();

  constructor(
    private readonly ttlMs = 15 * 60 * 1000,
    private readonly maxEntries = 10_000,
  ) {}

  get size(): number {
    return this.claimedAt.size;
  }

  /** Claims an ID exactly once within its bounded retention window. */
  claim(messageId: string, now = Date.now()): boolean {
    this.prune(now);
    if (this.claimedAt.has(messageId)) return false;

    this.claimedAt.set(messageId, now);
    this.evictOverflow();
    return true;
  }

  private prune(now: number): void {
    for (const [messageId, claimedAt] of this.claimedAt) {
      if (now - claimedAt >= this.ttlMs) this.claimedAt.delete(messageId);
    }
  }

  private evictOverflow(): void {
    while (this.claimedAt.size > this.maxEntries) {
      const oldestMessageId = this.claimedAt.keys().next().value;
      if (oldestMessageId === undefined) return;
      this.claimedAt.delete(oldestMessageId);
    }
  }
}
