///|
/// Select a value using an RFC 6901 JSON Pointer. The empty pointer selects
/// the document root; object keys use `~0` and `~1` escaping.
pub fn select_pointer(
  value : JsonValue,
  pointer : String,
) -> Result[JsonValue, String] {
  let segments = match parse_pointer(pointer) {
    Ok(segments) => segments
    Err(error) => return Err(error)
  }
  let mut current = value
  for segment in segments {
    current = match select_segment(current, segment) {
      Ok(next) => next
      Err(error) => return Err(error)
    }
  }
  Ok(current)
}

///|
pub fn select_document(
  input : String,
  pointer : String,
) -> Result[JsonValue, String] {
  let value = match parse(input) {
    Ok(value) => value
    Err(error) => return Err(error.message())
  }
  select_pointer(value, pointer)
}

///|
/// Return canonical path/value pairs in depth-first order. This is useful for
/// audit UIs that need to show exactly which values contributed to a digest.
pub fn enumerate_paths(value : JsonValue) -> Array[(String, String)] {
  let paths : Array[(String, String)] = []
  enumerate_value(value, "", paths)
  paths
}

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

///|
fn parse_pointer(pointer : String) -> Result[Array[String], String] {
  if pointer == "" {
    return Ok([])
  }
  let chars : Array[Char] = []
  for char in pointer {
    chars.push(char)
  }
  if chars.length() == 0 || chars[0] != '/' {
    return Err("JSON Pointer must be empty or start with '/'")
  }
  let segments : Array[String] = []
  let mut current = ""
  let mut index = 1
  while index <= chars.length() {
    if index == chars.length() || chars[index] == '/' {
      segments.push(current)
      current = ""
      index = index + 1
    } else if chars[index] == '~' {
      if index + 1 >= chars.length() {
        return Err("JSON Pointer escape '~' must be followed by 0 or 1")
      }
      if chars[index + 1] == '0' {
        current = current + "~"
      } else if chars[index + 1] == '1' {
        current = current + "/"
      } else {
        return Err("JSON Pointer escape '~' must be followed by 0 or 1")
      }
      index = index + 2
    } else {
      current = current + chars[index].to_string()
      index = index + 1
    }
  }
  Ok(segments)
}

///|
fn select_segment(
  value : JsonValue,
  segment : String,
) -> Result[JsonValue, String] {
  match value {
    Object(entries) => {
      for entry in entries {
        if entry.0 == segment {
          return Ok(entry.1)
        }
      }
      Err("object key not found: " + segment)
    }
    Array(values) => {
      let index = match parse_array_index(segment) {
        Some(index) => index
        None =>
          return Err("array pointer segment is not a valid index: " + segment)
      }
      if index < values.length() {
        Ok(values[index])
      } else {
        Err("array index out of range: " + segment)
      }
    }
    _ => Err("cannot select '" + segment + "' from a scalar value")
  }
}

///|
fn parse_array_index(segment : String) -> Int? {
  if segment == "" {
    return None
  }
  if segment.length() > 1 && segment[0] == '0' {
    return None
  }
  let index = @string.parse_int(segment) catch { _ => return None }
  if index < 0 {
    None
  } else {
    Some(index)
  }
}

///|
fn pointer_escape_local(value : String) -> String {
  let mut output = ""
  for char in value {
    if char == '~' {
      output = output + "~0"
    } else if char == '/' {
      output = output + "~1"
    } else {
      output = output + char.to_string()
    }
  }
  output
}