// Metadata filtering, expressed as data so it can travel: a store applies it where the documents live
// (in memory, in SQL, in pgvector) instead of the caller re-ranking afterwards. Every store reads the
// same tree, so a filter written for one store means the same in another.

///|
/// How `Compare` orders a metadata number against its operand.
#derive(Eq, Debug)
pub(all) enum Order {
  Less
  AtMost
  AtLeast
  Greater
}

///|
/// A condition on a document's metadata.
///
/// `Equal` matches a whole JSON value, `OneOf` any of a list, `Exists` only that the key is present,
/// and `Compare` a number against an operand. `All` and `Any` nest, `Not` negates. A document whose
/// metadata lacks the key matches nothing except `Not` of a filter it would otherwise fail.
#derive(Eq, Debug)
pub(all) enum Filter {
  Equal(String, Json)
  OneOf(String, Array[Json])
  Exists(String)
  Compare(String, Order, Double)
  All(Array[Filter])
  Any(Array[Filter])
  Not(Filter)
}

///|
/// Whether `document` satisfies the filter.
pub fn Filter::matches(self : Filter, document : Document) -> Bool {
  match self {
    Equal(key, want) =>
      match document.meta.get(key) {
        Some(got) => got == want
        None => false
      }
    OneOf(key, wants) =>
      match document.meta.get(key) {
        Some(got) => wants.iter().any(fn(want) { want == got })
        None => false
      }
    Exists(key) => document.meta.contains(key)
    Compare(key, order, want) =>
      match document.meta.get(key) {
        Some(Json::Number(got, ..)) =>
          match order {
            Less => got < want
            AtMost => got <= want
            AtLeast => got >= want
            Greater => got > want
          }
        Some(_) => false
        None => false
      }
    All(filters) => filters.iter().all(fn(filter) { filter.matches(document) })
    Any(filters) => filters.iter().any(fn(filter) { filter.matches(document) })
    Not(filter) => !filter.matches(document)
  }
}

///|
pub extend Order with Eq::{not_equal, equal}

///|
pub extend Order with Debug::{to_repr}

///|
pub extend Filter with Eq::{not_equal, equal}

///|
pub extend Filter with Debug::{to_repr}