///|
/// Compiled jq query with private, immutable execution-plan storage.
pub struct Query {
  priv plan : @runtime.Plan
} derive(Debug)

///|
/// Preserve method-style access to the derived debug representation.
pub extend Query with @debug.Debug::{to_repr}

///|
/// Methods on Query for direct control.
pub fn Query::eval(self : Query, input : Json) -> Iter[Result[Json, EvalError]] {
  @runtime.eval(self.plan, input)
}

///|
/// Evaluate this compiled query against one JSON input and collect every value
/// it produces.
///
/// This is the eager counterpart to `Query::eval`. It is convenient when the
/// full result set is small enough to keep in memory, while `Query::eval`
/// should be preferred for callers that want to stream results.
///
/// Raises if evaluation fails, for example when the query applies an operation
/// to an incompatible JSON type.
pub fn Query::eval_all(
  self : Query,
  input : Json,
) -> Array[Json] raise EvalError {
  @runtime.eval_all(self.plan, input)
}

///|
/// Apply the query to newline-delimited JSON logs, skipping invalid lines.
pub fn Query::eval_logs(
  self : Query,
  logs : String,
) -> Iter[Result[Json, EvalError]] {
  let lines = logs.split("\n")
  let current : Ref[Iter[Result[Json, EvalError]]] = Ref(Iter::empty())
  let done = Ref(false)
  Iter::new(fn() {
    if done.val {
      return None
    }
    while true {
      if current.val.next() is Some(event) {
        if event is Err(_) {
          done.val = true
        }
        return Some(event)
      }
      guard lines.next() is Some(raw_line) else {
        done.val = true
        return None
      }
      let line = raw_line.strip_suffix("\r").unwrap_or(raw_line)
      let input = @json.parse(line) catch { _ => continue }
      current.val = self.eval(input)
    } nobreak {
      None
    }
  })
}