///|
pub fn compile(
  source : String,
  limits? : Limits = default_limits(),
) -> Compiled raise JmesPathError {
  { source, expr: parse(source, limits), limits, }
}

///|
pub fn Compiled::search(
  self : Compiled,
  data : Json,
) -> Json raise JmesPathError {
  let state = { limits: self.limits, steps: 0, results: 0, }
  evaluate(self.expr, data, state)
}

///|
pub fn search(
  expression : String,
  data : Json,
  limits? : Limits = default_limits(),
) -> Json raise JmesPathError {
  compile(expression, limits~).search(data)
}

///|
pub fn search_json(
  expression : String,
  json_text : String,
  limits? : Limits = default_limits(),
) -> Json raise JmesPathError {
  let data = @json.parse(json_text) catch {
    _ => fail("invalid_json", 0, "input is not valid JSON")
  }
  search(expression, data, limits~)
}

///|
/// Runs independent queries from a JSON array of `{expression, data}` objects.
/// Errors are returned per item, so one invalid query does not abort the batch.
pub fn search_batch(
  requests : Json,
  limits? : Limits = default_limits(),
) -> Json raise JmesPathError {
  guard requests is Array(items) else {
    fail("invalid_batch", 0, "batch input must be a JSON array")
  }
  if items.length() > 10000 {
    fail("limit_exceeded", 0, "batch exceeds 10000 requests")
  }
  let responses : Array[Json] = []
  for item in items {
    guard item is Object(request) &&
      request.get("expression") is Some(String(expression)) &&
      request.get("data") is Some(data) else {
      fail("invalid_batch", 0, "each request needs string expression and data")
    }
    let response = Json::object(
      Map([
        ("ok", Json::boolean(true)),
        ("result", search(expression, data, limits~)),
      ]),
    ) catch {
      JmesPathError(diagnostic) =>
        Json::object(
          Map([
            ("ok", Json::boolean(false)),
            (
              "error",
              Json::object(
                Map([
                  ("code", Json::string(diagnostic.code)),
                  ("offset", Json::number(diagnostic.offset.to_double())),
                  ("message", Json::string(diagnostic.message)),
                ]),
              ),
            ),
          ]),
        )
    }
    responses.push(response)
  }
  Json::array(responses)
}