///|
fn numeric_digits(text : String) -> String? {
  let chars = text.to_array()
  let mut end = 0
  while end < chars.length() && chars[end].is_ascii_digit() {
    end += 1
  }
  if end == 0 {
    return None
  }
  let mut start = 0
  while start + 1 < end && chars[start] == '0' {
    start += 1
  }
  Some(chars_text(chars, start, end))
}

///|
fn ordered_compare(
  left : String,
  right : String,
  comparator : String,
  budget : Budget,
  span : Span,
) -> Int raise SieveError {
  budget.compare(left.length() + right.length() + 1, span)
  if comparator == "i;ascii-numeric" {
    match (numeric_digits(left), numeric_digits(right)) {
      (None, None) => 0
      (None, Some(_)) => 1
      (Some(_), None) => -1
      (Some(a), Some(b)) =>
        if a.length() < b.length() {
          -1
        } else if a.length() > b.length() {
          1
        } else {
          a.lexical_compare(b)
        }
    }
  } else if comparator == "i;ascii-casemap" {
    ascii_lower(left).lexical_compare(ascii_lower(right))
  } else {
    left.lexical_compare(right)
  }
}

///|
fn relation_holds(order : Int, relation : String) -> Bool {
  match relation {
    "gt" => order > 0
    "ge" => order >= 0
    "lt" => order < 0
    "le" => order <= 0
    "eq" => order == 0
    "ne" => order != 0
    _ => false
  }
}

///|
fn check_relational(
  a : Arguments,
  caps : Array[String],
  span : Span,
) -> Unit raise SieveError {
  let relation = match a.options.get("value") {
    Some(v) => Some(v)
    None => a.options.get("count")
  }
  match relation {
    Some(op) => {
      needs(caps, "relational", span)
      if !["gt", "ge", "lt", "le", "eq", "ne"].contains(op) {
        fail("check.relation", "invalid relational operator", span)
      }
    }
    None => ()
  }
  if a.options.get("comparator") == Some("i;ascii-numeric") {
    needs(caps, "comparator-i;ascii-numeric", span)
    if a.flags.contains("contains") || a.flags.contains("matches") {
      fail(
        "check.numeric_match", "numeric comparator does not support contains/matches",
        span,
      )
    }
  }
}

///|
fn Runtime::match_collection(
  self : Runtime,
  values : Array[String],
  keys : Array[String],
  a : Arguments,
  span : Span,
) -> Bool raise SieveError {
  let comparator = a.options.get("comparator").unwrap_or("i;ascii-casemap")
  let relation = match a.options.get("count") {
    Some(v) => Some(v)
    None => a.options.get("value")
  }
  let candidates = if a.flags.contains("count") {
    [values.length().to_string()]
  } else {
    values
  }
  for value in candidates {
    for key in keys {
      self.budget.step(span)
      match relation {
        Some(op) =>
          if relation_holds(
              ordered_compare(value, key, comparator, self.budget, span),
              op,
            ) {
            return true
          }
        None =>
          if comparator == "i;ascii-numeric" {
            if ordered_compare(value, key, comparator, self.budget, span) == 0 {
              return true
            }
          } else if self.matching(value, key, a, span) {
            return true
          }
      }
    }
  }
  false
}