///|
/// Distinguishes an omitted property from null and a present value.
///
/// ```mbt check
/// test {
///   let value = @json.Presence::from_option(Some(3))
///   assert_eq(value.map(n => n + 1).to_option(), Some(4))
///   assert_false(value.is_absent())
/// }
/// ```
pub(all) enum Presence[T] {
  Absent
  Null
  Value(T)
} derive(Eq, Debug)

///|
/// Compares presence and, for Value, the contained value.
pub extend Presence with Eq::{equal, not_equal}

///|
/// Formats the presence state and its value for debugging.
pub extend Presence with @debug.Debug::{to_repr}

///|
/// Collapses both omission and null to None.
pub fn[T] Presence::to_option(self : Presence[T]) -> T? {
  match self {
    Value(value) => Some(value)
    Absent | Null => None
  }
}

///|
/// Represents None as omission.
pub fn[T] Presence::from_option(value : T?) -> Presence[T] {
  match value {
    Some(value) => Value(value)
    None => Absent
  }
}

///|
/// Transforms only a present value, preserving omission and null.
pub fn[T, U] Presence::map(self : Presence[T], f : (T) -> U) -> Presence[U] {
  match self {
    Value(value) => Value(f(value))
    Absent => Absent
    Null => Null
  }
}

///|
/// Reports omission, as distinct from an explicit null.
pub fn[T] Presence::is_absent(self : Presence[T]) -> Bool {
  self is Absent
}