///|
/// Mutable state of one property thunk. The state transition is monotonic:
/// Pending -> Evaluating -> Resolved/Rejected. Re-entering Evaluating is the
/// structural cycle check; both successful values and failures are memoized.
pub enum EvalThunkState {
  PendingThunk
  EvaluatingThunk
  ResolvedThunk(Value)
  RejectedThunk(String)
}

///|
priv enum EvalThunkResult {
  ThunkOk(Value)
  ThunkError(String)
}

///|

///|
/// Opaque runtime handle for a memoized property computation. `Value` is a
/// public enum, so the payload type is public as well; its fields remain
/// private and cells can only be created and forced by the evaluator API.
pub struct EvalThunkCell {
  priv id : Int
  priv name : String
  priv state : Ref[EvalThunkState]
  priv computation : Ref[(() -> Value)?]
  priv force_count : Ref[Int]
}

///|
pub impl Eq for EvalThunkCell with fn equal(self, other) {
  self.id == other.id
}

///|
pub impl Debug for EvalThunkCell with fn to_repr(self) {
  Repr::literal("EvalThunkCell(\{self.id})")
}

///|
// Only identity allocation is process-global. Unlike the old registry, this
// counter retains no cell, computation closure, module cache, or Value graph.
let next_eval_thunk_id : Ref[Int] = { val: 0 }

///|
/// Force one runtime value when it is a property thunk. Ordinary values are
/// returned unchanged. `eval_path` already materializes its visible result;
/// this API is for consumers of `eval_path_with_runtime_metadata` and for
/// runtime integrations that inspect `ValueMember.value` directly.
pub fn force_value(value : Value) -> Value {
  force_eval_thunk(value)
}

///|
fn reserve_eval_thunk(name : String) -> EvalThunkCell {
  let id = next_eval_thunk_id.val
  next_eval_thunk_id.val = id + 1
  {
    id,
    name,
    state: { val: PendingThunk },
    computation: { val: None },
    force_count: { val: 0 },
  }
}

///|
fn initialize_eval_thunk(
  cell : EvalThunkCell,
  computation : () -> EvalThunkResult,
) -> Unit {
  cell.computation.val = Some(fn() {
    match computation() {
      ThunkOk(value) => value
      ThunkError(message) => deferred_error_value(message)
    }
  })
}

///|
fn force_eval_thunk(value : Value) -> Value {
  match value {
    ThunkValue(cell) => force_eval_thunk_cell(cell)
    _ => value
  }
}

///|
fn force_eval_thunk_cell(cell : EvalThunkCell) -> Value {
  match cell.state.val {
    ResolvedThunk(value) => value
    RejectedThunk(message) => deferred_error_value(message)
    EvaluatingThunk =>
      deferred_error_value("cyclic property reference \{cell.name}")
    PendingThunk => {
      cell.state.val = EvaluatingThunk
      cell.force_count.val = cell.force_count.val + 1
      match cell.computation.val {
        Some(computation) => {
          let value = computation()
          match direct_eval_thunk_error_message(value) {
            None => {
              cell.state.val = ResolvedThunk(value)
              // A resolved cell no longer needs its lexical environment.
              // Dropping the closure here breaks the common
              // cell -> computation -> object members -> cell retention cycle
              // immediately instead of waiting for a tracing-GC cycle pass.
              cell.computation.val = None
              value
            }
            Some(message) => {
              cell.state.val = RejectedThunk(message)
              cell.computation.val = None
              value
            }
          }
        }
        None => {
          let message = "Property thunk `\{cell.name}` was not initialized."
          cell.state.val = RejectedThunk(message)
          deferred_error_value(message)
        }
      }
    }
  }
}

///|
// `deferred_error_message` intentionally forces thunk values. A freshly run
// computation must be classified without triggering another cell, so inspect
// only the reserved deferred-error marker here.
fn direct_eval_thunk_error_message(value : Value) -> String? {
  match value {
    ObjectValue(members) =>
      match lookup_member(members, error_member_name("@deferred")) {
        Some(StringValue(message)) => Some(message)
        _ => None
      }
    _ => None
  }
}