///|
/// Abstract comparison emitted for a database or search-index adapter.
pub(all) enum SeekComparison {
  GreaterThan
  LessThan
} derive(Debug, Eq)

///|
pub struct EqualityTerm {
  field : String
  value : PageValue
  nulls : NullPlacement
} derive(Debug, Eq)

///|
/// One branch of a lexicographic keyset predicate. Branches are OR-ed; terms
/// in `equal_prefix` are AND-ed before the branch comparison.
pub struct SeekBranch {
  equal_prefix : Array[EqualityTerm]
  field : String
  value : PageValue
  comparison : SeekComparison
  nulls : NullPlacement
} derive(Debug, Eq)

///|
pub struct SeekPlan {
  branches : Array[SeekBranch]
  reverse_query_order : Bool
} derive(Debug, Eq)

///|
pub fn EqualityTerm::field(self : EqualityTerm) -> String {
  self.field
}

///|
pub fn EqualityTerm::value(self : EqualityTerm) -> PageValue {
  self.value
}

///|
pub fn EqualityTerm::nulls(self : EqualityTerm) -> NullPlacement {
  self.nulls
}

///|
pub fn SeekBranch::equal_prefix(self : SeekBranch) -> Array[EqualityTerm] {
  self.equal_prefix.copy()
}

///|
pub fn SeekBranch::field(self : SeekBranch) -> String {
  self.field
}

///|
pub fn SeekBranch::value(self : SeekBranch) -> PageValue {
  self.value
}

///|
pub fn SeekBranch::comparison(self : SeekBranch) -> SeekComparison {
  self.comparison
}

///|
pub fn SeekBranch::nulls(self : SeekBranch) -> NullPlacement {
  self.nulls
}

///|
pub fn SeekPlan::branches(self : SeekPlan) -> Array[SeekBranch] {
  self.branches.copy()
}

///|
pub fn SeekPlan::reverse_query_order(self : SeekPlan) -> Bool {
  self.reverse_query_order
}

///|
fn seek_comparison(
  direction : SortDirection,
  mode : PageMode,
) -> SeekComparison {
  match (direction, mode) {
    (Ascending, ForwardPage) | (Descending, BackwardPage) => GreaterThan
    (Descending, ForwardPage) | (Ascending, BackwardPage) => LessThan
  }
}

///|
/// Build `(k1 op v1) OR (k1=v1 AND k2 op v2) ...` without generating SQL.
/// The host maps fields and operators to its typed query API, avoiding string
/// interpolation and keeping this package storage-engine neutral.
pub fn build_seek_plan(
  position : PagePosition,
  sort : Array[SortField],
  mode : PageMode,
) -> Result[SeekPlan, PageError] {
  if !cursor_matches_sort(position, sort) {
    return Err(
      page_error(
        CursorSortMismatch,
        "cursor",
        "cursor ordering does not match seek plan sort",
      ),
    )
  }
  let branches : Array[SeekBranch] = []
  let prefix : Array[EqualityTerm] = []
  for index = 0; index < sort.length(); index = index + 1 {
    let field = sort[index]
    let part = position.parts[index]
    branches.push({
      equal_prefix: prefix.copy(),
      field: field.name,
      value: part.value,
      comparison: seek_comparison(field.direction, mode),
      nulls: field.nulls,
    })
    prefix.push({ field: field.name, value: part.value, nulls: field.nulls })
  }
  branches.push({
    equal_prefix: prefix,
    field: "id",
    value: TextValue(position.tie_breaker),
    comparison: seek_comparison(Ascending, mode),
    nulls: NullsLast,
  })
  Ok({ branches, reverse_query_order: mode is BackwardPage })
}