///| Efficient LRU cache using two-generation approach

///|

///| Instead of tracking exact order (O(n) operations), we use two maps:

///| - "hot": recently accessed items

///| - "cold": older items

///|

///| On access: move from cold to hot if needed

///| On eviction: clear cold, swap hot->cold

///|

///| This gives O(1) operations with approximate LRU semantics.

///|
pub struct LruCache[V] {
  mut hot : Map[String, V]
  mut cold : Map[String, V]
  mut hot_bytes : Int
  mut cold_bytes : Int
  max_bytes : Int
  size_fn : (V) -> Int
}

///|
pub fn[V] LruCache::new(max_bytes : Int, size_fn : (V) -> Int) -> LruCache[V] {
  {
    hot: Map([]),
    cold: Map([]),
    hot_bytes: 0,
    cold_bytes: 0,
    max_bytes,
    size_fn,
  }
}

///|
pub fn[V] LruCache::get(self : LruCache[V], key : String) -> V? {
  // Check hot first
  match self.hot.get(key) {
    Some(v) => Some(v)
    None =>
      // Check cold, promote to hot if found
      match self.cold.get(key) {
        Some(v) => {
          // Promote to hot
          let size = (self.size_fn)(v)
          self.cold.remove(key)
          self.cold_bytes -= size
          self.hot[key] = v
          self.hot_bytes += size
          Some(v)
        }
        None => None
      }
  }
}

///|
pub fn[V] LruCache::set(self : LruCache[V], key : String, value : V) -> Unit {
  let size = (self.size_fn)(value)
  // Don't cache items larger than max
  if size > self.max_bytes {
    return
  }
  // Remove from cold if exists
  if self.cold.get(key) is Some(old) {
    self.cold_bytes -= (self.size_fn)(old)
    self.cold.remove(key)
  }
  // Remove from hot if exists
  if self.hot.get(key) is Some(old) {
    self.hot_bytes -= (self.size_fn)(old)
    self.hot.remove(key)
  }
  // Ensure space
  while self.hot_bytes + self.cold_bytes + size > self.max_bytes {
    self.evict()
  }
  // Add to hot
  self.hot[key] = value
  self.hot_bytes += size
}

///|
fn[V] LruCache::evict(self : LruCache[V]) -> Unit {
  if self.cold.length() > 0 {
    // Clear cold generation
    self.cold.clear()
    self.cold_bytes = 0
  } else if self.hot.length() > 0 {
    // Swap hot to cold
    let old_hot = self.hot
    let old_hot_bytes = self.hot_bytes
    self.hot = Map([])
    self.hot_bytes = 0
    self.cold = old_hot
    self.cold_bytes = old_hot_bytes
  }
}

///|
pub fn[V] LruCache::remove(self : LruCache[V], key : String) -> Unit {
  if self.hot.get(key) is Some(v) {
    self.hot_bytes -= (self.size_fn)(v)
    self.hot.remove(key)
  }
  if self.cold.get(key) is Some(v) {
    self.cold_bytes -= (self.size_fn)(v)
    self.cold.remove(key)
  }
}

///|
pub fn[V] LruCache::clear(self : LruCache[V]) -> Unit {
  self.hot.clear()
  self.cold.clear()
  self.hot_bytes = 0
  self.cold_bytes = 0
}

///|
pub fn[V] LruCache::total_bytes(self : LruCache[V]) -> Int {
  self.hot_bytes + self.cold_bytes
}

///|
pub fn[V] LruCache::length(self : LruCache[V]) -> Int {
  self.hot.length() + self.cold.length()
}

///|
pub fn[V] LruCache::contains(self : LruCache[V], key : String) -> Bool {
  self.hot.contains(key) || self.cold.contains(key)
}