// A depth-first visitor over an Ion value tree.
//
// Ion has a single node type, `IonValue`, so `visit_value` is called for every
// value and callers match on `value.kind()`. The container callbacks and the
// annotation/field-name callbacks exist for the common cases. Every method has
// a default no-op implementation, so a visitor overrides only what it needs.

///|
/// A visitor for traversing an Ion value. Use [`IonValue::accept`] to begin
/// traversal.
pub(open) trait IonVisitor {
  /// Called for every value, before its annotations and children.
  fn visit_value(Self, IonValue) -> Unit = _
  /// Called for each annotation on a value, in order.
  fn visit_annotation(Self, SymbolToken) -> Unit = _
  /// Called for each struct field name, before its value.
  fn visit_field_name(Self, SymbolToken) -> Unit = _
  /// Called for a list value, before its elements.
  fn visit_list(Self, Array[IonValue]) -> Unit = _
  /// Called for an S-expression value, before its elements.
  fn visit_sexp(Self, Array[IonValue]) -> Unit = _
  /// Called for a struct value, before its fields.
  fn visit_struct(Self, Array[Field]) -> Unit = _
}

///|
impl IonVisitor with fn visit_value(_self, _value) {
  ()
}

///|
impl IonVisitor with fn visit_annotation(_self, _token) {
  ()
}

///|
impl IonVisitor with fn visit_field_name(_self, _token) {
  ()
}

///|
impl IonVisitor with fn visit_list(_self, _items) {
  ()
}

///|
impl IonVisitor with fn visit_sexp(_self, _items) {
  ()
}

///|
impl IonVisitor with fn visit_struct(_self, _fields) {
  ()
}

///|
/// Accept `visitor` on this value, traversing depth-first in document order:
/// the value, then its annotations, then — for containers — the child values
/// (struct field names precede their values). It works at any nesting depth.
pub fn IonValue::accept(self : IonValue, visitor : &IonVisitor) -> Unit {
  accept_shallow(self, visitor, @work.SHALLOW_DEPTH)
}

///|
/// Accept `visitor` with recursion while `levels` is above zero. At zero
/// levels a container switches to `accept_deep`; a scalar stays here. Recursion is the fastest walk; the limit keeps the call
/// stack small at any depth.
fn accept_shallow(
  value : IonValue,
  visitor : &IonVisitor,
  levels : Int32,
) -> Unit {
  if levels == 0 && value.kind is (List(_) | Sexp(_) | Struct(_)) {
    return accept_deep(value, visitor)
  }
  visitor.visit_value(value)
  for annotation in value.annotations {
    visitor.visit_annotation(annotation)
  }
  match value.kind {
    List(items) => {
      visitor.visit_list(items)
      for item in items {
        accept_shallow(item, visitor, levels - 1)
      }
    }
    Sexp(items) => {
      visitor.visit_sexp(items)
      for item in items {
        accept_shallow(item, visitor, levels - 1)
      }
    }
    Struct(fields) => {
      visitor.visit_struct(fields)
      for field in fields {
        visitor.visit_field_name(field.name)
        accept_shallow(field.value, visitor, levels - 1)
      }
    }
    _ => ()
  }
}

///|
/// Accept `visitor` with no recursion, on a `ValueWalker`.
fn accept_deep(value : IonValue, visitor : &IonVisitor) -> Unit {
  let walker = value.walker()
  while true {
    match walker.step() {
      AtScalar | AtEnter => ()
      AtExit => continue
      Done => break
    }
    let value = walker.current
    let field = walker.current_field
    if field is Some(name) {
      visitor.visit_field_name(name)
    }
    visitor.visit_value(value)
    for annotation in value.annotations() {
      visitor.visit_annotation(annotation)
    }
    match value.kind() {
      List(items) => visitor.visit_list(items)
      Sexp(items) => visitor.visit_sexp(items)
      Struct(fields) => visitor.visit_struct(fields)
      _ => ()
    }
  }
}

///|
/// Accept `visitor` on every value of a datagram, in order.
pub fn accept_values(values : Array[IonValue], visitor : &IonVisitor) -> Unit {
  for value in values {
    value.accept(visitor)
  }
}