///|
/// A session owns a persistent chart; each successful push only adds a new chart.
pub struct Session {
  engines : Array[Engine]
}

///|
/// Deep clone supports transactional resource failures and independent forks.
fn Engine::clone(self : Engine) -> Engine {
  {
    grammar: self.grammar,
    limits: self.limits,
    charts: self.charts.map(c => { items: c.items.copy(), cursor: c.cursor, }),
    nodes: self.nodes.map(n => {
      kind: n.kind,
      start: n.start,
      end: n.end,
      families: n.families.map(copy_family),
    }),
    tokens: self.tokens.copy(),
    item_count: self.item_count,
    family_count: self.family_count,
    work: self.work,
  }
}

///|
/// Opening a session can fail during nullable closure before the first token.
pub fn Session::new(
  grammar : Grammar,
  limits~ : Limits = default_limits(),
) -> Result[Session, Problem] {
  match limits.validate() {
    Err(p) => return Err(p)
    Ok(_) => ()
  }
  Ok({ engines: [Engine::new(grammar, limits)], }) catch {
    Halt(p) => Err(p)
  }
}

///|
/// Validate and append one token atomically; failed appends leave statistics unchanged.
pub fn Session::push(self : Session, token : Token) -> Result[Report, Problem] {
  let previous = self.engines[0].tokens
  let index = previous.length()
  if token.kind == "" || token.start < 0 || token.end < token.start {
    return Err(InvalidToken(index, "invalid kind or character span"))
  }
  if index > 0 && token.start < previous[index - 1].end {
    return Err(
      InvalidToken(index, "overlapping or nonmonotonic character span"),
    )
  }
  let next = self.engines[0].clone()
  try {
    next.append(token)
    self.engines[0] = next
    Ok(next.report())
  } catch {
    Halt(p) => Err(p)
  }
}

///|
/// Append a batch atomically. Input can start at any later character offset.
pub fn Session::push_all(
  self : Session,
  tokens : Array[Token],
) -> Result[Report, Problem] {
  let fork = self.fork()
  for token in tokens {
    match fork.push(token) {
      Err(p) => return Err(p)
      Ok(_) => ()
    }
  }
  self.engines[0] = fork.engines[0]
  Ok(self.report())
}

///|
/// Snapshots and forked sessions have no shared mutable chart or family arrays.
pub fn Session::fork(self : Session) -> Session {
  { engines: [self.engines[0].clone()], }
}

///|
/// Reports copy the packed forest and remain valid after future appends.
pub fn Session::report(self : Session) -> Report {
  self.engines[0].report()
}