///|
/// A typed family of named cells. Each family has its own name space.
/// Keep this object alive and reuse it across edits.
pub struct Cells[T] {
  priv engine : Engine
  priv mut named : Map[(Array[Name], Name), Art[T]]
  priv mut interned : Map[(Array[Name], T), Art[T]]
}

///|
pub fn[T] Cells::Cells(engine : Engine) -> Cells[T] {
  engine.check_context()
  let cells = {
    engine,
    named: Default::default(),
    interned: Default::default(),
  }
  engine.releases.push(() => {
    cells.named = Default::default()
    cells.interned = Default::default()
  })
  cells
}

///|
/// Allocate or update a named cell. Inside a thunk, records an allocation edge:
/// if someone changes the cell, the allocator must run again to restore it.
pub fn[T : Eq] Cells::cell(self : Cells[T], name : Name, value : T) -> Art[T] {
  let e = self.engine
  e.check_context()
  let key = (e.path.val, name)
  let art = match self.named.get(key) {
    Some(art) => {
      art.write(value)
      art
    }
    None => {
      let art = e.cell(value)
      self.named[key] = art
      art
    }
  }
  e.observe(art.node, () => art.value != Some(value), true, Allocate)
  art
}

///|
/// Hash-cons an immutable value using both hash and equality, within this namespace.
pub fn[T : Hash + Eq] Cells::intern(self : Cells[T], value : T) -> Art[T] {
  let e = self.engine
  e.check_context()
  let key = (e.path.val, value)
  match self.interned.get(key) {
    Some(art) => art
    None => {
      let art = e.put(value)
      self.interned[key] = art
      art
    }
  }
}