///|
/// Node ID is array index + 2; children must reference earlier IDs.
pub(all) struct NodeRecord {
  variable : Int32
  low : Int32
  high : Int32
}

///|
/// Versioned, self-contained interchange. Terminals 0 and 1 are implicit.
pub(all) struct GraphData {
  format : Int32
  universe : Int32
  root : Int32
  nodes : Array[NodeRecord]
}

///|
/// Returns fresh arrays; mutating an exported graph cannot affect the Family.
pub fn Family::to_graph(self : Family) -> GraphData {
  let nodes : Array[NodeRecord] = []
  for id in 2.. Family raise ZddError {
  let e = engine(data.universe, limits)
  if data.format != 1 {
    raise Invalid("unsupported graph format")
  }
  if data.nodes.length() > limits.nodes - 2 {
    raise BudgetExceeded
  }
  let size = data.nodes.length() + 2
  if data.root < 0 || data.root >= size {
    raise Invalid("invalid root ID")
  }
  for index in 0..= data.universe {
      raise Invalid("invalid variable")
    }
    if node.low < 0 || node.low >= id || node.high <= 0 || node.high >= id {
      raise Invalid("children must precede parents; high must be nonzero")
    }
    if e.nodes[node.low].variable <= node.variable ||
      e.nodes[node.high].variable <= node.variable {
      raise Invalid("variables must increase along edges")
    }
    let key = (node.variable, node.low, node.high)
    if e.unique.contains(key) {
      raise Invalid("duplicate node triple")
    }
    let made = e.make(node.variable, node.low, node.high)
    if made != id {
      raise Invalid("noncanonical node")
    }
  }
  let seen = Array::make(size, false)
  let pending = [data.root]
  let mut reachable = 0
  while pending.length() > 0 {
    e.tick()
    let id = pending.pop().unwrap()
    if id <= 1 || seen[id] {
      continue
    }
    seen[id] = true
    reachable += 1
    let node = e.nodes[id]
    pending.push(node.low)
    pending.push(node.high)
  }
  if reachable != data.nodes.length() {
    raise Invalid("unreachable nodes are forbidden")
  }
  e.finish(data.root)
}