///|
/// An active call frame recorded for debugger inspection: the executing function
/// value plus its compiled code and the live local-slot array, from which the
/// local names and current values are recovered when a `DebugFrame` snapshot is
/// taken.
priv struct ActiveCallFrame {
  func_val : @value.Value
  funcode : @compile.Funcode
  slots : Array[LocalSlot]
}

///|
/// A snapshot of a single Starlark call frame for debugger inspection.
/// Obtained via `Thread::debug_frame(depth)`.
pub struct DebugFrame {
  priv callable_val : @value.Value
  priv all_local_names : Array[String]
  priv locals_map : Map[String, @value.Value]
}

///|
fn DebugFrame::from_active(af : ActiveCallFrame) -> DebugFrame {
  // Recover the frame's named locals (parameters first, then body locals) and
  // their current values from the live slot array.
  let all_local_names : Array[String] = []
  let locals_map : Map[String, @value.Value] = Map([])
  for i, b in af.funcode.locals {
    let name = b.name()
    if !all_local_names.contains(name) {
      all_local_names.push(name)
    }
    let v = match af.slots[i] {
      LVal(v) => Some(v)
      LBoxed(c) => c.get()
      LUnbound => None
    }
    match v {
      Some(vv) => locals_map[name] = vv
      None => ()
    }
  }
  { callable_val: af.func_val, all_local_names, locals_map }
}

///|
/// Returns the callable value (`Function` or `Builtin`) that owns this frame.
///
/// Returns the `Value` representing the function or builtin being executed in
/// this frame.
pub fn DebugFrame::callable(self : DebugFrame) -> @value.Value {
  self.callable_val
}

///|
/// Returns the number of local variables (parameters + body locals) in this frame.
///
/// Returns the total count of named locals, including both function parameters
/// and variables declared in the function body.
pub fn DebugFrame::num_locals(self : DebugFrame) -> Int {
  self.all_local_names.length()
}

///|
/// Returns the binding descriptor and current value of the `i`-th local.
/// The value is `None` if the local has not yet been assigned.
///
/// Parameters:
///
/// - `self` : The debug frame to inspect.
/// - `i` : Zero-based index into the frame's local variable list.
///
/// Returns a tuple of the `Binding` descriptor (name and declaration position)
/// and the current value of that local, or `None` if it has not been assigned.
pub fn DebugFrame::frame_local(
  self : DebugFrame,
  i : Int,
) -> (@errors.Binding, @value.Value?) {
  let name = self.all_local_names[i]
  let bind = @errors.Binding::new(name, @errors.Position::new("", 0, 0))
  let v = self.locals_map.get(name)
  (bind, v)
}

///|
/// Returns the current value of the local variable named `name`, or `None`
/// if the name is absent or the variable has not yet been assigned.
///
/// Parameters:
///
/// - `self` : The debug frame to inspect.
/// - `name` : The name of the local variable to look up.
///
/// Returns the current `Value` of the named local, or `None` if the name is
/// not found or the variable has not yet been assigned.
pub fn DebugFrame::local_by_name(
  self : DebugFrame,
  name : String,
) -> @value.Value? {
  self.locals_map.get(name)
}

///|
/// Returns the source position of the current execution point within this frame.
///
/// Returns the `Position` indicating where execution is currently paused inside
/// this call frame.
pub fn DebugFrame::position(self : DebugFrame) -> @errors.Position {
  ignore(self)
  @errors.Position::new("", 0, 0)
}