///|
#external
pub type Identity

///|
#external
pub(all) type IReader

///|
pub fn[R, A, B] IReader::map(ab : (R) -> A, bc : (A) -> B) -> (R) -> B {
  fn(a) { a |> ab |> bc }
}

///|
pub fn[R, A, B] IReader::chain(ab : (R) -> A, bc : (A) -> (R) -> B) -> (R) -> B {
  fn(a) { (a |> ab |> bc)(a) }
}

///|
pub fn[N, A] constant(a : A) -> (N) -> A {
  fn(_n) { a }
}

///|
pub fn[N, A] const_identity(_n : N) -> (A) -> A {
  identity
}

///|
pub fn[I : Show] constant_as_show(a : I) -> &Show {
  a
}

///|
pub fn[N, N2, A] constant_2(a : A) -> (N, N2) -> A {
  fn(_n, _n2) { a }
}

///|
pub fn[N, A] panic_with(_n : N) -> A {
  panic()
}

///|
pub fn[A] constant_0(a : A) -> () -> A {
  fn() { a }
}

///|
pub fn[A, E : Error] raise_exn(a : E) -> A raise E {
  raise a
}

///|
pub fn[R, A, E : Error] constant_exn(a : E) -> (R) -> A raise E {
  fn(_r) { raise a }
}

///|
pub fn[A, E : Error] constant_exn_0(a : E) -> () -> A raise E {
  fn() { raise a }
}

///|
pub fn[A] constant_none() -> A? {
  None
}

///|
pub fn[R, A] constant_from_lazy(a : Lazy[A]) -> (R) -> A {
  fn(_r) { a.get() }
}

///|
pub fn[A] constant_0_from_lazy(a : Lazy[A]) -> () -> A {
  fn() { a.get() }
}

///|
pub fn[A] const_empty_string(_arg : A) -> String {
  ""
}

///|
pub fn[A] const_zero(_arg : A) -> Int {
  0
}

///|
pub fn[A] const_one(_arg : A) -> Int {
  1
}

///|
pub fn[A] const_true(_arg : A) -> Bool {
  true
}

///|
pub fn[A] const_false(_a : A) -> Bool {
  false
}

///|
pub fn[A, B] const_empty_array(_a : A) -> Array[B] {
  []
}

///|
pub fn[R, A] lift_arg(f : () -> A) -> (R) -> A {
  fn(_) { f() }
}

///|
pub fn[R, R2, A] lift_arg_0_2(f : () -> A) -> (R, R2) -> A {
  fn(_, _) { f() }
}

///|
pub fn[R, R2, A] lift_arg_2(f : (R) -> A) -> (R, R2) -> A {
  fn(r, _) { f(r) }
}

///|
pub fn[R, A, E : Error] lift_exn(f : (R) -> A) -> (R) -> A raise E {
  fn(r) { f(r) }
}

///|
pub fn[A, E : Error] lift_exn_0(f : () -> A) -> () -> A raise E {
  fn() { f() }
}

// ///|
// pub fn const_unit[A](_ : A) -> Unit {

// }

///|
pub fn[A] const_unit(_ : A) -> Unit = "%ignore"

///|
pub fn const_unit_0() -> Unit {

}

///|
pub fn[A, B] ignore2(_a : A, _b : B) -> Unit {

}

///|
pub fn[A : Eq] curried_eq(a : A) -> (A) -> Bool {
  curried_first(A::op_equal, a)
}

///|
pub fn[A : Eq] curried_eq_not(a : A) -> (A) -> Bool {
  curried_not(A::op_equal, a)
  // fn(b) { a != b }
}

///|
pub fn[A : Eq] curried_one_of(a : Many[A]) -> (A) -> Bool {
  curried_second(Many::contains, a)
  // fn(b) { (a.contains(b)) }
}

///|
pub fn[A : Eq] curried_one_of_not(a : Many[A]) -> (A) -> Bool {
  curried_not(Many::contains, a)
  // fn(b) { not(a.contains(b)) }
}

///|
pub fn[A : Semigroup] curried_add_pre(a : A) -> (A) -> A {
  curried_second(A::op_add, a)
}

///|
pub fn[A : Semigroup] curried_add(a : A) -> (A) -> A {
  curried_first(A::op_add, a)
}

///|
test "curray_add" {
  inspect(
    ["1", "2"].map(curried_add("r")),
    content=
      #|["1r", "2r"]
    ,
  )
  inspect(
    ["1", "2"].map(curried_add_pre("l")),
    content=
      #|["l1", "l2"]
    ,
  )
}

///|
pub fn[A, B] curried_tuple_snd(a : A) -> (B) -> (B, A) {
  curried_first(@tuple.pair, a)
  // fn(b) { (b, a) }
}

///|
/// Map a function over the second element of a tuple.
///
/// # Example
/// ```
/// test "Tuple::map_snd" {
///   let tuple = (1, 2)
///   let mapped = @tuple.map_snd(fn(x : Int) -> Int { x + 1 }, tuple)
///   assert_eq(mapped, (1, 3))
/// }
/// ```
pub fn[T, U, V] map_snd(f : (T) -> U, tuple : (V, T)) -> (V, U) {
  (tuple.0, f(tuple.1))
}

///|
/// Map a function over the first element of a tuple.
///
/// # Example
/// ```
/// test "Tuple::map_fst" {
///   let tuple = (1, 2)
///   let mapped = @tuple.map_fst(fn(x : Int) -> Int { x + 1 }, tuple)
///   assert_eq(mapped, (2, 2))
/// }
/// ```
pub fn[T, U, V] map_fst(f : (T) -> U, tuple : (T, V)) -> (U, V) {
  (f(tuple.0), tuple.1)
}

///|
pub fn[T, U, V] curried_tuple_map_fst(f : (T) -> U) -> ((T, V)) -> (U, V) {
  curried_second(map_fst, f)
  // fn(t) { (f(t.0), t.1) }
}

///|
pub fn[T, U, V] curried_tuple_map_snd(f : (T) -> U) -> ((V, T)) -> (V, U) {
  curried_second(map_snd, f)
  // fn(t) { (f(t.0), t.1) }
}

///|
pub fn[A, B] curried_tuple_fst(a : A) -> (B) -> (A, B) {
  curried_second(@tuple.pair, a)
  // fn(b) { (a, b) }
}

///|
pub fn[A, B] Identity::ap(ab : (A) -> B, a : A) -> B {
  ab(a)
}

///|
pub fn[A, B, E : Error] Identity::ap_exn(
  ab : (A) -> B raise E,
  a : A
) -> B raise E {
  ab(a)
}

///|
pub fn[A, B] Identity::map(a : A, f : (A) -> B) -> B {
  a |> f
}

///|
pub fn[A, B] Identity::tap(a : A, f : (A) -> B) -> A {
  a |> f |> ignore
  a
}

///|
pub fn[A, B] curried_apply(a : A) -> ((A) -> B) -> B {
  curried_first(Identity::ap, a)
}

///|
pub fn[A, B, E : Error] curried_apply_exn(
  a : A
) -> ((A) -> B raise E) -> B raise E {
  fn(f) { f(a) }
}

///|
pub fn[A, B] curried_apply_rev(a : (A) -> B) -> (A) -> B {
  curried_second(Identity::ap, a)
}

///|
pub fn[A : Compare] curried_maximum(a : A) -> (A) -> A {
  curried_second(@math.maximum, a)
}

///|
pub fn[A : Compare] curried_minimum(a : A) -> (A) -> A {
  curried_second(@math.minimum, a)
}

///|
test "curried_compare" {
  assert_eq(curried_maximum(1)(2), 2)
  assert_eq(curried_minimum(1)(2), 1)
}

// ///|
// pub fn identity[A](a : A) -> A {
//   a
// }

///|
pub fn[A] identity(a : A) -> A {
  a
}

///|
pub fn[A, B] __unsafe_cast(a : A) -> B = "%identity"

///|
pub fn[A, B] is_identity(a : (A) -> B) -> Bool {
  unsafe_is(identity, a)
}

///|
fn tests() -> Unit raise Error {
  assert_true(is_identity(identity))
  assert_false(is_identity(__unsafe_cast))
  assert_false(is_identity(const_true))
  assert_true(is_identity(const_identity(())))
}

///|
test "is_identity" {
  tests()
}

///|
pub fn[A, B, C, D] __unsafe_cast_arrow(a : (A) -> C) -> (B) -> D = "%identity"

///|
pub fn[A : Eq] eq(x : A, y : A) -> Bool {
  A::op_equal(x, y)
}

///|
pub fn[A : Eq] not_eq(x : A, y : A) -> Bool {
  not(x == y)
}

///|
pub fn[A] check_is(a : A, b : A) -> Bool = "%refeq"

///|
pub fn[A, B] unsafe_is(a : A, b : B) -> Bool = "%refeq"
// {
//   physical_equal(a, b)
// }

///|
pub fn[A] is_not(a : A, b : A) -> Bool {
  not(check_is(a, b))
}

///|
pub fn[A] curried_is(a : A) -> (A) -> Bool {
  curried_second(check_is, a)
}

///|
pub fn[A] curried_is_not(a : A) -> (A) -> Bool {
  curried_not(check_is, a)
}