///|
/// An in-memory content-addressed blob store for tests and ephemeral caches.
pub struct MemoryStore {
  blobs : @hashmap.HashMap[String, Bytes]
  pins : @hashmap.HashMap[String, Int]
}

///|
pub fn MemoryStore::new() -> MemoryStore {
  MemoryStore::{ blobs: @hashmap.HashMap([]), pins: @hashmap.HashMap([]), }
}

///|
/// Store bytes and return their SHA-256 content identifier.
pub fn MemoryStore::put(self : MemoryStore, data : Bytes) -> Digest {
  let id = digest(data)
  self.blobs.set(id.to_string(), data)
  id
}

///|
/// Store data only when it matches an expected digest.
pub fn MemoryStore::put_verified(
  self : MemoryStore,
  expected : Digest,
  data : Bytes,
) -> Bool {
  let actual = digest(data)
  if actual.to_string() != expected.to_string() {
    return false
  }
  self.blobs.set(expected.to_string(), data)
  true
}

///|
pub fn MemoryStore::get(self : MemoryStore, id : Digest) -> Bytes? {
  self.blobs.get(id.to_string())
}

///|
pub fn MemoryStore::has(self : MemoryStore, id : Digest) -> Bool {
  self.blobs.contains(id.to_string())
}

///|
/// Re-hash a stored blob to detect accidental or hostile corruption.
pub fn MemoryStore::verify(self : MemoryStore, id : Digest) -> Bool {
  match self.get(id) {
    Some(data) => digest(data).to_string() == id.to_string()
    None => false
  }
}

///|
/// Delete an unpinned blob. Returns false when live references still exist.
pub fn MemoryStore::delete(self : MemoryStore, id : Digest) -> Bool {
  let key = id.to_string()
  if self.pins.contains(key) {
    return false
  }
  let existed = self.blobs.contains(key)
  self.blobs.remove(key)
  self.pins.remove(key)
  existed
}

///|
pub fn MemoryStore::length(self : MemoryStore) -> Int {
  self.blobs.length()
}

///|
/// Add a live reference. Returns false when the blob is unknown.
pub fn MemoryStore::pin(self : MemoryStore, id : Digest) -> Bool {
  let key = id.to_string()
  if !self.blobs.contains(key) {
    return false
  }
  let count = self.pins.get(key).unwrap_or(0)
  self.pins.set(key, count + 1)
  true
}

///|
/// Release one live reference without allowing a negative count.
pub fn MemoryStore::unpin(self : MemoryStore, id : Digest) -> Unit {
  let key = id.to_string()
  match self.pins.get(key) {
    Some(count) if count > 1 => self.pins.set(key, count - 1)
    Some(_) => self.pins.remove(key)
    None => ()
  }
}

///|
/// Delete every unpinned blob and return the number collected.
pub fn MemoryStore::collect(self : MemoryStore) -> Int {
  let before = self.blobs.length()
  self.blobs.retain(fn(key, _) { self.pins.contains(key) })
  before - self.blobs.length()
}