///|
/// RFC 6901 escaping only; array indices are interpreted during traversal.
pub fn parse_pointer(pointer : String) -> Array[String] raise PlistError {
  if pointer.is_empty() {
    return []
  }
  if !pointer.has_prefix("/") {
    raise PathError("pointer must start with slash")
  }
  let tokens : Array[String] = []
  for raw in pointer[1:].split("/") {
    let out = StringBuilder()
    let s = raw.to_owned()
    let mut i = 0
    while i < s.length() {
      if s[i] == 126 {
        if i + 1 >= s.length() {
          raise PathError("truncated pointer escape")
        }
        match s[i + 1].to_int() {
          48 => out.write_char('~')
          49 => out.write_char('/')
          _ => raise PathError("pointer escape must be tilde-zero or tilde-one")
        }
        i += 2
      } else {
        let c = match s.get_char(i) {
          Some(c) => c
          None => raise PathError("invalid pointer Unicode")
        }
        out.write_char(c)
        i += if c.to_int() > 0xffff { 2 } else { 1 }
      }
    }
    tokens.push(out.to_string())
  }
  tokens
}

///|
pub fn format_pointer(tokens : Array[String]) -> String {
  let out = StringBuilder()
  for token in tokens {
    out.write_char('/')
    out.write_string(
      token.replace_all(old="~", new="~0").replace_all(old="/", new="~1"),
    )
  }
  out.to_string()
}

///|
fn array_index(
  token : String,
  length : Int32,
  append : Bool,
) -> Int32 raise PlistError {
  if append && token == "-" {
    return length
  }
  if token.is_empty() || (token.length() > 1 && token[0] == 48) {
    raise PathError("invalid array index spelling")
  }
  let mut n = 0
  for c in token {
    let d = c.to_int() - 48
    if d < 0 || d > 9 || n > (2147483647 - d) / 10 {
      raise PathError("array index overflow or syntax")
    }
    n = n * 10 + d
  }
  if n > length || (!append && n == length) {
    raise PathError("array index out of bounds")
  }
  n
}

///|
pub fn lookup(
  value : Value,
  pointer : String,
  limits~ : Limits = Limits::default(),
) -> Value raise PlistError {
  limits.check()
  if pointer.length() > limits.max_bytes {
    raise Limit("pointer byte budget")
  }
  let tokens = parse_pointer(pointer)
  if tokens.length() > limits.max_depth {
    raise Limit("pointer depth budget")
  }
  let mut v = value
  for token in tokens {
    v = match v {
      Dict(_) =>
        match v.get(token) {
          Some(item) => item
          None => raise PathError("dictionary key missing")
        }
      List(items) => items[array_index(token, items.length(), false)]
      _ => raise PathError("pointer traverses scalar")
    }
  }
  v
}