export interface SessionCacheOpts<V> {
  maxEntries?: number;
  idleMs?: number;
  now?: () => number;
  onEvict?: (key: string, value: V) => void;
}

interface CacheEntry<V> {
  value: V;
  lastAccessed: number;
}

export class SessionCache<V> {
  private readonly entries = new Map<string, CacheEntry<V>>();
  private readonly maxEntries: number;
  private readonly idleMs: number;
  private readonly now: () => number;
  private readonly onEvict: ((key: string, value: V) => void) | undefined;

  constructor(opts: SessionCacheOpts<V> = {}) {
    this.maxEntries = opts.maxEntries ?? 128;
    this.idleMs = opts.idleMs ?? 30 * 60 * 1000;
    this.now = opts.now ?? Date.now;
    this.onEvict = opts.onEvict;
  }

  get(key: string): V | undefined {
    this.sweepIdle();
    const entry = this.entries.get(key);
    if (!entry) return undefined;
    entry.lastAccessed = this.now();
    return entry.value;
  }

  has(key: string): boolean {
    return this.get(key) !== undefined;
  }

  set(key: string, value: V): void {
    this.sweepIdle();
    const existing = this.entries.get(key);
    if (existing) {
      existing.value = value;
      existing.lastAccessed = this.now();
      return;
    }
    this.entries.set(key, { value, lastAccessed: this.now() });
    this.evictLru();
  }

  dispose(): void {
    for (const [key, entry] of this.entries) {
      this.onEvict?.(key, entry.value);
    }
    this.entries.clear();
  }

  private sweepIdle(): void {
    const cutoff = this.now() - this.idleMs;
    for (const [key, entry] of this.entries) {
      if (entry.lastAccessed < cutoff) this.evict(key, entry);
    }
  }

  private evictLru(): void {
    while (this.entries.size > this.maxEntries) {
      let oldestKey: string | null = null;
      let oldestEntry: CacheEntry<V> | null = null;
      for (const [key, entry] of this.entries) {
        if (!oldestEntry || entry.lastAccessed < oldestEntry.lastAccessed) {
          oldestKey = key;
          oldestEntry = entry;
        }
      }
      if (!oldestKey || !oldestEntry) return;
      this.evict(oldestKey, oldestEntry);
    }
  }

  private evict(key: string, entry: CacheEntry<V>): void {
    if (!this.entries.delete(key)) return;
    this.onEvict?.(key, entry.value);
  }
}
