import type { CachePort } from "./cache-port.js";

// Doubly-linked-list node for O(1) LRU eviction
interface Node<V> {
  key: string;
  val: V;
  exp: number;   // expiry epoch ms (0 = never)
  prev: Node<V> | null;
  next: Node<V> | null;
}

class LRU<V> {
  private map = new Map<string, Node<V>>();
  private head: Node<V> | null = null; // MRU end
  private tail: Node<V> | null = null; // LRU end

  constructor(private readonly max: number) {}

  get(key: string): V | undefined {
    const node = this.map.get(key);
    if (!node) return undefined;
    if (node.exp && Date.now() > node.exp) { this.evict(node); return undefined; }
    this.promote(node);
    return node.val;
  }

  set(key: string, val: V, ttlMs: number): void {
    const existing = this.map.get(key);
    if (existing) { existing.val = val; existing.exp = ttlMs ? Date.now() + ttlMs : 0; this.promote(existing); return; }
    const node: Node<V> = { key, val, exp: ttlMs ? Date.now() + ttlMs : 0, prev: null, next: this.head };
    if (this.head) this.head.prev = node;
    this.head = node;
    if (!this.tail) this.tail = node;
    this.map.set(key, node);
    if (this.map.size > this.max) this.evictTail();
  }

  delete(key: string): void { const n = this.map.get(key); if (n) this.evict(n); }

  keys(): IterableIterator<string> { return this.map.keys(); }

  private promote(node: Node<V>): void {
    if (node === this.head) return;
    if (node.prev) node.prev.next = node.next;
    if (node.next) node.next.prev = node.prev;
    if (node === this.tail) this.tail = node.prev;
    node.prev = null; node.next = this.head;
    if (this.head) this.head.prev = node;
    this.head = node;
  }

  private evict(node: Node<V>): void {
    if (node.prev) node.prev.next = node.next; else this.head = node.next;
    if (node.next) node.next.prev = node.prev; else this.tail = node.prev;
    this.map.delete(node.key);
  }

  private evictTail(): void { if (this.tail) this.evict(this.tail); }
}

const store = new LRU<string>(2_000);

export class MemoryCacheAdapter implements CachePort {
  async get<T>(key: string): Promise<T | null> {
    const raw = store.get(key);
    return raw !== undefined ? (JSON.parse(raw) as T) : null;
  }

  async set<T>(key: string, value: T, ttlSeconds: number): Promise<void> {
    store.set(key, JSON.stringify(value), ttlSeconds * 1_000);
  }

  async delByPrefix(prefix: string): Promise<void> {
    for (const key of store.keys()) {
      if (key.startsWith(prefix)) store.delete(key);
    }
  }
}
