/**
 * A simple LRU (Least Recently Used) cache implementation
 */
export class LRUCache<K, V> {
  private cache = new Map<K, V>();
  private readonly maxSize: number;

  constructor(maxSize: number) {
    this.maxSize = maxSize;
  }

  get(key: K): V | undefined {
    const value = this.cache.get(key);
    if (value) {
      // Refresh position by removing and re-adding
      this.cache.delete(key);
      this.cache.set(key, value);
    }
    return value;
  }

  set(key: K, value: V): void {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.maxSize) {
      // Remove oldest entry (first item in map)
      const firstKey = this.cache.keys().next().value;
      if (firstKey) {
        this.cache.delete(firstKey);
      }
    }
    this.cache.set(key, value);
  }

  has(key: K): boolean {
    return this.cache.has(key);
  }

  delete(key: K): boolean {
    return this.cache.delete(key);
  }

  clear(): void {
    this.cache.clear();
  }

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

/**
 * Size-aware LRU cache that tracks memory usage in bytes
 * Evicts entries when total size exceeds the maximum
 */
export class SizeAwareLRUCache<K> {
  private cache = new Map<K, Promise<ArrayBuffer>>();
  private sizes = new Map<K, number>();
  private currentSize = 0;
  private readonly maxSizeBytes: number;

  constructor(maxSizeBytes: number) {
    this.maxSizeBytes = maxSizeBytes;
  }

  get(key: K): Promise<ArrayBuffer> | undefined {
    const value = this.cache.get(key);
    if (value) {
      // Refresh position by removing and re-adding
      const size = this.sizes.get(key) || 0;
      this.cache.delete(key);
      this.cache.set(key, value);
      this.sizes.delete(key);
      this.sizes.set(key, size);
    }
    return value;
  }

  set(key: K, value: Promise<ArrayBuffer>): void {
    // If key already exists, remove it first
    if (this.cache.has(key)) {
      const oldSize = this.sizes.get(key) || 0;
      this.currentSize -= oldSize;
      this.cache.delete(key);
      this.sizes.delete(key);
    }

    // Track the size when the promise resolves
    const sizeTrackingPromise = value
      .then((buffer) => {
        const bufferSize = buffer.byteLength;
        this.sizes.set(key, bufferSize);
        this.currentSize += bufferSize;

        // Evict oldest entries if we exceed the size limit
        this.evictIfNecessary();

        return buffer;
      })
      .catch((error) => {
        // If the promise fails, clean up the entry
        this.cache.delete(key);
        this.sizes.delete(key);
        throw error;
      });

    this.cache.set(key, sizeTrackingPromise);
  }

  private evictIfNecessary(): void {
    while (this.currentSize > this.maxSizeBytes && this.cache.size > 0) {
      // Remove oldest entry (first item in map)
      const firstKey = this.cache.keys().next().value;
      if (firstKey) {
        const size = this.sizes.get(firstKey) || 0;
        this.currentSize -= size;
        this.cache.delete(firstKey);
        this.sizes.delete(firstKey);
      } else {
        break;
      }
    }
  }

  has(key: K): boolean {
    return this.cache.has(key);
  }

  delete(key: K): boolean {
    const size = this.sizes.get(key) || 0;
    this.currentSize -= size;
    this.sizes.delete(key);
    return this.cache.delete(key);
  }

  clear(): void {
    this.cache.clear();
    this.sizes.clear();
    this.currentSize = 0;
  }

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

  get currentSizeBytes(): number {
    return this.currentSize;
  }

  get maxSize(): number {
    return this.maxSizeBytes;
  }
}
