///|
/// A selected subtree retained with the pointer that identified it.
pub(all) struct SelectedValue {
  path : String
  value : JsonValue
}

///|
pub fn select_many(
  value : JsonValue,
  pointers : Array[String],
) -> Result[Array[SelectedValue], String] {
  let selected : Array[SelectedValue] = []
  for pointer in pointers {
    match select_pointer(value, pointer) {
      Ok(child) => selected.push({ path: pointer, value: child })
      Err(error) => return Err(pointer + ": " + error)
    }
  }
  Ok(selected)
}

///|
/// Project selected values into a deterministic array of `{path,value}`
/// records. This preserves pointer order because callers often use it to
/// generate small review artifacts from a large document.
pub fn project_document(
  input : String,
  pointers : Array[String],
) -> Result[String, String] {
  let value = match parse(input) {
    Ok(value) => value
    Err(error) => return Err(error.message())
  }
  let selected = match select_many(value, pointers) {
    Ok(selected) => selected
    Err(error) => return Err(error)
  }
  let mut output = "["
  let mut first = true
  for item in selected {
    let serialized = match canonicalize(item.value) {
      Ok(serialized) => serialized
      Err(error) => return Err(error.message())
    }
    if !first {
      output = output + ","
    }
    first = false
    output = output +
      "{\"path\":" +
      canonical_string(item.path) +
      ",\"value\":" +
      serialized +
      "}"
  }
  Ok(output + "]")
}

///|
pub fn SelectedValue::to_text(self : SelectedValue) -> String {
  self.path + " = " + value_preview(self.value)
}

///|
pub fn selected_text(values : Array[SelectedValue]) -> String {
  let mut output = ""
  for value in values {
    if output != "" {
      output = output + "\n"
    }
    output = output + value.to_text()
  }
  output
}

///|
pub fn scalar_paths(value : JsonValue) -> Array[String] {
  let paths : Array[String] = []
  collect_scalar_paths(value, "", paths)
  paths
}

///|
fn collect_scalar_paths(
  value : JsonValue,
  path : String,
  paths : Array[String],
) -> Unit {
  match value {
    Array(values) => {
      let mut index = 0
      while index < values.length() {
        collect_scalar_paths(
          values[index],
          path + "/" + index.to_string(),
          paths,
        )
        index = index + 1
      }
    }
    Object(entries) =>
      for entry in entries {
        collect_scalar_paths(
          entry.1,
          path + "/" + pointer_escape_local(entry.0),
          paths,
        )
      }
    _ => paths.push(path)
  }
}