// NativeFunction: a JS-callable function implemented in MoonBit code.
//
// This is Step 9's bridge between the VM and the builtin machinery: an
// `Object` / `Error` / `TypeError` constructor exposed to script code is a
// `JSValue::NativeFn(NativeFunction)` whose `impl` field is a MoonBit closure
// invoked by `OP_CALL` / `OP_CONSTRUCT` inside the interpreter loop.
//
// Rationale for a dedicated variant (rather than reusing `Function`):
// - `Function` carries a `chunk_id` referencing bytecode; a native function
//   has no bytecode.
// - The VM must dispatch differently for the two — a bytecode function
//   creates a new `Frame`, a native function calls its `impl` synchronously
//   inline. Two variants make the dispatch fall out of a single `match`.
// - `NativeFn` participates in the JS `===` "objects compare by identity"
//   rule via `physical_equal`, same as `Object` / `Function`.
//
// Cross-package cycle avoidance: `NativeFunction::impl` uses `JSValue` in its
// signature so the whole type lives in the `value` package. The builtins
// package (which constructs the impl closures) imports `value`; nothing in
// `value` needs to know about builtins.

///|
/// Error payload for a native function's failure path. Native functions do
/// not directly construct `JSException` values — they return
/// `Result[JSValue, NativeError]`, and the VM converts the error to a proper
/// exception with a real Error object and captured stack trace.
///
/// `name` is one of "Error" / "TypeError" / "RangeError" / "SyntaxError" /
/// "ReferenceError". The VM uses this to pick the corresponding Error
/// prototype when it wraps the message into a JS Error object.
pub struct NativeError {
  name : String
  message : String
} derive(@debug.Debug)

///|
/// Constructor for a native error. Callers pass the error class name and the
/// user-visible message.
pub fn NativeError::new(name : String, message : String) -> NativeError {
  { name, message, }
}

///|
/// Accessor for the error class name.
pub fn NativeError::name(self : NativeError) -> String {
  self.name
}

///|
/// Accessor for the error message.
pub fn NativeError::message(self : NativeError) -> String {
  self.message
}

///|
/// A native (MoonBit-defined) JS-callable function. Its runtime shape:
///
/// - `name`: display name — surfaces via `Function.name`, `typeof`, and in
///   error messages ("X is not a function"). "" for anonymous.
/// - `is_constructor`: whether `new X(...)` is permitted. `true` for
///   `Object` / `Error` / etc. Native functions that must not be constructed
///   (e.g. `parseInt` in later milestones) set this to `false`.
/// - `impl`: the MoonBit body. Signature `(this_val, args) -> Result[return,
///   error]`. `this_val` is the receiver:
///   - For a plain call `X(a, b)`, `this_val` is `Undefined`.
///   - For a method call `obj.X(a, b)`, `this_val` is `Object(obj)`.
///   - For `new X(a, b)`, `this_val` is a freshly-created object whose
///     `[[Prototype]]` is `X.prototype` (see `OP_CONSTRUCT` in vm.mbt).
/// - `prototype`: the `.prototype` object attached to this function value.
///   Written by builtin initialisation via `set_prototype`, then consulted
///   by `OP_CONSTRUCT` when instantiating a new receiver. `Undefined` if
///   never assigned.
pub struct NativeFunction {
  name : String
  is_constructor : Bool
  impl_ : (JSValue, Array[JSValue]) -> Result[JSValue, NativeError]
  mut prototype : JSValue
} derive(@debug.Debug)

///|
/// Constructor for a native function. Sets `prototype` to `Undefined`;
/// builtins install a real prototype object via `set_prototype` after both
/// the function value and its prototype have been allocated (this two-step
/// dance is unavoidable because a constructor's prototype often refers back
/// to the constructor via `.constructor`, so neither can be fully wired
/// until both exist).
pub fn NativeFunction::new(
  name : String,
  is_constructor : Bool,
  impl_ : (JSValue, Array[JSValue]) -> Result[JSValue, NativeError],
) -> NativeFunction {
  { name, is_constructor, impl_, prototype: Undefined, }
}

///|
/// Function name. Used for `Function.name` and stack traces.
pub fn NativeFunction::name(self : NativeFunction) -> String {
  self.name
}

///|
/// Whether `new X(...)` is permitted.
pub fn NativeFunction::is_constructor(self : NativeFunction) -> Bool {
  self.is_constructor
}

///|
/// The `.prototype` object. `Undefined` until `set_prototype` runs.
pub fn NativeFunction::prototype(self : NativeFunction) -> JSValue {
  self.prototype
}

///|
/// Rewire the `.prototype` object. Called by builtin initialisation once the
/// constructor and its prototype have both been allocated.
pub fn NativeFunction::set_prototype(
  self : NativeFunction,
  proto : JSValue,
) -> Unit {
  self.prototype = proto
}

///|
/// Invoke the native function's body. The VM does not call this directly;
/// it goes through the higher-level `call` helper that also handles the
/// `Result[JSValue, NativeError] → JSException` conversion. This accessor
/// exists so callers that already have both `this` and args in hand can
/// invoke without duplicating the boilerplate.
pub fn NativeFunction::call(
  self : NativeFunction,
  this_val : JSValue,
  args : Array[JSValue],
) -> Result[JSValue, NativeError] {
  (self.impl_)(this_val, args)
}