///|
fn json_kind(value : Json) -> JsonKind {
  match value {
    Json::Null => JsonKind::Null
    Json::True | Json::False => JsonKind::Boolean
    Json::Number(_, ..) => JsonKind::Number
    Json::String(_) => JsonKind::String
    Json::Array(_) => JsonKind::Array
    Json::Object(_) => JsonKind::Object
  }
}

///|
fn lookup(document : Json, pointer : Pointer) -> Json? raise LensError {
  let mut current = document
  for segment_index, segment in pointer.segments {
    match segment {
      Key(key) => {
        guard current is Json::Object(properties) else {
          raise LensError(Issue::{
            pointer: pointer.prefix(segment_index),
            code: TypeMismatch(
              expected=JsonKind::Object,
              actual=json_kind(current),
            ),
            message: None,
          })
        }
        guard properties.get(key) is Some(value) else {
          let depth = segment_index + 1
          if depth == pointer.length() {
            return None
          }
          raise LensError(Issue::{
            pointer: pointer.prefix(depth),
            code: MissingProperty,
            message: None,
          })
        }
        current = value
      }
      Index(index) => {
        guard current is Json::Array(items) else {
          raise LensError(Issue::{
            pointer: pointer.prefix(segment_index),
            code: TypeMismatch(
              expected=JsonKind::Array,
              actual=json_kind(current),
            ),
            message: None,
          })
        }
        let length = items.length()
        guard index >= 0 && index < length else {
          raise LensError(Issue::{
            pointer: pointer.prefix(segment_index + 1),
            code: IndexOutOfBounds(index~, length~),
            message: None,
          })
        }
        current = items[index]
      }
    }
  }
  Some(current)
}