// Copyright 2018-2026 the Deno authors. MIT license.
use std::borrow::Cow;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
#[cfg(target_os = "linux")]
use std::sync::LazyLock;
use std::sync::OnceLock;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;

use deno_cache::CacheImpl;
use deno_cache::CreateCache;
use deno_cache::SqliteBackedCache;
use deno_core::CompiledWasmModuleStore;
use deno_core::Extension;
use deno_core::InspectorSessionKind;
use deno_core::JsRuntime;
use deno_core::JsRuntimeInspector;
use deno_core::LocalInspectorSession;
use deno_core::ModuleCodeString;
use deno_core::ModuleId;
use deno_core::ModuleLoadOptions;
use deno_core::ModuleLoadReferrer;
use deno_core::ModuleLoader;
use deno_core::ModuleSpecifier;
use deno_core::OpMetricsFactoryFn;
use deno_core::PollEventLoopOptions;
use deno_core::RuntimeOptions;
use deno_core::SharedArrayBufferStore;
use deno_core::SourceCodeCacheInfo;
use deno_core::error::CoreError;
use deno_core::error::JsError;
use deno_core::v8;
use deno_cron::CronHandler;
use deno_cron::CronHandlerImpl;
use deno_error::JsErrorBox;
use deno_fs::FileSystem;
use deno_io::Stdio;
use deno_kv::dynamic::MultiBackendDbHandler;
use deno_napi::DenoRtNativeAddonLoaderRc;
use deno_node::ExtNodeSys;
use deno_node::NodeExtInitServices;
use deno_os::ExitCode;
use deno_permissions::PermissionsContainer;
use deno_process::NpmProcessStateProviderRc;
use deno_tls::RootCertStoreProvider;
use deno_tls::TlsKeys;
use deno_web::BlobStoreTrait;
use deno_web::InMemoryBroadcastChannel;
use log::debug;
use node_resolver::InNpmPackageChecker;
use node_resolver::NpmPackageFolderResolver;

use crate::BootstrapOptions;
use crate::FeatureChecker;
use crate::code_cache::CodeCache;
use crate::code_cache::CodeCacheType;
use crate::deno_inspector_server::get_inspector_server;
use crate::ops;
use crate::shared::runtime;

pub type FormatJsErrorFn = dyn Fn(&JsError) -> String + Sync + Send;

#[cfg(target_os = "linux")]
pub(crate) static MEMORY_TRIM_HANDLER_ENABLED: LazyLock<bool> =
  LazyLock::new(|| std::env::var_os("DENO_USR2_MEMORY_TRIM").is_some());

#[cfg(target_os = "linux")]
pub(crate) static SIGUSR2_RX: LazyLock<tokio::sync::watch::Receiver<()>> =
  LazyLock::new(|| {
    let (tx, rx) = tokio::sync::watch::channel(());

    tokio::spawn(async move {
      let mut sigusr2 = deno_signals::signal_stream(libc::SIGUSR2).unwrap();

      loop {
        sigusr2.recv().await;

        // SAFETY: calling into libc, nothing relevant on the Rust side.
        unsafe {
          libc::malloc_trim(0);
        }

        if tx.send(()).is_err() {
          break;
        }
      }
    });

    rx
  });

// TODO(bartlomieju): temporary measurement until we start supporting more
// module types
pub fn create_validate_import_attributes_callback(
  enable_raw_imports: Arc<AtomicBool>,
) -> deno_core::ValidateImportAttributesCb {
  Box::new(
    move |scope: &mut v8::PinScope<'_, '_>,
          attributes: &HashMap<String, String>,
          context: &deno_core::ImportAttributesContext| {
      // If `module.registerHooks()` has installed user hooks, defer
      // import-attribute validation to the user hooks so they can
      // implement custom module types and attributes
      // (e.g. `with { type: "x-css" }`). Without this, the builtin
      // validator rejects anything outside the small allow-list before
      // hooks ever get a chance to handle the import.
      let op_state = JsRuntime::op_state_from(&*scope);
      if let Ok(op_state) = op_state.try_borrow()
        && let Some(registry) = op_state
          .try_borrow::<deno_node::ops::module_hooks::LoaderHookRegistry>(
        )
        && registry.hooks_active.get()
      {
        return;
      }
      let valid_attribute = |kind: &str| {
        matches!(kind, "json" | "text")
          || (enable_raw_imports.load(Ordering::Relaxed)
            && matches!(kind, "bytes" | "css"))
      };
      for (key, value) in attributes {
        let msg = if key != "type" {
          Some(format!("\"{key}\" attribute is not supported."))
        } else if !valid_attribute(value.as_str()) {
          Some(format!("\"{value}\" is not a valid module type."))
        } else {
          None
        };

        let Some(msg) = msg else {
          continue;
        };

        let msg = format!("{msg}{}", context.format_location());
        let message = v8::String::new(scope, &msg).unwrap();
        let exception = v8::Exception::type_error(scope, message);
        scope.throw_exception(exception);
        return;
      }
    },
  )
}

/// Evaluates custom module types that aren't built into deno_core. Currently
/// only `with { type: "css" }` imports (gated on `--unstable-raw-imports` by
/// `create_validate_import_attributes_callback`), which evaluate to a
/// `CSSStyleSheet` containing the source text.
pub fn create_custom_module_evaluation_callback()
-> deno_core::CustomModuleEvaluationCb {
  Box::new(
    |scope, module_type, _module_name, code| match &*module_type {
      "css" => {
        let code = match code {
          deno_core::ModuleSourceCode::String(code) => code.as_str().to_owned(),
          deno_core::ModuleSourceCode::Bytes(bytes) => {
            String::from_utf8_lossy(bytes.as_bytes()).into_owned()
          }
        };
        let sheet: v8::Local<v8::Value> =
          deno_web::create_css_style_sheet(scope, code).into();
        let sheet = v8::Global::new(scope, sheet);
        Ok(deno_core::CustomModuleEvaluationKind::Synthetic(sheet))
      }
      _ => Err(deno_error::JsErrorBox::generic(format!(
        "Importing \"{module_type}\" modules is not supported"
      ))),
    },
  )
}

pub fn make_wait_for_inspector_disconnect_callback() -> Box<dyn Fn()> {
  let has_notified_of_inspector_disconnect = AtomicBool::new(false);
  Box::new(move || {
    if !has_notified_of_inspector_disconnect
      .swap(true, std::sync::atomic::Ordering::SeqCst)
    {
      // Match Node.js message format that debugger clients rely on
      log::info!("Waiting for the debugger to disconnect...");
    }
  })
}

/// This worker is created and used by almost all
/// subcommands in Deno executable.
///
/// It provides ops available in the `Deno` namespace.
///
/// All `WebWorker`s created during program execution
/// are descendants of this worker.
pub struct MainWorker {
  pub js_runtime: JsRuntime,
  should_break_on_first_statement: bool,
  should_wait_for_inspector_session: bool,
  exit_code: ExitCode,
  bootstrap_fn_global: Option<v8::Global<v8::Function>>,
  dispatch_load_event_fn_global: v8::Global<v8::Function>,
  dispatch_beforeunload_event_fn_global: v8::Global<v8::Function>,
  dispatch_unload_event_fn_global: v8::Global<v8::Function>,
  dispatch_process_beforeexit_event_fn_global: v8::Global<v8::Function>,
  dispatch_process_exit_event_fn_global: v8::Global<v8::Function>,
  memory_trim_handle: Option<tokio::task::JoinHandle<()>>,
}

impl Drop for MainWorker {
  fn drop(&mut self) {
    if let Some(memory_trim_handle) = self.memory_trim_handle.take() {
      memory_trim_handle.abort();
    }
  }
}

pub struct WorkerServiceOptions<
  TInNpmPackageChecker: InNpmPackageChecker,
  TNpmPackageFolderResolver: NpmPackageFolderResolver,
  TExtNodeSys: ExtNodeSys,
> {
  pub blob_store: Arc<dyn BlobStoreTrait>,
  pub broadcast_channel: InMemoryBroadcastChannel,
  pub deno_rt_native_addon_loader: Option<DenoRtNativeAddonLoaderRc>,
  pub feature_checker: Arc<FeatureChecker>,
  pub fs: Arc<dyn FileSystem>,
  /// Implementation of `ModuleLoader` which will be
  /// called when V8 requests to load ES modules.
  ///
  /// If not provided runtime will error if code being
  /// executed tries to load modules.
  pub module_loader: Rc<dyn ModuleLoader>,
  pub node_services: Option<
    NodeExtInitServices<
      TInNpmPackageChecker,
      TNpmPackageFolderResolver,
      TExtNodeSys,
    >,
  >,
  pub npm_process_state_provider: Option<NpmProcessStateProviderRc>,
  pub permissions: PermissionsContainer,
  pub root_cert_store_provider: Option<Arc<dyn RootCertStoreProvider>>,
  pub fetch_dns_resolver: deno_fetch::dns::Resolver,

  /// The store to use for transferring SharedArrayBuffers between isolates.
  /// If multiple isolates should have the possibility of sharing
  /// SharedArrayBuffers, they should use the same [SharedArrayBufferStore]. If
  /// no [SharedArrayBufferStore] is specified, SharedArrayBuffer can not be
  /// serialized.
  pub shared_array_buffer_store: Option<SharedArrayBufferStore>,

  /// The store to use for transferring `WebAssembly.Module` objects between
  /// isolates.
  /// If multiple isolates should have the possibility of sharing
  /// `WebAssembly.Module` objects, they should use the same
  /// [CompiledWasmModuleStore]. If no [CompiledWasmModuleStore] is specified,
  /// `WebAssembly.Module` objects cannot be serialized.
  pub compiled_wasm_module_store: Option<CompiledWasmModuleStore>,

  /// V8 code cache for module and script source code.
  pub v8_code_cache: Option<Arc<dyn CodeCache>>,

  pub bundle_provider: Option<Arc<dyn deno_bundle_runtime::BundleProvider>>,
}

pub struct WorkerOptions {
  pub bootstrap: BootstrapOptions,

  /// JsRuntime extensions, not to be confused with ES modules.
  ///
  /// Extensions register "ops" and JavaScript sources provided in `js` or `esm`
  /// configuration. If you are using a snapshot, then extensions shouldn't
  /// provide JavaScript sources that were already snapshotted.
  pub extensions: Vec<Extension>,

  /// V8 snapshot that should be loaded on startup.
  pub startup_snapshot: Option<&'static [u8]>,

  /// `(specifier, source)` pairs for `lazy_loaded_js` files that were not
  /// consumed during snapshot creation. Emitted by the snapshot build script.
  pub residual_lazy_js_sources: &'static [(&'static str, &'static str)],

  /// `(specifier, source)` pairs for `lazy_loaded_esm` files that were not
  /// consumed during snapshot creation. Emitted by the snapshot build script.
  pub residual_lazy_esm_sources: &'static [(&'static str, &'static str)],

  /// Should op registration be skipped?
  pub skip_op_registration: bool,

  /// Optional isolate creation parameters, such as heap limits.
  pub create_params: Option<v8::CreateParams>,

  pub unsafely_ignore_certificate_errors: Option<Vec<String>>,
  pub seed: Option<u64>,

  // Callbacks invoked when creating new instance of WebWorker
  pub create_web_worker_cb: Arc<ops::worker_host::CreateWebWorkerCb>,
  pub format_js_error_fn: Option<Arc<FormatJsErrorFn>>,

  // If true, the worker will wait for inspector session and break on first
  // statement of user code. Takes higher precedence than
  // `should_wait_for_inspector_session`.
  pub should_break_on_first_statement: bool,
  // If true, the worker will wait for inspector session before executing
  // user code.
  pub should_wait_for_inspector_session: bool,
  /// If Some, print a low-level trace output for ops matching the given patterns.
  pub trace_ops: Option<Vec<String>>,

  pub cache_storage_dir: Option<std::path::PathBuf>,
  pub origin_storage_dir: Option<std::path::PathBuf>,
  pub stdio: Stdio,
  pub enable_raw_imports: bool,
  pub enable_stack_trace_arg_in_ops: bool,

  pub unconfigured_runtime: Option<UnconfiguredRuntime>,
}

impl Default for WorkerOptions {
  fn default() -> Self {
    Self {
      create_web_worker_cb: Arc::new(|_| {
        unimplemented!("web workers are not supported")
      }),
      skip_op_registration: false,
      seed: None,
      unsafely_ignore_certificate_errors: Default::default(),
      should_break_on_first_statement: Default::default(),
      should_wait_for_inspector_session: Default::default(),
      trace_ops: Default::default(),
      format_js_error_fn: Default::default(),
      origin_storage_dir: Default::default(),
      cache_storage_dir: Default::default(),
      extensions: Default::default(),
      startup_snapshot: Default::default(),
      residual_lazy_js_sources: &[],
      residual_lazy_esm_sources: &[],
      create_params: Default::default(),
      bootstrap: Default::default(),
      stdio: Default::default(),
      enable_raw_imports: false,
      enable_stack_trace_arg_in_ops: false,
      unconfigured_runtime: None,
    }
  }
}

pub(crate) fn request_builder_hook(
  request: &mut http::Request<deno_fetch::ReqBody>,
) -> Result<(), JsErrorBox> {
  const X_DENO_FETCH_TOKEN: http::HeaderName =
    http::HeaderName::from_static("x-deno-fetch-token");
  const CDN_LOOP: http::HeaderName = http::HeaderName::from_static("cdn-loop");
  static X_DENO_FETCH_TOKEN_VALUE: OnceLock<Option<http::HeaderValue>> =
    OnceLock::new();
  static CDN_LOOP_VALUE: OnceLock<Option<http::HeaderValue>> = OnceLock::new();

  // Scrub Deno-specific headers to prevent user code from spoofing them.
  if let http::header::Entry::Occupied(entry) =
    request.headers_mut().entry(X_DENO_FETCH_TOKEN)
  {
    entry.remove_entry_mult();
  }

  let maybe_x_deno_fetch_token = X_DENO_FETCH_TOKEN_VALUE.get_or_init(|| {
    std::env::var("X_DENO_FETCH_TOKEN")
      .ok()
      .and_then(|v| http::HeaderValue::from_str(&v).ok())
  });

  if let Some(token) = maybe_x_deno_fetch_token {
    request
      .headers_mut()
      .insert(X_DENO_FETCH_TOKEN, token.clone());
  }

  let cdn_loop_value = CDN_LOOP_VALUE.get_or_init(|| {
    std::env::var("CDN_LOOP")
      .ok()
      .and_then(|v| http::HeaderValue::from_str(&v).ok())
  });

  if let Some(cdn_loop) = cdn_loop_value {
    request.headers_mut().insert(CDN_LOOP, cdn_loop.clone());
  }

  Ok(())
}

pub fn create_op_metrics(
  trace_ops: Option<Vec<String>>,
) -> Option<OpMetricsFactoryFn> {
  let patterns = trace_ops?;
  let now = Instant::now();
  let max_len: Rc<std::cell::Cell<usize>> = Default::default();

  /// Match an op name against a list of patterns
  fn matches_pattern(patterns: &[String], name: &str) -> bool {
    let mut found_match = false;
    let mut found_nomatch = false;
    for pattern in patterns.iter() {
      if let Some(pattern) = pattern.strip_prefix('-') {
        if name.contains(pattern) {
          return false;
        }
      } else if name.contains(pattern.as_str()) {
        found_match = true;
      } else {
        found_nomatch = true;
      }
    }

    found_match || !found_nomatch
  }

  Some(Box::new(move |_, _, decl| {
    // If we don't match a requested pattern, or we match a negative pattern, bail
    if !matches_pattern(&patterns, decl.name) {
      return None;
    }

    max_len.set(max_len.get().max(decl.name.len()));
    let max_len = max_len.clone();
    Some(Rc::new(
      #[allow(
        clippy::print_stderr,
        reason = "we actually want to output here"
      )]
      move |op: &deno_core::_ops::OpCtx, event, source| {
        eprintln!(
          "[{: >10.3}] {name:max_len$}: {event:?} {source:?}",
          now.elapsed().as_secs_f64(),
          name = op.decl().name,
          max_len = max_len.get()
        );
      },
    ))
  }))
}

impl MainWorker {
  pub fn bootstrap_from_options<
    TInNpmPackageChecker: InNpmPackageChecker + 'static,
    TNpmPackageFolderResolver: NpmPackageFolderResolver + 'static,
    TExtNodeSys: ExtNodeSys + 'static,
  >(
    main_module: &ModuleSpecifier,
    services: WorkerServiceOptions<
      TInNpmPackageChecker,
      TNpmPackageFolderResolver,
      TExtNodeSys,
    >,
    options: WorkerOptions,
  ) -> Self {
    let (mut worker, bootstrap_options) =
      Self::from_options(main_module, services, options);
    worker.bootstrap(bootstrap_options);
    worker
  }

  fn from_options<
    TInNpmPackageChecker: InNpmPackageChecker + 'static,
    TNpmPackageFolderResolver: NpmPackageFolderResolver + 'static,
    TExtNodeSys: ExtNodeSys + 'static,
  >(
    main_module: &ModuleSpecifier,
    services: WorkerServiceOptions<
      TInNpmPackageChecker,
      TNpmPackageFolderResolver,
      TExtNodeSys,
    >,
    mut options: WorkerOptions,
  ) -> (Self, BootstrapOptions) {
    fn create_cache_inner(options: &WorkerOptions) -> Option<CreateCache> {
      if let Ok(var) = std::env::var("DENO_CACHE_LSC_ENDPOINT") {
        let elems: Vec<_> = var.split(",").collect();
        if elems.len() == 2 {
          let endpoint = elems[0];
          let token = elems[1];
          use deno_cache::CacheShard;

          let shard =
            Rc::new(CacheShard::new(endpoint.to_string(), token.to_string()));
          let create_cache_fn = move || {
            let x = deno_cache::LscBackend::default();
            x.set_shard(shard.clone());

            Ok(CacheImpl::Lsc(x))
          };
          #[allow(
            clippy::arc_with_non_send_sync,
            reason = "fine because the Rc is in the return type"
          )]
          return Some(CreateCache(Arc::new(create_cache_fn)));
        }
      }

      if let Some(storage_dir) = &options.cache_storage_dir {
        let storage_dir = storage_dir.clone();
        let create_cache_fn = move || {
          let s = SqliteBackedCache::new(storage_dir.clone())?;
          Ok(CacheImpl::Sqlite(s))
        };
        return Some(CreateCache(Arc::new(create_cache_fn)));
      }

      None
    }
    let create_cache = create_cache_inner(&options);

    // Get our op metrics
    let op_metrics_factory_fn = create_op_metrics(options.trace_ops);

    // Permissions: many ops depend on this
    let enable_testing_features = options.bootstrap.enable_testing_features;
    let exit_code = ExitCode::default();

    // check options that require configuring a new jsruntime
    if options.unconfigured_runtime.is_some()
      && (options.enable_stack_trace_arg_in_ops
        || op_metrics_factory_fn.is_some())
    {
      options.unconfigured_runtime = None;
    }

    let mut js_runtime = if let Some(u) = options.unconfigured_runtime {
      let js_runtime = u.hydrate(services.module_loader);

      let op_state = js_runtime.op_state();
      let current_handler =
        op_state.borrow().borrow::<Rc<dyn CronHandler>>().clone();
      if let Some(new_handler) = current_handler.maybe_reload() {
        op_state
          .borrow_mut()
          .put::<Rc<dyn CronHandler>>(Rc::from(new_handler));
      }

      js_runtime
    } else {
      let mut extensions = common_extensions::<
        TInNpmPackageChecker,
        TNpmPackageFolderResolver,
        TExtNodeSys,
      >(options.startup_snapshot.is_some(), false);

      extensions.extend(std::mem::take(&mut options.extensions));

      common_runtime(CommonRuntimeOptions {
        module_loader: services.module_loader.clone(),
        startup_snapshot: options.startup_snapshot,
        residual_lazy_js_sources: options.residual_lazy_js_sources,
        residual_lazy_esm_sources: options.residual_lazy_esm_sources,
        create_params: options.create_params,
        skip_op_registration: options.skip_op_registration,
        shared_array_buffer_store: services.shared_array_buffer_store,
        compiled_wasm_module_store: services.compiled_wasm_module_store,
        extensions,
        op_metrics_factory_fn,
        enable_stack_trace_arg_in_ops: options.enable_stack_trace_arg_in_ops,
      })
    };

    js_runtime
      .set_eval_context_code_cache_cbs(services.v8_code_cache.map(|cache| {
      let cache_clone = cache.clone();
      (
        Box::new(move |specifier: &ModuleSpecifier, code: &v8::String| {
          let source_hash = {
            use std::hash::Hash;
            use std::hash::Hasher;
            let mut hasher = twox_hash::XxHash64::default();
            code.hash(&mut hasher);
            hasher.finish()
          };
          let data = cache
            .get_sync(specifier, CodeCacheType::Script, source_hash)
            .inspect(|_| {
              // This log line is also used by tests.
              log::debug!(
                "V8 code cache hit for script: {specifier}, [{source_hash}]"
              );
            })
            .map(Cow::Owned);
          Ok(SourceCodeCacheInfo {
            data,
            hash: source_hash,
          })
        }) as Box<dyn Fn(&_, &_) -> _>,
        Box::new(
          move |specifier: ModuleSpecifier, source_hash: u64, data: &[u8]| {
            // This log line is also used by tests.
            log::debug!(
              "Updating V8 code cache for script: {specifier}, [{source_hash}]"
            );
            cache_clone.set_sync(
              specifier,
              CodeCacheType::Script,
              source_hash,
              data,
            );
          },
        ) as Box<dyn Fn(_, _, &_)>,
      )
    }));

    js_runtime
      .op_state()
      .borrow_mut()
      .borrow::<EnableRawImports>()
      .0
      .store(options.enable_raw_imports, Ordering::Relaxed);

    js_runtime
      .lazy_init_extensions(vec![
        deno_web::deno_web::args(
          services.blob_store.clone(),
          options.bootstrap.location.clone(),
          true,
          services.broadcast_channel.clone(),
        ),
        deno_fetch::deno_fetch::args(deno_fetch::Options {
          user_agent: options.bootstrap.user_agent.clone(),
          root_cert_store_provider: services.root_cert_store_provider.clone(),
          unsafely_ignore_certificate_errors: options
            .unsafely_ignore_certificate_errors
            .clone(),
          file_fetch_handler: Rc::new(deno_fetch::FsFetchHandler),
          resolver: services.fetch_dns_resolver,
          request_builder_hook: Some(request_builder_hook),
          ..Default::default()
        }),
        deno_cache::deno_cache::args(create_cache),
        deno_websocket::deno_websocket::args(),
        deno_webstorage::deno_webstorage::args(
          options.origin_storage_dir.clone(),
        ),
        deno_crypto::deno_crypto::args(options.seed),
        deno_ffi::deno_ffi::args(services.deno_rt_native_addon_loader.clone()),
        deno_net::deno_net::args(
          services.root_cert_store_provider.clone(),
          options.unsafely_ignore_certificate_errors.clone(),
        ),
        deno_kv::deno_kv::args(
          Box::new(MultiBackendDbHandler::remote_or_sqlite(
            options.origin_storage_dir.clone(),
            options.seed,
            deno_kv::remote::HttpOptions {
              user_agent: options.bootstrap.user_agent.clone(),
              root_cert_store_provider: services
                .root_cert_store_provider
                .clone(),
              unsafely_ignore_certificate_errors: options
                .unsafely_ignore_certificate_errors
                .clone(),
              client_cert_chain_and_key: TlsKeys::Null,
              proxy: None,
            },
          )),
          deno_kv::KvConfig::builder().build(),
        ),
        deno_napi::deno_napi::args(
          services.deno_rt_native_addon_loader.clone(),
        ),
        deno_http::deno_http::args(deno_http::Options {
          no_legacy_abort: options.bootstrap.no_legacy_abort,
          ..Default::default()
        }),
        deno_io::deno_io::args(Some(options.stdio)),
        deno_fs::deno_fs::args(services.fs.clone()),
        deno_os::deno_os::args(Some(exit_code.clone())),
        deno_process::deno_process::args(services.npm_process_state_provider),
        deno_node::deno_node::args::<
          TInNpmPackageChecker,
          TNpmPackageFolderResolver,
          TExtNodeSys,
        >(services.node_services, services.fs.clone()),
        ops::runtime::deno_runtime::args(main_module.clone()),
        ops::worker_host::deno_worker_host::args(
          options.create_web_worker_cb.clone(),
          options.format_js_error_fn.clone(),
        ),
        deno_bundle_runtime::deno_bundle_runtime::args(
          services.bundle_provider.clone(),
        ),
      ])
      .unwrap();

    {
      let state = js_runtime.op_state();
      let mut state = state.borrow_mut();

      // Put inspector handle into the op state so we can put a breakpoint when
      // executing a CJS entrypoint.
      state.put(js_runtime.inspector());

      state.put::<PermissionsContainer>(services.permissions);
      state.put(ops::TestingFeaturesEnabled(enable_testing_features));
      state.put(services.feature_checker);
    }

    // Register the uv_loop_t (created by deno_node extension state callback)
    // The uv loop is auto-created and registered by JsRuntime::new_inner.

    if let Some(server) = get_inspector_server() {
      let inspector_url = server.register_inspector(
        main_module.to_string(),
        js_runtime.inspector(),
        options.should_break_on_first_statement
          || options.should_wait_for_inspector_session,
      );
      js_runtime.op_state().borrow_mut().put(inspector_url);
    }

    let (
      bootstrap_fn_global,
      dispatch_load_event_fn_global,
      dispatch_beforeunload_event_fn_global,
      dispatch_unload_event_fn_global,
      dispatch_process_beforeexit_event_fn_global,
      dispatch_process_exit_event_fn_global,
    ) = {
      let context = js_runtime.main_context();
      deno_core::scope!(scope, &mut js_runtime);
      let context_local = v8::Local::new(scope, context);
      let global_obj = context_local.global(scope);
      let bootstrap_str =
        v8::String::new_external_onebyte_static(scope, b"bootstrap").unwrap();
      let bootstrap_ns: v8::Local<v8::Object> = global_obj
        .get(scope, bootstrap_str.into())
        .unwrap()
        .try_into()
        .unwrap();
      let main_runtime_str =
        v8::String::new_external_onebyte_static(scope, b"mainRuntime").unwrap();
      let bootstrap_fn =
        bootstrap_ns.get(scope, main_runtime_str.into()).unwrap();
      let bootstrap_fn =
        v8::Local::<v8::Function>::try_from(bootstrap_fn).unwrap();
      let dispatch_load_event_fn_str =
        v8::String::new_external_onebyte_static(scope, b"dispatchLoadEvent")
          .unwrap();
      let dispatch_load_event_fn = bootstrap_ns
        .get(scope, dispatch_load_event_fn_str.into())
        .unwrap();
      let dispatch_load_event_fn =
        v8::Local::<v8::Function>::try_from(dispatch_load_event_fn).unwrap();
      let dispatch_beforeunload_event_fn_str =
        v8::String::new_external_onebyte_static(
          scope,
          b"dispatchBeforeUnloadEvent",
        )
        .unwrap();
      let dispatch_beforeunload_event_fn = bootstrap_ns
        .get(scope, dispatch_beforeunload_event_fn_str.into())
        .unwrap();
      let dispatch_beforeunload_event_fn =
        v8::Local::<v8::Function>::try_from(dispatch_beforeunload_event_fn)
          .unwrap();
      let dispatch_unload_event_fn_str =
        v8::String::new_external_onebyte_static(scope, b"dispatchUnloadEvent")
          .unwrap();
      let dispatch_unload_event_fn = bootstrap_ns
        .get(scope, dispatch_unload_event_fn_str.into())
        .unwrap();
      let dispatch_unload_event_fn =
        v8::Local::<v8::Function>::try_from(dispatch_unload_event_fn).unwrap();
      let dispatch_process_beforeexit_event =
        v8::String::new_external_onebyte_static(
          scope,
          b"dispatchProcessBeforeExitEvent",
        )
        .unwrap();
      let dispatch_process_beforeexit_event_fn = bootstrap_ns
        .get(scope, dispatch_process_beforeexit_event.into())
        .unwrap();
      let dispatch_process_beforeexit_event_fn =
        v8::Local::<v8::Function>::try_from(
          dispatch_process_beforeexit_event_fn,
        )
        .unwrap();
      let dispatch_process_exit_event =
        v8::String::new_external_onebyte_static(
          scope,
          b"dispatchProcessExitEvent",
        )
        .unwrap();
      let dispatch_process_exit_event_fn = bootstrap_ns
        .get(scope, dispatch_process_exit_event.into())
        .unwrap();
      let dispatch_process_exit_event_fn =
        v8::Local::<v8::Function>::try_from(dispatch_process_exit_event_fn)
          .unwrap();
      (
        v8::Global::new(scope, bootstrap_fn),
        v8::Global::new(scope, dispatch_load_event_fn),
        v8::Global::new(scope, dispatch_beforeunload_event_fn),
        v8::Global::new(scope, dispatch_unload_event_fn),
        v8::Global::new(scope, dispatch_process_beforeexit_event_fn),
        v8::Global::new(scope, dispatch_process_exit_event_fn),
      )
    };

    let worker = Self {
      js_runtime,
      should_break_on_first_statement: options.should_break_on_first_statement,
      should_wait_for_inspector_session: options
        .should_wait_for_inspector_session,
      exit_code,
      bootstrap_fn_global: Some(bootstrap_fn_global),
      dispatch_load_event_fn_global,
      dispatch_beforeunload_event_fn_global,
      dispatch_unload_event_fn_global,
      dispatch_process_beforeexit_event_fn_global,
      dispatch_process_exit_event_fn_global,
      memory_trim_handle: None,
    };
    (worker, options.bootstrap)
  }

  pub fn bootstrap(&mut self, options: BootstrapOptions) {
    // Diagnostic env var; not on the sys_traits surface in this crate.
    #[allow(
      clippy::disallowed_methods,
      reason = "diagnostic env var; not part of the sys_traits surface"
    )]
    let phase_enabled = std::env::var_os("DENO_STARTUP_PHASES")
      .is_some_and(|v| !v.is_empty() && v != "0");
    let t0 = if phase_enabled {
      Some(std::time::Instant::now())
    } else {
      None
    };
    // Setup bootstrap options for ops.
    {
      let op_state = self.js_runtime.op_state();
      let mut state = op_state.borrow_mut();
      state.put(options.clone());
      if let Some((fd, serialization)) = options.node_ipc_init {
        state.put(deno_node::ChildPipeFd(fd, serialization));
      }
    }

    deno_core::scope!(scope, &mut self.js_runtime);
    v8::tc_scope!(scope, scope);
    let args = options.as_v8(scope);
    let bootstrap_fn = self.bootstrap_fn_global.take().unwrap();
    let bootstrap_fn = v8::Local::new(scope, bootstrap_fn);
    let undefined = v8::undefined(scope);
    bootstrap_fn.call(scope, undefined.into(), &[args]);
    if let Some(exception) = scope.exception() {
      let error = JsError::from_v8_exception(scope, exception);
      panic!("Bootstrap exception: {error}");
    }
    if let Some(t) = t0 {
      #[allow(clippy::print_stderr, reason = "diagnostic")]
      {
        eprintln!(
          "[startup] {:>32}  {:?}",
          "MainWorker::bootstrap (99_main)",
          t.elapsed()
        );
      }
    }
  }

  #[cfg(not(target_os = "linux"))]
  pub fn setup_memory_trim_handler(&mut self) {
    // Noop
  }

  /// Sets up a handler that responds to SIGUSR2 signals by trimming unused
  /// memory and notifying V8 of low memory conditions.
  /// Note that this must be called within a tokio runtime.
  /// Calling this method multiple times will be a no-op.
  #[cfg(target_os = "linux")]
  pub fn setup_memory_trim_handler(&mut self) {
    if self.memory_trim_handle.is_some() {
      return;
    }

    if !*MEMORY_TRIM_HANDLER_ENABLED {
      return;
    }

    let mut sigusr2_rx = SIGUSR2_RX.clone();

    let spawner = self
      .js_runtime
      .op_state()
      .borrow()
      .borrow::<deno_core::V8CrossThreadTaskSpawner>()
      .clone();

    let memory_trim_handle = tokio::spawn(async move {
      loop {
        if sigusr2_rx.changed().await.is_err() {
          break;
        }

        spawner.spawn(move |isolate| {
          isolate.low_memory_notification();
        });
      }
    });

    self.memory_trim_handle = Some(memory_trim_handle);
  }

  /// See [JsRuntime::execute_script](deno_core::JsRuntime::execute_script)
  pub fn execute_script(
    &mut self,
    script_name: &'static str,
    source_code: ModuleCodeString,
  ) -> Result<v8::Global<v8::Value>, Box<JsError>> {
    self.js_runtime.execute_script(script_name, source_code)
  }

  /// Loads and instantiates specified JavaScript module as "main" module.
  pub async fn preload_main_module(
    &mut self,
    module_specifier: &ModuleSpecifier,
  ) -> Result<ModuleId, CoreError> {
    self.js_runtime.load_main_es_module(module_specifier).await
  }

  /// Loads and instantiates specified JavaScript module as "side" module.
  pub async fn preload_side_module(
    &mut self,
    module_specifier: &ModuleSpecifier,
  ) -> Result<ModuleId, CoreError> {
    self.js_runtime.load_side_es_module(module_specifier).await
  }

  /// Executes specified JavaScript module.
  pub async fn evaluate_module(
    &mut self,
    id: ModuleId,
  ) -> Result<(), CoreError> {
    self.wait_for_inspector_session();
    let mut receiver = self.js_runtime.mod_evaluate(id);
    tokio::select! {
      // Not using biased mode leads to non-determinism for relatively simple
      // programs.
      biased;

      maybe_result = &mut receiver => {
        debug!("received module evaluate {:#?}", maybe_result);
        maybe_result
      }

      event_loop_result = self.run_event_loop(false) => {
        event_loop_result?;
        receiver.await
      }
    }
  }

  /// Run the event loop up to a given duration. If the runtime resolves early, returns
  /// early. Will always poll the runtime at least once.
  pub async fn run_up_to_duration(
    &mut self,
    duration: Duration,
  ) -> Result<(), CoreError> {
    match tokio::time::timeout(
      duration,
      self
        .js_runtime
        .run_event_loop(PollEventLoopOptions::default()),
    )
    .await
    {
      Ok(Ok(_)) => Ok(()),
      Err(_) => Ok(()),
      Ok(Err(e)) => Err(e),
    }
  }

  /// Loads, instantiates and executes specified JavaScript module.
  pub async fn execute_side_module(
    &mut self,
    module_specifier: &ModuleSpecifier,
  ) -> Result<(), CoreError> {
    let id = self.preload_side_module(module_specifier).await?;
    self.evaluate_module(id).await
  }

  /// Loads, instantiates and executes specified JavaScript module.
  ///
  /// This module will have "import.meta.main" equal to true.
  pub async fn execute_main_module(
    &mut self,
    module_specifier: &ModuleSpecifier,
  ) -> Result<(), CoreError> {
    let id = self.preload_main_module(module_specifier).await?;
    self.evaluate_module(id).await
  }

  fn wait_for_inspector_session(&mut self) {
    if self.should_break_on_first_statement {
      self
        .js_runtime
        .inspector()
        .wait_for_session_and_break_on_next_statement();
    } else if self.should_wait_for_inspector_session {
      self.js_runtime.inspector().wait_for_session();
    }
  }

  /// Create new inspector session. This function panics if Worker
  /// was not configured to create inspector.
  pub fn create_inspector_session(
    &mut self,
    cb: deno_core::InspectorSessionSend,
  ) -> LocalInspectorSession {
    self.js_runtime.maybe_init_inspector();
    let insp = self.js_runtime.inspector();

    JsRuntimeInspector::create_local_session(
      insp,
      cb,
      InspectorSessionKind::Blocking,
    )
  }

  pub async fn run_event_loop(
    &mut self,
    wait_for_inspector: bool,
  ) -> Result<(), CoreError> {
    self
      .js_runtime
      .run_event_loop(PollEventLoopOptions { wait_for_inspector })
      .await
  }

  /// If a debugger session is attached and would otherwise be dropped on
  /// shutdown (a legacy blocking session, or one that opted into
  /// `NodeRuntime.notifyWhenWaitingForDisconnect` (e.g. Chrome DevTools)),
  /// block until it disconnects. Returns immediately when no such session
  /// is attached.
  ///
  /// This mirrors the wait the `deno run` event loop performs at the end of
  /// execution and is meant for tools like `deno test` / `deno bench` whose
  /// normal shutdown polls the event loop with a zero-duration timeout and
  /// would otherwise exit while a debugger is still attached.
  pub async fn wait_for_inspector_session_disconnect(
    &mut self,
  ) -> Result<(), CoreError> {
    let sessions_state = self.js_runtime.inspector().sessions_state();
    if sessions_state.has_active
      && (sessions_state.has_blocking
        || sessions_state.has_nonblocking_wait_for_disconnect)
    {
      self
        .js_runtime
        .run_event_loop(PollEventLoopOptions {
          wait_for_inspector: true,
        })
        .await?;
    }
    Ok(())
  }

  /// Return exit code set by the executed code (either in main worker
  /// or one of child web workers).
  pub fn exit_code(&self) -> i32 {
    self.exit_code.get()
  }

  /// Dispatches "load" event to the JavaScript runtime.
  ///
  /// Does not poll event loop, and thus not await any of the "load" event handlers.
  pub fn dispatch_load_event(&mut self) -> Result<(), Box<JsError>> {
    deno_core::scope!(scope, &mut self.js_runtime);
    v8::tc_scope!(tc_scope, scope);
    let dispatch_load_event_fn =
      v8::Local::new(tc_scope, &self.dispatch_load_event_fn_global);
    let undefined = v8::undefined(tc_scope);
    dispatch_load_event_fn.call(tc_scope, undefined.into(), &[]);
    if let Some(exception) = tc_scope.exception() {
      return Err(deno_core::exception_to_err(
        tc_scope, exception, false, true,
      ));
    }
    Ok(())
  }

  /// Dispatches "unload" event to the JavaScript runtime.
  ///
  /// Does not poll event loop, and thus not await any of the "unload" event handlers.
  pub fn dispatch_unload_event(&mut self) -> Result<(), Box<JsError>> {
    deno_core::scope!(scope, &mut self.js_runtime);
    v8::tc_scope!(tc_scope, scope);
    let dispatch_unload_event_fn =
      v8::Local::new(tc_scope, &self.dispatch_unload_event_fn_global);
    let undefined = v8::undefined(tc_scope);
    dispatch_unload_event_fn.call(tc_scope, undefined.into(), &[]);
    if let Some(exception) = tc_scope.exception() {
      return Err(deno_core::exception_to_err(
        tc_scope, exception, false, true,
      ));
    }
    Ok(())
  }

  /// Dispatches process.emit("exit") event for node compat.
  pub fn dispatch_process_exit_event(&mut self) -> Result<(), Box<JsError>> {
    deno_core::scope!(scope, &mut self.js_runtime);
    v8::tc_scope!(tc_scope, scope);
    let dispatch_process_exit_event_fn =
      v8::Local::new(tc_scope, &self.dispatch_process_exit_event_fn_global);
    let undefined = v8::undefined(tc_scope);
    dispatch_process_exit_event_fn.call(tc_scope, undefined.into(), &[]);
    if let Some(exception) = tc_scope.exception() {
      return Err(deno_core::exception_to_err(
        tc_scope, exception, false, true,
      ));
    }
    Ok(())
  }

  /// Runs pending NAPI ref/wrap finalizers by calling them directly while V8
  /// is still alive. This matches Node.js behavior where `napi_env::DeleteMe()`
  /// calls `RefTracker::FinalizeAll()` during environment teardown.
  pub fn run_napi_ref_finalizers(&mut self) {
    let finalizers = {
      let op_state = self.js_runtime.op_state();
      let op_state = op_state.borrow();
      match op_state.try_borrow::<deno_napi::NapiState>() {
        Some(s) => {
          let mut tracker = s.ref_tracker.borrow_mut();
          std::mem::take(&mut *tracker)
        }
        None => return,
      }
    };
    if finalizers.is_empty() {
      return;
    }

    // Call each finalizer in reverse (LIFO) order within a proper HandleScope
    // so native addon code can use NAPI functions during cleanup. LIFO matches
    // Node.js's linked list behavior (head insertion → reverse iteration).
    {
      deno_core::scope!(scope, &mut self.js_runtime);
      let _scope = v8::HandleScope::new(scope);
      for f in finalizers.into_iter().rev() {
        // SAFETY: The pointers (env, data, hint) were stored when the native
        // addon called napi_wrap/napi_create_external/napi_add_finalizer and
        // V8 is still alive (we have an active HandleScope).
        unsafe {
          (f.cb)(f.env, f.data, f.hint);
        }
      }
    }
  }

  /// Dispatches "beforeunload" event to the JavaScript runtime. Returns a boolean
  /// indicating if the event was prevented and thus event loop should continue
  /// running.
  pub fn dispatch_beforeunload_event(&mut self) -> Result<bool, Box<JsError>> {
    deno_core::scope!(scope, &mut self.js_runtime);
    v8::tc_scope!(tc_scope, scope);
    let dispatch_beforeunload_event_fn =
      v8::Local::new(tc_scope, &self.dispatch_beforeunload_event_fn_global);
    let undefined = v8::undefined(tc_scope);
    let ret_val =
      dispatch_beforeunload_event_fn.call(tc_scope, undefined.into(), &[]);
    if let Some(exception) = tc_scope.exception() {
      return Err(deno_core::exception_to_err(
        tc_scope, exception, false, true,
      ));
    }
    let ret_val = ret_val.unwrap();
    Ok(ret_val.is_false())
  }

  /// Dispatches process.emit("beforeExit") event for node compat.
  pub fn dispatch_process_beforeexit_event(
    &mut self,
  ) -> Result<bool, Box<JsError>> {
    deno_core::scope!(scope, &mut self.js_runtime);
    v8::tc_scope!(tc_scope, scope);
    let dispatch_process_beforeexit_event_fn = v8::Local::new(
      tc_scope,
      &self.dispatch_process_beforeexit_event_fn_global,
    );
    let undefined = v8::undefined(tc_scope);
    let ret_val = dispatch_process_beforeexit_event_fn.call(
      tc_scope,
      undefined.into(),
      &[],
    );
    if let Some(exception) = tc_scope.exception() {
      return Err(deno_core::exception_to_err(
        tc_scope, exception, false, true,
      ));
    }
    let ret_val = ret_val.unwrap();
    Ok(ret_val.is_true())
  }
}

fn common_extensions<
  TInNpmPackageChecker: InNpmPackageChecker + 'static,
  TNpmPackageFolderResolver: NpmPackageFolderResolver + 'static,
  TExtNodeSys: ExtNodeSys + 'static,
>(
  has_snapshot: bool,
  unconfigured_runtime: bool,
) -> Vec<Extension> {
  // NOTE(bartlomieju): ordering is important here, keep it in sync with
  // `runtime/worker.rs`, `runtime/web_worker.rs`, `runtime/snapshot_info.rs`
  // and `runtime/snapshot.rs`!
  vec![
    deno_telemetry::deno_telemetry::init(),
    // Web APIs
    deno_webidl::deno_webidl::init(),
    deno_web::deno_web::lazy_init(),
    deno_webgpu::deno_webgpu::init(),
    deno_image::deno_image::init(),
    deno_canvas::deno_canvas::init(),
    deno_fetch::deno_fetch::lazy_init(),
    deno_cache::deno_cache::lazy_init(),
    deno_websocket::deno_websocket::lazy_init(),
    deno_webstorage::deno_webstorage::lazy_init(),
    deno_crypto::deno_crypto::lazy_init(),
    deno_ffi::deno_ffi::lazy_init(),
    deno_net::deno_net::lazy_init(),
    deno_tls::deno_tls::init(),
    deno_kv::deno_kv::lazy_init(),
    deno_cron::deno_cron::init(Box::new(CronHandlerImpl::create_from_env())),
    deno_napi::deno_napi::lazy_init(),
    deno_http::deno_http::lazy_init(),
    deno_io::deno_io::lazy_init(),
    deno_fs::deno_fs::lazy_init(),
    deno_os::deno_os::lazy_init(),
    deno_process::deno_process::lazy_init(),
    deno_node_crypto::deno_node_crypto::init(),
    deno_node_sqlite::deno_node_sqlite::init(),
    deno_node::deno_node::lazy_init::<
      TInNpmPackageChecker,
      TNpmPackageFolderResolver,
      TExtNodeSys,
    >(),
    // Ops from this crate
    ops::runtime::deno_runtime::lazy_init(),
    ops::worker_host::deno_worker_host::lazy_init(),
    ops::fs_events::deno_fs_events::init(),
    ops::permissions::deno_permissions::init(),
    ops::tty::deno_tty::init(),
    ops::http::deno_http_runtime::init(),
    deno_bundle_runtime::deno_bundle_runtime::lazy_init(),
    ops::desktop::deno_desktop::init(),
    ops::bootstrap::deno_bootstrap::init(
      has_snapshot.then(Default::default),
      unconfigured_runtime,
    ),
    runtime::init(),
    // NOTE(bartlomieju): this is done, just so that ops from this extension
    // are available and importing them in `99_main.js` doesn't cause an
    // error because they're not defined. Trying to use these ops in non-worker
    // context will cause a panic.
    ops::web_worker::deno_web_worker::init().disable(),
  ]
}

struct CommonRuntimeOptions {
  module_loader: Rc<dyn ModuleLoader>,
  startup_snapshot: Option<&'static [u8]>,
  residual_lazy_js_sources: &'static [(&'static str, &'static str)],
  residual_lazy_esm_sources: &'static [(&'static str, &'static str)],
  create_params: Option<v8::CreateParams>,
  skip_op_registration: bool,
  shared_array_buffer_store: Option<SharedArrayBufferStore>,
  compiled_wasm_module_store: Option<CompiledWasmModuleStore>,
  extensions: Vec<Extension>,
  op_metrics_factory_fn: Option<OpMetricsFactoryFn>,
  enable_stack_trace_arg_in_ops: bool,
}

struct EnableRawImports(Arc<AtomicBool>);

fn common_runtime(opts: CommonRuntimeOptions) -> JsRuntime {
  let enable_raw_imports = Arc::new(AtomicBool::new(false));

  let js_runtime = JsRuntime::new(RuntimeOptions {
    module_loader: Some(opts.module_loader),
    startup_snapshot: opts.startup_snapshot,
    residual_lazy_js_sources: opts.residual_lazy_js_sources,
    residual_lazy_esm_sources: opts.residual_lazy_esm_sources,
    create_params: opts.create_params,
    skip_op_registration: opts.skip_op_registration,
    shared_array_buffer_store: opts.shared_array_buffer_store,
    compiled_wasm_module_store: opts.compiled_wasm_module_store,
    extensions: opts.extensions,
    #[cfg(feature = "transpile")]
    extension_transpiler: Some(Rc::new(|specifier, source| {
      crate::transpile::maybe_transpile_source(specifier, source)
    })),
    #[cfg(not(feature = "transpile"))]
    extension_transpiler: None,
    inspector: true,
    is_main: true,
    worker_id: None,
    op_metrics_factory_fn: opts.op_metrics_factory_fn,
    wait_for_inspector_disconnect_callback: Some(
      make_wait_for_inspector_disconnect_callback(),
    ),
    validate_import_attributes_cb: Some(
      create_validate_import_attributes_callback(enable_raw_imports.clone()),
    ),
    maybe_op_stack_trace_callback: opts
      .enable_stack_trace_arg_in_ops
      .then(create_permissions_stack_trace_callback),
    extension_code_cache: None,
    v8_platform: None,
    custom_module_evaluation_cb: Some(
      create_custom_module_evaluation_callback(),
    ),
    eval_context_code_cache_cbs: None,
  });

  js_runtime
    .op_state()
    .borrow_mut()
    .put(EnableRawImports(enable_raw_imports));

  js_runtime
}

pub fn create_permissions_stack_trace_callback()
-> deno_core::OpStackTraceCallback {
  Box::new(|stack: Vec<deno_core::error::JsStackFrame>| {
    deno_permissions::prompter::set_current_stacktrace(Box::new(move || {
      stack
        .iter()
        .map(|frame| {
          deno_core::error::format_frame::<deno_core::error::NoAnsiColors>(
            frame, None,
          )
        })
        .collect()
    }))
  }) as _
}

pub struct UnconfiguredRuntimeOptions {
  pub startup_snapshot: &'static [u8],
  pub residual_lazy_js_sources: &'static [(&'static str, &'static str)],
  pub residual_lazy_esm_sources: &'static [(&'static str, &'static str)],
  pub create_params: Option<v8::CreateParams>,
  pub shared_array_buffer_store: Option<SharedArrayBufferStore>,
  pub compiled_wasm_module_store: Option<CompiledWasmModuleStore>,
  pub additional_extensions: Vec<Extension>,
}

pub struct UnconfiguredRuntime {
  module_loader: Rc<PlaceholderModuleLoader>,
  js_runtime: JsRuntime,
}

impl UnconfiguredRuntime {
  pub fn new<
    TInNpmPackageChecker: InNpmPackageChecker + 'static,
    TNpmPackageFolderResolver: NpmPackageFolderResolver + 'static,
    TExtNodeSys: ExtNodeSys + 'static,
  >(
    options: UnconfiguredRuntimeOptions,
  ) -> Self {
    let mut extensions = common_extensions::<
      TInNpmPackageChecker,
      TNpmPackageFolderResolver,
      TExtNodeSys,
    >(true, true);

    extensions.extend(options.additional_extensions);

    let module_loader =
      Rc::new(PlaceholderModuleLoader(std::cell::RefCell::new(None)));

    let js_runtime = common_runtime(CommonRuntimeOptions {
      module_loader: module_loader.clone(),
      startup_snapshot: Some(options.startup_snapshot),
      residual_lazy_js_sources: options.residual_lazy_js_sources,
      residual_lazy_esm_sources: options.residual_lazy_esm_sources,
      create_params: options.create_params,
      skip_op_registration: true,
      shared_array_buffer_store: options.shared_array_buffer_store,
      compiled_wasm_module_store: options.compiled_wasm_module_store,
      extensions,
      op_metrics_factory_fn: None,
      enable_stack_trace_arg_in_ops: false,
    });

    UnconfiguredRuntime {
      module_loader,
      js_runtime,
    }
  }

  fn hydrate(self, module_loader: Rc<dyn ModuleLoader>) -> JsRuntime {
    let _ = self.module_loader.0.borrow_mut().insert(module_loader);
    self.js_runtime
  }
}

struct PlaceholderModuleLoader(
  std::cell::RefCell<Option<Rc<dyn ModuleLoader>>>,
);

impl ModuleLoader for PlaceholderModuleLoader {
  fn resolve(
    &self,
    specifier: &str,
    referrer: &str,
    kind: deno_core::ResolutionKind,
  ) -> deno_core::ModuleResolveResponse {
    self
      .0
      .borrow_mut()
      .clone()
      .unwrap()
      .resolve(specifier, referrer, kind)
  }

  fn load(
    &self,
    module_specifier: &ModuleSpecifier,
    maybe_referrer: Option<&ModuleLoadReferrer>,
    options: ModuleLoadOptions,
  ) -> deno_core::ModuleLoadResponse {
    self.0.borrow_mut().clone().unwrap().load(
      module_specifier,
      maybe_referrer,
      options,
    )
  }

  fn prepare_load(
    &self,
    module_specifier: &ModuleSpecifier,
    maybe_referrer: Option<String>,
    maybe_code: Option<String>,
    options: ModuleLoadOptions,
  ) -> std::pin::Pin<
    Box<
      dyn std::prelude::rust_2024::Future<
          Output = Result<(), deno_core::error::ModuleLoaderError>,
        >,
    >,
  > {
    self.0.borrow_mut().clone().unwrap().prepare_load(
      module_specifier,
      maybe_referrer,
      maybe_code,
      options,
    )
  }

  fn finish_load(&self) {
    self.0.borrow_mut().clone().unwrap().finish_load()
  }

  fn purge_and_prevent_code_cache(&self, module_specifier: &str) {
    self
      .0
      .borrow_mut()
      .clone()
      .unwrap()
      .purge_and_prevent_code_cache(module_specifier)
  }

  fn get_source_map(&self, file_name: &str) -> Option<Cow<'_, [u8]>> {
    let v = self.0.borrow_mut().clone().unwrap();
    let v = v.get_source_map(file_name);
    v.map(|c| Cow::from(c.into_owned()))
  }

  fn get_source_mapped_source_line(
    &self,
    file_name: &str,
    line_number: usize,
  ) -> Option<String> {
    self
      .0
      .borrow_mut()
      .clone()
      .unwrap()
      .get_source_mapped_source_line(file_name, line_number)
  }

  fn get_host_defined_options<'s>(
    &self,
    scope: &mut v8::PinScope<'s, '_>,
    name: &str,
  ) -> Option<v8::Local<'s, v8::Data>> {
    self
      .0
      .borrow_mut()
      .clone()
      .unwrap()
      .get_host_defined_options(scope, name)
  }
}
