///|
pub(open) trait Enumerable {
  enumerate() -> Enumerate[Self]
}

///|
pub impl Enumerable for Bool with enumerate() {
  pay(fn() { singleton(true) + singleton(false) })
}

///|
pub impl Enumerable for Int with enumerate() {
  pay(fn() {
    singleton(0) +
    Enumerable::enumerate().fmap(fn(x) { x + 1 }) +
    Enumerable::enumerate().fmap(fn(x) { x - 1 })
  })
}

///|
pub impl Enumerable for Int64 with enumerate() {
  pay(fn() {
    singleton(0L) +
    Enumerable::enumerate().fmap(fn(x) { x + 1L }) +
    Enumerable::enumerate().fmap(fn(x) { x - 1L })
  })
}

///|
pub impl Enumerable for UInt with enumerate() {
  pay(fn() { singleton(0U) + Enumerable::enumerate().fmap(fn(x) { x + 1U }) })
}

///|
pub impl Enumerable for UInt64 with enumerate() {
  pay(fn() { singleton(0UL) + Enumerable::enumerate().fmap(fn(x) { x + 1UL }) })
}

///|
pub impl[E : Enumerable] Enumerable for @list.T[E] with enumerate() {
  consts(
    @list.of([
      singleton(@list.empty()),
      unary(@utils.pair_function(fn(e : E, lst : @list.T[E]) { lst.add(e) })),
    ]),
  )
}

///|
pub impl[A : Enumerable, B : Enumerable] Enumerable for (A, B) with enumerate() {
  pay(fn() { product(A::enumerate(), B::enumerate()) })
}

///|
pub impl[E : Enumerable] Enumerable for E? with enumerate() {
  pay(fn() { singleton(None) + E::enumerate().fmap(Option::Some(_)) })
}

///|
pub impl[T : Enumerable, E : Enumerable] Enumerable for Result[T, E] with enumerate() {
  pay(fn() {
    E::enumerate().fmap(Result::Err(_)) + T::enumerate().fmap(Result::Ok(_))
  })
}

///|
pub impl Enumerable for Unit with enumerate() {
  singleton(())
}