///|
pub(all) type Reader[R, A] (R) -> A

///|
pub fn[R, A] Reader::read(reader : Reader[R, A], arg : R) -> A {
  reader.inner()(arg)
}

///|
pub fn[R, A] Reader::read_curry(arg : R) -> (Reader[R, A]) -> A {
  fn(ra) { ra.read(arg) }
}

///|
pub fn[R, A] Reader::of(a : A) -> Reader[R, A] {
  constant(a)
}

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

///|
pub fn[R] Reader::ask() -> Reader[R, R] {
  identity
}

///|
pub fn[R, A] Reader::ask_reader(f : (R) -> Reader[R, A]) -> Reader[R, A] {
  fn(r) { f(r).read(r) }
}

///|
pub fn[R, A, R2] Reader::locally(
  self : Reader[R, A],
  f : (R2) -> R
) -> Reader[R2, A] {
  fn(r) { self.read(f(r)) }
}

///|
pub fn[R, A, B] Reader::map(self : Reader[R, A], f : (A) -> B) -> Reader[R, B] {
  self.inner() |> IReader::map(f)
}

///|
pub fn[R, A, B] Reader::map_trace(
  self : Reader[R, A],
  f : (A, R) -> B
) -> Reader[R, B] {
  fn(r) { self.read(r) |> f(r) }
}

///|
pub fn[R, A, B] Reader::ap(
  fab : Reader[R, (A) -> B],
  fa : Reader[R, A]
) -> Reader[R, B] {
  fn(r) { fab.read(r)(fa.read(r)) }
}

///|
pub fn[R, A, B] Reader::ap_first(
  fa : Reader[R, A],
  fb : Reader[R, B]
) -> Reader[R, A] {
  fa.map(constant).ap(fb)
}

///|
pub fn[R, A, B] Reader::ap_second(
  fa : Reader[R, A],
  fb : Reader[R, B]
) -> Reader[R, B] {
  fa.map(const_identity).ap(fb)
}

///|
pub fn[R, A, B] Reader::ap_tuple(
  fa : Reader[R, A],
  fb : Reader[R, B]
) -> Reader[R, (A, B)] {
  fa.map(curried_tuple_fst).ap(fb)
}

///|
pub fn[R, A, B] Reader::ap_tuple_rev(
  fa : Reader[R, A],
  fb : Reader[R, B]
) -> Reader[R, (B, A)] {
  fa.map(curried_tuple_snd).ap(fb)
}

///|
pub fn[R, A, B] Reader::chain(
  self : Reader[R, A],
  f : (A) -> Reader[R, B]
) -> Reader[R, B] {
  fn(r) { f(self.read(r)).read(r) }
}

///|
pub fn[R, A, B] Reader::chain_first(
  self : Reader[R, A],
  f : (A) -> Reader[R, B]
) -> Reader[R, A] {
  self.chain(fn(a) { f(a).map(constant(a)) })
}

///|
pub fn[R, A] Reader::flatten(self : Reader[R, Reader[R, A]]) -> Reader[R, A] {
  self.chain(identity)
}

///|
pub fn[A, B, C] Reader::compose(
  ab : Reader[A, B],
  bc : Reader[B, C]
) -> Reader[A, C] {
  fn(r) { bc.read(ab.read(r)) }
}

///|
pub fn[E, A, B, D] Reader::promap(
  fea : Reader[E, A],
  f : (D) -> E,
  g : (A) -> B
) -> Reader[D, B] {
  fn(r) { fea.read(f(r)) |> g }
}

///|
pub fn[R, A, B] Reader::tap(fa : Reader[R, A], f : (A) -> B) -> Reader[R, A] {
  fa.map(fn(a) {
    a |> f |> ignore
    a
  })
}

///|
pub fn[A, B] Reader::unwrap(self : Reader[A, B]) -> (A) -> B {
  self.inner()
}

///|
pub fn[K : Hash + Eq, V] Reader::memo(f : Reader[K, V]) -> Reader[K, V] {
  let map = @hashmap.new()
  fn(k : K) -> V {
    match map.get(k) {
      Some(v) => v
      None => {
        let v = f.read(k)
        map.set(k, v)
        v
      }
    }
  }
}

///|
/// Creates a memoized version of a reader that caches the first result and
/// returns it for all subsequent calls, regardless of the input value.
///
/// Parameters:
///
/// * `reader` : A reader function of type `Reader[K, V]` that takes an input of
/// type `K` and produces an output of type `V`.
///
/// Returns a new reader that will compute the value only once on its first call
/// and return the cached value for all subsequent calls.
///
/// Example:
///
/// ```moonbit
///   let counter = StringBuilder::new()
///   let reader = fn(_x : Int) {
///     counter.write_string("computed\n")
///     42
///   }
///   let memoized = Reader::memo_ignore_arg(reader)
///   inspect(memoized.read(1), content="42")
///   inspect(memoized.read(2), content="42")
///   inspect(counter.to_string(), content="computed\n")
/// ```
pub fn[K, V] Reader::memo_ignore_arg(f : Reader[K, V]) -> Reader[K, V] {
  let mut cache = panic_with
  cache = fn(k : K) -> V {
    let v = f.read(k)
    cache = constant(v)
    v
  }
  fn(k) { cache(k) }
}

///|
pub fn[R, A] Reader::from_lazy(lazy_ : Lazy[A]) -> Reader[R, A] {
  _a => lazy_.get()
}

///|
pub impl[R, A : Semigroup] Semigroup for Reader[R, A] with op_add(_x, _y) {
  r => _x.read(r).op_add(_y.read(r))
}

///|
pub impl[R, A : Default] Default for Reader[R, A] with default() {
  Reader(A::default |> lift_arg)
}

///|
fn[R, A, B] Reader::_traverse_non_empty_arrayi(
  arr : Array[A],
  f : (Int, A) -> Reader[R, B]
) -> Reader[R, Array[B]] {
  fn(r) { arr.mapi(fn(i, a) { f(i, a).read(r) }) }
}

///|
pub fn[R, A, B] Reader::_traverse_arrayi(
  arr : Array[A],
  f : (Int, A) -> Reader[R, B]
) -> Reader[R, Array[B]] {
  if arr.length() == 0 {
    const_empty_array
  } else {
    Reader::_traverse_non_empty_arrayi(arr, f)
  }
}

///|
pub fn[R, A, B] Reader::traverse_non_empty_arrayi(
  f : (Int, A) -> Reader[R, B]
) -> (Array[A]) -> Reader[R, Array[B]] {
  fn(arr) { Reader::_traverse_non_empty_arrayi(arr, f) }
}

///|
pub fn[R, A, B] Reader::traverse_arrayi(
  f : (Int, A) -> Reader[R, B]
) -> (Array[A]) -> Reader[R, Array[B]] {
  fn(arr) { Reader::_traverse_arrayi(arr, f) }
}

///|
pub fn[R, A, B] Reader::traverse_array(
  f : (A) -> Reader[R, B]
) -> (Array[A]) -> Reader[R, Array[B]] {
  Reader::traverse_arrayi(fn(_i, a) { f(a) })
}

///|
pub fn[R, A] Reader::sequence_array(
  arr : Array[Reader[R, A]]
) -> Reader[R, Array[A]] {
  Reader::_traverse_arrayi(arr, fn(_i, a) { a })
}

///|
pub fn[R, A] Reader::sequence_fixed_array(
  arr : FixedArray[Reader[R, A]]
) -> Reader[R, FixedArray[A]] {
  let (self, target) = (Indexable::fixed_array(), Indexable::fixed_array())
  fn(r) {
    self.sequence_transform(arr, curried_first(Reader::read, r), target~)
  }
}

///|
pub fn[R, A] Reader::sequence_many(
  arr : Many[Reader[R, A]]
) -> Reader[R, Many[A]] {
  let (self, target) = (Indexable::many(), Indexable::many())
  fn(r) {
    self.sequence_transform(arr, curried_first(Reader::read, r), target~)
  }
}

///|
pub fn[R, K : Hash + Eq, A] Reader::sequence_map(
  map : Map[K, Reader[R, A]]
) -> Reader[R, Map[K, A]] {
  fn(r) { map |> MapFunctor::map(Reader::read_curry(r)) }
}

///|
pub fn[R, K : Hash + Eq, A] Reader::bind_to_map(
  self : Reader[R, A],
  key : K
) -> Reader[R, Map[K, A]] {
  self.map(fn(a) {
    let map = Map::new()
    map..set(key, a)
    map
  })
}

///|
pub fn[R, K : Hash + Eq, A] Reader::bind_map(
  self : Reader[R, Map[K, A]],
  key : K,
  fa : Reader[R, A]
) -> Reader[R, Map[K, A]] {
  self.chain(fn(map) {
    fa.map(fn(a) { map.iter() |> iter_append((key, a)) |> Map::from_iter })
  })
}

///|
pub fn[R, A : ToJson] Reader::bind_to_json(
  self : Reader[R, A],
  key : String
) -> Reader[R, Json] {
  self.map(fn(a) {
    let json : Json = a.to_json()
    let map = Map::new()
    map.set(key, json)
    Json::object(map)
  })
}

///|
pub fn[R, A : ToJson] Reader::bind_json(
  self : Reader[R, Json],
  key : String,
  fa : Reader[R, A]
) -> Reader[R, Json] {
  self.chain(fn(a) {
    match a {
      Object(map) =>
        fa.map(fn(a) {
          let json : Json = a.to_json()
          Json::object(map.iter() |> iter_append((key, json)) |> Map::from_iter)
        })
      _ => if DEBUGGER { abort("json is not a object") } else { panic() }
    }
  })
}

///|
test {
  let arg = [
      ..[1, 2, 3, 4].map(Reader::of),
      Reader::ask(),
      Reader::ask().map(a => a + 1),
    ]
  inspect(Reader::sequence_array(arg).read(5), content="[1, 2, 3, 4, 5, 6]")
}

///|
#external
pub type ReaderT

// pub fn ReaderT::map[R, A, B](reader: Reader[R, A], ) -> (A, B) {

// }