// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Internal cell state.
///
/// - `Unforced(thunk)`: the thunk has not yet run.
/// - `Forcing`: the thunk is currently running. Used to detect reentrant
///   `force` calls on the same cell, which would otherwise re-evaluate
///   the thunk and silently break the at-most-once guarantee.
/// - `Forced(v)`: the thunk has produced `v`; the thunk reference is
///   dropped so its captures can be reclaimed.
priv enum LazyState[A] {
  Unforced(() -> A)
  Forcing
  Forced(A)
}

///|
/// A memoized thunk: the first call to `force` runs the thunk and caches
/// the result; later calls return the cached value without re-running it.
///
/// Construct one with `Lazy(thunk)` (deferred) or `ready(value)` (already
/// evaluated). For fallible work, wrap the result in a `Result` value
/// inside the thunk — failure as data is the recommended shape; see the
/// rationale below.
///
/// ## Why force is non-raising / non-async
///
/// `force` has signature `(Self[A]) -> A` — no `raise?`, no async. That is
/// a deliberate choice given how MoonBit surfaces effects in signatures:
///
/// - **No raise.** A raising thunk would make `force` raise too, which
///   would then leak into every consumer that touches a lazy cell — and
///   any data structure built on top (lazy lists, lazy trees, memoized
///   graph nodes) would inherit the effect at every traversal point.
///   Memoizing the failure also forces a choice between "cache the
///   exception and re-raise on every retry" (OCaml-style) and "retry on
///   each force" (Rust-style); both are defensible but neither is
///   obviously right. The recommended pattern for a fallible deferred
///   computation is to make the failure data: `Lazy(() => try? f())`
///   produces a `Lazy[Result[A, Error]]`, and the consumer handles the
///   result at the call site it controls.
///
/// - **No async.** Async memoization additionally needs an in-flight
///   state to handle two coroutines racing to force the same cell, which
///   would pull a concurrency primitive into a type whose only job is to
///   delay a value. The right tool for an async deferred value is the
///   language's promise/future type (which already gives "compute once,
///   await many"), not a thunk wrapper.
///
/// ## Concurrency
///
/// `Lazy[A]` is not thread-safe. Sharing one across threads requires
/// external synchronization.
struct Lazy[A] {
  mut state : LazyState[A]
}

///|
/// Wraps a thunk. The thunk is not invoked until `force` is called; on
/// the first `force`, its result is memoized.
///
/// ```mbt check
/// test {
///   let mut runs = 0
///   let cell = @lazy.Lazy(() => {
///     runs += 1
///     42
///   })
///   @test.assert_eq(cell.force(), 42)
///   @test.assert_eq(cell.force(), 42)
///   @test.assert_eq(runs, 1)
/// }
/// ```
#owned(thunk)
pub fn[A] Lazy::Lazy(thunk : () -> A) -> Lazy[A] {
  { state: Unforced(thunk) }
}

///|
/// Wraps an already-evaluated value. `force` returns it directly without
/// running any code.
///
/// ```mbt check
/// test {
///   let cell = @lazy.Lazy::ready(7)
///   @test.assert_eq(cell.force(), 7)
/// }
/// ```
#owned(value)
pub fn[A] Lazy::ready(value : A) -> Lazy[A] {
  { state: Forced(value) }
}

///|
/// Returns the cached value if the cell has already been forced, or
/// `None` if the thunk has not yet run (or is currently running). Never
/// invokes the thunk — safe to call on cells whose thunks are infinite,
/// effectful, or expensive.
///
/// Useful for introspection / debugging of lazy data structures: you can
/// walk a chain of `Lazy` cells and render only the parts that are
/// already evaluated.
///
/// ```mbt check
/// test {
///   let cell = @lazy.Lazy(() => 7)
///   debug_inspect(cell.peek(), content="None")
///   ignore(cell.force())
///   debug_inspect(cell.peek(), content="Some(7)")
/// }
/// ```
pub fn[A] Lazy::peek(self : Lazy[A]) -> A? {
  match self.state {
    Forced(v) => Some(v)
    _ => None
  }
}

///|
/// Returns the cell's value, running the thunk on the first call and
/// caching the result. Later calls return the cached value in O(1).
///
/// Reentrant forces (a thunk that, while running, forces the same cell
/// again — e.g., through a captured `Ref`) are detected and aborted.
/// Without that check, the inner force would silently re-run the thunk
/// and break the at-most-once memoization guarantee.
pub fn[A] Lazy::force(self : Lazy[A]) -> A {
  match self.state {
    Forced(v) => v
    Forcing => abort("Lazy::force: reentrant force on the same cell")
    Unforced(thunk) => {
      self.state = Forcing
      let v = thunk()
      self.state = Forced(v)
      v
    }
  }
}