///|
/// 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 and tree-walking execution using the
/// same identifier-reference semantics: unresolved names produce
/// "undefined", while TDZ bindings and a selected dynamic binding that
/// disappears during resolution 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)
  let value = env.get_with_strict(name, ctx.strict) catch {
    @errors.ReferenceError(message~) =>
      if message == with_object_binding_missing_message(name) {
        raise @errors.ReferenceError(message="\{name} is not defined")
      } else if message == "\{name} is not defined" {
        match self.global_this {
          Value::Object(data) =>
            match data.bag.properties.get(name) {
              Some(value) => value
              None => Undefined
            }
          _ => Undefined
        }
      } else {
        raise @errors.ReferenceError(message~)
      }
    other => raise other
  }
  String_(type_of(value))
}

///|
/// 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 self.has_property_key(self.global_this, String_(name)) {
        let _ = self.set_property(
          self.global_this,
          name,
          value,
          @token.Loc::default(),
          strict=ctx.strict,
        )
      } else 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 {
  self.run_binding_update_to_completion(ctx, env, name, op, prefix, loc)
}

///|
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
  }
  match binding_kind {
    VarBinding => {
      // A var declaration belongs to this activation's nearest variable
      // environment. A same-named var in an enclosing activation must not
      // suppress creation of the local cell: captured bytecode slots retain
      // that cell before function declarations initialize it.
      let var_env = env.find_var_env()
      let binding_existed = var_env.bindings.contains(name)
      if binding_existed {
        if has_initializer {
          var_env.assign_var(name, value)
        }
      } else {
        var_env.def(name, value, VarBinding)
      }
      if physical_equal(var_env, self.global) &&
        (has_initializer || !binding_existed) {
        self.mirror_to_global(name, value)
      }
    }
    LetBinding | ConstBinding =>
      if env.bindings.contains(name) {
        env.initialize(name, value)
      } else {
        env.def(name, value, binding_kind)
      }
    FunctionNameBinding =>
      raise @errors.InternalError(
        message="compiled declaration cannot define a function-name binding",
      )
  }
}

///|
/// 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_prepared_compiled_script(
  self : Interpreter,
  preparation : CompiledScriptPreparation,
  eval : (ExecContext, Environment) -> Value raise Error,
) -> Value raise Error {
  let strict = preparation.strict
  let ctx : ExecContext = { strict, current_generator: None, }
  try {
    let result = with_cleared_active_callee_realm(self.realm_state, fn() raise {
      self.apply_compiled_script_preparation(preparation)
      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
      }
  }
}

///|
// Compatibility adapter for direct dependents of the historical AST entry.
// This path remains AST-consuming and runtime-owned; finalized bytecode uses
// run_prepared_compiled_script instead.
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() })
}