///|
/// Resolve an identifier for compiled execution using the same runtime
/// fallback as the tree-walking interpreter.
pub fn Interpreter::get_compiled_name(
  self : Interpreter,
  env : Environment,
  name : String,
) -> Value raise Error {
  env.get(name) catch {
    @errors.ReferenceError(message~) =>
      if message == "\{name} is not defined" {
        match self.global_this {
          Value::Object(data) =>
            match data.bag.properties.get(name) {
              Some(value) => value
              None =>
                raise @errors.ReferenceError(message="\{name} is not defined")
            }
          _ => raise @errors.ReferenceError(message="\{name} is not defined")
        }
      } else {
        raise @errors.ReferenceError(message~)
      }
    other => raise other
  }
}

///|
/// Resolve `typeof name` for compiled execution using the same identifier
/// reference special case as the tree-walking interpreter: unresolved names
/// produce "undefined", while TDZ bindings still raise.
pub fn Interpreter::typeof_compiled_name(
  self : Interpreter,
  ctx : ExecContext,
  env : Environment,
  name : String,
) -> Value raise Error {
  @static_semantics.validate_strict_identifier_reference(ctx.strict, name)
  if env.has(name) {
    String_(type_of(env.get(name)))
  } else {
    match self.global_this {
      Value::Object(data) =>
        match data.bag.properties.get(name) {
          Some(value) => String_(type_of(value))
          None => String_("undefined")
        }
      _ => String_("undefined")
    }
  }
}

///|
/// Assign an identifier for compiled execution without duplicating global
/// object, strict-mode, or implicit-global assignment rules in the compiler.
pub fn Interpreter::assign_compiled_name(
  self : Interpreter,
  ctx : ExecContext,
  env : Environment,
  name : String,
  value : Value,
) -> Value raise Error {
  @static_semantics.validate_strict_assignment_target_name(ctx.strict, name)
  if self.is_immutable_global(name) {
    if ctx.strict {
      raise @errors.TypeError(
        message="Cannot assign to read only property '\{name}' of object '[object global]'",
      )
    }
    return value
  }
  env.assign_with_strict(name, value, ctx.strict) catch {
    @errors.ReferenceError(_) =>
      if !ctx.strict {
        self.global.def(name, value, VarBinding)
        self.mirror_to_global(name, value, configurable=true)
      } else {
        raise @errors.ReferenceError(message="\{name} is not defined")
      }
    e => raise e
  }
  value
}

///|
/// Update an identifier for compiled execution while keeping strict-mode and
/// immutable-global behavior owned by the runtime.
pub fn Interpreter::update_compiled_name(
  self : Interpreter,
  ctx : ExecContext,
  env : Environment,
  name : String,
  op : @ast.UpdateOp,
  prefix : Bool,
  _loc : @token.Loc,
) -> Value raise Error {
  @static_semantics.validate_strict_assignment_target_name(ctx.strict, name)
  let old_value = env.get(name)
  let old_number = to_number(old_value, interp=Some(self))
  let next_value = match op {
    Increment => Value::Number(old_number + 1.0)
    Decrement => Value::Number(old_number - 1.0)
  }
  if self.is_immutable_global(name) {
    if ctx.strict {
      raise @errors.TypeError(
        message="Cannot assign to read only property '\{name}' of object '[object global]'",
      )
    }
  } else {
    env.assign_with_strict(name, next_value, ctx.strict)
  }
  if prefix {
    next_value
  } else {
    Value::Number(old_number)
  }
}

///|
pub fn Interpreter::define_compiled_binding(
  self : Interpreter,
  env : Environment,
  kind : @ast.VarKind,
  name : String,
  value : Value,
  has_initializer : Bool,
) -> Unit raise Error {
  let binding_kind : BindingKind = match kind {
    LetKind => LetBinding
    ConstKind => ConstBinding
    VarKind => VarBinding
  }
  if binding_kind == VarBinding &&
    (env.bindings.contains(name) || env.has_var(name)) {
    if has_initializer {
      env.assign_var(name, value)
      if physical_equal(env.find_var_env(), self.global) {
        self.mirror_to_global(name, value)
      }
    }
  } else if (binding_kind == LetBinding || binding_kind == ConstBinding) &&
    env.bindings.contains(name) {
    env.initialize(name, value)
  } else {
    env.def(name, value, binding_kind)
    if binding_kind == VarBinding &&
      physical_equal(env.find_var_env(), self.global) {
      self.mirror_to_global(name, value)
    }
  }
}

///|
/// Run an already-compiled script body through the same script setup envelope
/// used by `Interpreter::run`.
///
/// Closure conversion lives outside the runtime package, but script execution
/// setup owns private runtime state: the active interpreter ref, static early
/// errors, declaration hoisting, and conversion of engine errors into JS
/// exceptions. Keeping that envelope here lets compiled execution share the
/// same boundary without exposing those internals.
pub fn Interpreter::run_compiled_script(
  self : Interpreter,
  stmts : Array[@ast.Stmt],
  eval : (ExecContext, Environment) -> Value raise Error,
) -> Value raise Error {
  let strict = @static_semantics.has_use_strict(stmts)
  let ctx : ExecContext = { strict, current_generator: None }
  try {
    let result = with_cleared_active_callee_realm(self.realm_state, fn() raise {
      self.validate_block_early_errors(stmts, strict)
      self.hoist_declarations(stmts, self.global, strict~)
      hoist_block_tdz(stmts, self.global)
      eval(ctx, self.global)
    })
    result
  } catch {
    e =>
      if is_js_catchable_error(e) {
        let translated = JsException(
          js_error_to_value_with_env(e, Some(self.global)),
        )
        remap_observed_source_failure(self.realm_state, e, translated)
        raise translated
      } else {
        raise e
      }
  }
}

///|
/// Run a public runtime entry point with this interpreter installed as the
/// active realm for compatibility factory lookups.
pub fn Interpreter::with_active_value(
  self : Interpreter,
  eval : () -> Value raise Error,
) -> Value raise Error {
  with_cleared_active_callee_realm(self.realm_state, fn() raise { eval() })
}