///|
/// Structural events emitted in document order for streaming-style consumers.
pub enum JsonEvent {
  BeginObject(String, Int)
  EndObject(String)
  BeginArray(String, Int)
  EndArray(String)
  Scalar(String, String, String)
}

///|
pub fn walk_events(value : JsonValue) -> Array[JsonEvent] {
  let events : Array[JsonEvent] = []
  walk_value(value, "", events)
  events
}

///|
pub fn JsonEvent::to_text(self : JsonEvent) -> String {
  match self {
    BeginObject(path, count) =>
      "begin-object " + path + " (" + count.to_string() + ")"
    EndObject(path) => "end-object " + path
    BeginArray(path, count) =>
      "begin-array " + path + " (" + count.to_string() + ")"
    EndArray(path) => "end-array " + path
    Scalar(path, kind, value) => "scalar " + path + " " + kind + " " + value
  }
}

///|
pub fn JsonEvent::to_json(self : JsonEvent) -> String {
  match self {
    BeginObject(path, count) =>
      "{\"event\":\"begin-object\",\"path\":" +
      canonical_string(path) +
      ",\"count\":" +
      count.to_string() +
      "}"
    EndObject(path) =>
      "{\"event\":\"end-object\",\"path\":" + canonical_string(path) + "}"
    BeginArray(path, count) =>
      "{\"event\":\"begin-array\",\"path\":" +
      canonical_string(path) +
      ",\"count\":" +
      count.to_string() +
      "}"
    EndArray(path) =>
      "{\"event\":\"end-array\",\"path\":" + canonical_string(path) + "}"
    Scalar(path, kind, value) =>
      "{\"event\":\"scalar\",\"path\":" +
      canonical_string(path) +
      ",\"kind\":" +
      canonical_string(kind) +
      ",\"value\":" +
      value +
      "}"
  }
}

///|
pub fn events_text(events : Array[JsonEvent]) -> String {
  let mut output = ""
  for event in events {
    if output != "" {
      output = output + "\n"
    }
    output = output + event.to_text()
  }
  output
}

///|
fn walk_value(
  value : JsonValue,
  path : String,
  events : Array[JsonEvent],
) -> Unit {
  match value {
    Null => events.push(Scalar(path, "null", "null"))
    Bool(flag) =>
      events.push(Scalar(path, "boolean", if flag { "true" } else { "false" }))
    Number(number) =>
      events.push(Scalar(path, "number", value_preview(Number(number))))
    String(string) =>
      events.push(Scalar(path, "string", canonical_string(string)))
    Array(values) => {
      events.push(BeginArray(path, values.length()))
      let mut index = 0
      while index < values.length() {
        walk_value(values[index], path + "/" + index.to_string(), events)
        index = index + 1
      }
      events.push(EndArray(path))
    }
    Object(entries) => {
      events.push(BeginObject(path, entries.length()))
      for entry in entries {
        walk_value(entry.1, path + "/" + pointer_escape_local(entry.0), events)
      }
      events.push(EndObject(path))
    }
  }
}