///|
/// Decode an RFC 6901 JSON Pointer. The empty string selects the root.
/// URI fragment identifiers such as `#/a` are intentionally not accepted.
pub fn parse_pointer(path : String) -> Array[String] raise PatchError {
  let chars = path.to_array()
  if chars.length() > 16384 {
    patch_fail("limit_exceeded", -1, path, "pointer exceeds 16384 characters")
  }
  if chars.length() == 0 {
    return []
  }
  if chars[0] != '/' {
    patch_fail(
      "invalid_pointer", -1, path, "pointer must be empty or start with /",
    )
  }
  let tokens : Array[String] = []
  let mut token : Array[Char] = []
  let mut i = 1
  while i < chars.length() {
    match chars[i] {
      '/' => {
        if tokens.length() >= 128 {
          patch_fail("limit_exceeded", -1, path, "pointer exceeds 128 tokens")
        }
        tokens.push(String::from_array(token))
        token = []
      }
      '~' => {
        if i + 1 >= chars.length() {
          patch_fail("invalid_pointer", -1, path, "incomplete ~ escape")
        }
        i += 1
        match chars[i] {
          '0' => token.push('~')
          '1' => token.push('/')
          _ => patch_fail("invalid_pointer", -1, path, "invalid ~ escape")
        }
      }
      value => token.push(value)
    }
    i += 1
  }
  if tokens.length() >= 128 {
    patch_fail("limit_exceeded", -1, path, "pointer exceeds 128 tokens")
  }
  tokens.push(String::from_array(token))
  tokens
}

///|
/// Escape one object key for inclusion in a JSON Pointer.
pub fn escape_pointer_token(token : String) -> String {
  let escaped : Array[Char] = []
  for c in token {
    match c {
      '~' => {
        escaped.push('~')
        escaped.push('0')
      }
      '/' => {
        escaped.push('~')
        escaped.push('1')
      }
      _ => escaped.push(c)
    }
  }
  String::from_array(escaped)
}

///|
/// Build a bounded RFC 6901 pointer from already-decoded object keys or
/// array index tokens. The empty token list addresses the document root.
pub fn format_pointer(tokens : Array[String]) -> String raise PatchError {
  if tokens.length() > 128 {
    patch_fail("limit_exceeded", -1, "", "pointer exceeds 128 tokens")
  }
  let mut path = ""
  for token in tokens {
    path += "/" + escape_pointer_token(token)
  }
  if path.to_array().length() > 16384 {
    patch_fail("limit_exceeded", -1, path, "pointer exceeds 16384 characters")
  }
  path
}

///|
fn array_index(
  token : String,
  length : Int,
  allow_end : Bool,
  allow_dash : Bool,
  operation : Int,
  path : String,
) -> Int raise PatchError {
  if allow_dash && token == "-" {
    return length
  }
  let chars = token.to_array()
  if chars.length() == 0 || (chars.length() > 1 && chars[0] == '0') {
    patch_fail("invalid_index", operation, path, "array index is not canonical")
  }
  let mut value = 0
  for c in chars {
    if c < '0' || c > '9' {
      patch_fail(
        "invalid_index", operation, path, "array index must be decimal",
      )
    }
    let digit = c.to_int() - '0'.to_int()
    if value > 214748364 || (value == 214748364 && digit > 7) {
      patch_fail("invalid_index", operation, path, "array index overflows")
    }
    value = value * 10 + digit
  }
  if value > length || (!allow_end && value == length) {
    patch_fail("missing_path", operation, path, "array index is out of bounds")
  }
  value
}

///|
fn get_tokens(
  document : Json,
  tokens : Array[String],
  operation : Int,
  path : String,
) -> Json raise PatchError {
  let mut current = document
  for token in tokens {
    match current {
      Object(fields) =>
        match fields.get(token) {
          Some(value) => current = value
          None =>
            patch_fail(
              "missing_path", operation, path, "object member does not exist",
            )
        }
      Array(items) => {
        let index = array_index(
          token,
          items.length(),
          false,
          false,
          operation,
          path,
        )
        current = items[index]
      }
      _ =>
        patch_fail("missing_path", operation, path, "path traverses a scalar")
    }
  }
  current
}

///|
/// Retrieve the value selected by an RFC 6901 pointer.
pub fn pointer_get(document : Json, path : String) -> Json raise PatchError {
  get_tokens(document, parse_pointer(path), -1, path)
}