///|
priv struct QueryToken {
  field : String
  selector : QuerySelector
}

///|
priv enum QuerySelector {
  NoSelector
  Wildcard
  Index(Int)
  Invalid
}

///|
fn query_token(text : String) -> QueryToken {
  match text.find("[") {
    None => { field: text, selector: NoSelector }
    Some(open) =>
      if !text.has_suffix("]") || open == 0 {
        { field: "", selector: Invalid }
      } else {
        let field = text.unsafe_substring(start=0, end=open)
        let selector_text = text.unsafe_substring(
          start=open + 1,
          end=text.length() - 1,
        )
        let selector = if selector_text == "*" {
          Wildcard
        } else {
          match parse_index(selector_text) {
            Some(index) => Index(index)
            None => Invalid
          }
        }
        { field, selector }
      }
  }
}

///|
fn query_children(value : Json, token : QueryToken) -> Array[Json] {
  let selected : Array[Json] = []
  let base = if token.field == "" {
    Some(value)
  } else {
    object_get(value, token.field)
  }
  match base {
    None => ()
    Some(value) =>
      match token.selector {
        NoSelector => selected.push(value)
        Wildcard =>
          match value {
            Array(items) => selected.append(items[:])
            _ => ()
          }
        Index(index) =>
          match value {
            Array(items) =>
              match items.get(index) {
                Some(item) => selected.push(item)
                None => ()
              }
            _ => ()
          }
        Invalid => ()
      }
  }
  selected
}

///|
fn query_walk(
  values : Array[Json],
  parts : Array[String],
  index : Int,
) -> Array[Json] {
  if index >= parts.length() {
    values
  } else {
    let next : Array[Json] = []
    let token = query_token(parts[index])
    if token.selector is Invalid {
      []
    } else {
      for value in values {
        next.append(query_children(value, token)[:])
      }
      query_walk(next, parts, index + 1)
    }
  }
}

///|
pub fn query_values(value : Json, expression : String) -> Array[Json] {
  if expression == "" {
    [value]
  } else if expression.has_prefix(".") || expression.has_suffix(".") {
    []
  } else {
    let parts = expression
      .split(".")
      .map(fn(part) { part.to_owned() })
      .to_array()
    query_walk([value], parts, 0)
  }
}

///|
pub fn query_strings(value : Json, expression : String) -> Array[String] {
  query_values(value, expression).filter_map(fn(value) { as_scalar_text(value) })
}

///|
pub fn query_first(value : Json, expression : String) -> Json? {
  match query_values(value, expression).get(0) {
    Some(item) => Some(item)
    None => None
  }
}

///|
pub fn query_exists(value : Json, expression : String) -> Bool {
  query_values(value, expression).length() > 0
}

///|
pub fn query_count(value : Json, expression : String) -> Int {
  query_values(value, expression).length()
}

///|
pub fn query_text(
  value : Json,
  expression : String,
  fallback? : String = "",
) -> String {
  match query_strings(value, expression).get(0) {
    Some(text) => text
    None => fallback
  }
}

///|
pub fn query_paths(value : Json, expression : String) -> Array[(String, Json)] {
  let output : Array[(String, Json)] = []
  let values = query_values(value, expression)
  for index, item in values {
    let _ = index
    output.push(("\{expression}[\{index}]", item))
  }
  output
}

///|
pub fn query_all_strings(
  value : Json,
  expressions : Array[String],
) -> Map[String, Array[String]] {
  let output : Map[String, Array[String]] = Map([])
  for expression in expressions {
    output[expression] = query_strings(value, expression)
  }
  output
}

///|
pub fn query_resource_references(value : Json) -> Array[String] {
  query_strings(value, "subject.reference")
  .add(query_strings(value, "patient.reference"))
  .add(query_strings(value, "performer[*].reference"))
  .add(query_strings(value, "basedOn[*].reference"))
  .add(query_strings(value, "partOf.reference"))
}