///|
/// Evaluate one condition against a context.
pub fn Condition::matches(self : Condition, ctx : Context) -> Bool {
  match self.matcher {
    Exists => ctx.contains(self.attr)
    Missing => !ctx.contains(self.attr)
    Is(expected) =>
      match ctx.get(self.attr) {
        Some(actual) => actual == expected
        None => false
      }
    IsNot(expected) =>
      match ctx.get(self.attr) {
        Some(actual) => actual != expected
        None => true
      }
    OneOf(expected) =>
      match ctx.get(self.attr) {
        Some(actual) => expected.contains(actual)
        None => false
      }
    Contains(value) =>
      match ctx.get(self.attr) {
        Some(Str(actual)) => actual.contains(value)
        Some(Strings(values)) => values.contains(value)
        _ => false
      }
    ContainsAny(expected) =>
      match ctx.get(self.attr) {
        Some(Strings(values)) => any_string_overlap(values, expected)
        Some(Str(actual)) => expected.any(value => actual.contains(value))
        _ => false
      }
    StartsWith(prefix) =>
      match ctx.get(self.attr) {
        Some(Str(actual)) => actual.has_prefix(prefix)
        _ => false
      }
    EndsWith(suffix) =>
      match ctx.get(self.attr) {
        Some(Str(actual)) => actual.has_suffix(suffix)
        _ => false
      }
    GreaterThan(limit) =>
      match ctx.get(self.attr) {
        Some(Int(actual)) => actual > limit
        _ => false
      }
    GreaterEq(limit) =>
      match ctx.get(self.attr) {
        Some(Int(actual)) => actual >= limit
        _ => false
      }
    LessThan(limit) =>
      match ctx.get(self.attr) {
        Some(Int(actual)) => actual < limit
        _ => false
      }
    LessEq(limit) =>
      match ctx.get(self.attr) {
        Some(Int(actual)) => actual <= limit
        _ => false
      }
  }
}

///|
/// Evaluate all conditions with AND semantics.
pub fn all_conditions_match(
  conditions : ArrayView[Condition],
  ctx : Context,
) -> Bool {
  for condition in conditions {
    if !condition.matches(ctx) {
      return false
    }
  }
  true
}

///|
/// Evaluate any condition with OR semantics.
pub fn any_condition_matches(
  conditions : ArrayView[Condition],
  ctx : Context,
) -> Bool {
  for condition in conditions {
    if condition.matches(ctx) {
      return true
    }
  }
  false
}

///|
/// Evaluate a segment.
pub fn Segment::matches(self : Segment, ctx : Context) -> Bool {
  match ctx.user_key {
    Some(user) => {
      if self.exclude_users.contains(user) {
        return false
      }
      if self.include_users.contains(user) {
        return true
      }
    }
    None => ()
  }
  all_conditions_match(self.conditions, ctx)
}

///|
/// Return a compact explanation for why a condition passed or failed.
pub fn Condition::explain(self : Condition, ctx : Context) -> String {
  let state = if self.matches(ctx) { "pass" } else { "fail" }
  state + ":" + self.attr
}

///|
fn any_string_overlap(left : Array[String], right : Array[String]) -> Bool {
  for item in left {
    if right.contains(item) {
      return true
    }
  }
  false
}