///|
/// Types that can enumerate all of their values in size-indexed order.
///
/// `enumerate()` returns an `Enumerate[Self]`: a lazy stream of parts
/// where the `k`-th part contains every value of size `k`. Primitive
/// types already implement this trait; algebraic composites (sums,
/// products, recursive types) compose via `singleton` / `+` /
/// `product` / `unary` wrapped in a single `pay`.
///
/// See the package README for the full contract, the built-in
/// instance table, and the recipe for implementing `Enumerable` on a
/// user-defined recursive type.
pub(open) trait Enumerable {
  enumerate() -> Enumerate[Self]
}

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

///|

///|
pub impl Enumerable for Int with enumerate() {
  letrec int_parts_from: (Int) -> @lazy.LazyList[Finite[Int]] = n => {
    Cons(
      fin_pure(n),
      @lazy.LazyRef::from_thunk(() => {
        Cons(
          fin_pure(-n),
          @lazy.LazyRef::from_thunk(() => int_parts_from(n + 1)),
        )
      }),
    )
  }

  {
    parts: Cons(fin_pure(0), @lazy.LazyRef::from_thunk(() => int_parts_from(1))),
  }
}

///|
pub impl Enumerable for Int64 with enumerate() {
  letrec int64_parts_from: (Int64) -> @lazy.LazyList[Finite[Int64]] = n => {
    Cons(
      fin_pure(n),
      @lazy.LazyRef::from_thunk(() => {
        Cons(
          fin_pure(-n),
          @lazy.LazyRef::from_thunk(() => int64_parts_from(n + 1)),
        )
      }),
    )
  }

  {
    parts: Cons(
      fin_pure(0L),
      @lazy.LazyRef::from_thunk(() => int64_parts_from(1L)),
    ),
  }
}

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

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

///|
pub impl Enumerable for Byte with enumerate() {
  let byte_card : BigInt = 256
  let byte_from_index = (i : BigInt) => Int::to_byte(i.to_int())
  {
    parts: Cons(
      { fCard: byte_card, fIndex: byte_from_index },
      @lazy.LazyRef::from_value(Nil),
    ),
  }
}

///|
pub impl Enumerable for Char with enumerate() {
  let char_card : BigInt = 1112064
  let char_from_index = (i : BigInt) => {
    let idx = i.to_int()
    let cp = if idx < 55296 { idx } else { idx + 2048 }
    Int::unsafe_to_char(cp)
  }
  {
    parts: Cons(
      { fCard: char_card, fIndex: char_from_index },
      @lazy.LazyRef::from_value(Nil),
    ),
  }
}

///|
pub impl[E : Enumerable] Enumerable for @moonbitlang/core/list.List[E] with enumerate() {
  consts(
    @list.from_array([
      singleton(@list.empty()),
      unary((pair : (E, @moonbitlang/core/list.List[E])) => pair.1.add(pair.0)),
    ]),
  )
}

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

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

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

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