// Copyright 2018-2026 the Deno authors. MIT license.

use deno_core::error::CoreError;
use deno_error::JsErrorBox;

use super::CodeCacheInfo;
use super::ModuleMap;
use super::is_internal_scheme;
use crate::ModuleLoadResponse;
use crate::ModuleSource;
use crate::ModuleSpecifier;
use crate::error::CoreErrorKind;
use crate::error::JsError;
use crate::error::exception_to_err;
use crate::modules::LazyEsmModuleLoader;
use crate::modules::ModuleCodeString;
use crate::modules::ModuleId;
use crate::modules::ModuleLoader;
use crate::modules::ModuleName;
use crate::modules::RequestedModuleType;
use crate::modules::loaders::ModuleLoadOptions;

fn residual_source_from_static_table(
  entries: &'static [(&'static str, &'static str)],
  specifier: &str,
) -> Option<ModuleCodeString> {
  entries
    .binary_search_by(|(entry_specifier, _)| entry_specifier.cmp(&specifier))
    .ok()
    .map(|index| {
      let code = entries[index].1;
      // SAFETY: Residual sources are generated by the snapshot build and
      // asserted to be ASCII there.
      unsafe { ModuleCodeString::from_ascii_static_unchecked(code) }
    })
}

fn residual_static_table_contains(
  entries: &'static [(&'static str, &'static str)],
  specifier: &str,
) -> bool {
  entries
    .binary_search_by(|(entry_specifier, _)| entry_specifier.cmp(&specifier))
    .is_ok()
}

fn debug_assert_residual_static_table_sorted(
  entries: &'static [(&'static str, &'static str)],
) {
  debug_assert!(
    entries.windows(2).all(|window| window[0].0 < window[1].0),
    "residual lazy source tables must be sorted by specifier",
  );
}

impl ModuleMap {
  /// Load and evaluate an ES module provided the specifier and source code.
  ///
  /// The module should not have Top-Level Await (that is, it should be
  /// possible to evaluate it synchronously).
  ///
  /// It is caller's responsibility to ensure that not duplicate specifiers are
  /// passed to this method.
  pub(crate) fn lazy_load_es_module_with_code(
    &self,
    scope: &mut v8::PinScope,
    module_specifier: &str,
    source_code: ModuleCodeString,
    code_cache_info: Option<CodeCacheInfo>,
  ) -> Result<v8::Global<v8::Value>, CoreError> {
    let specifier = ModuleSpecifier::parse(module_specifier)?;
    let previous_loading_internal_modules =
      is_internal_scheme(specifier.scheme())
        .then(|| self.loading_internal_modules.replace(true));
    let result = self.lazy_load_es_module_with_code_inner(
      scope,
      specifier,
      source_code,
      code_cache_info,
    );
    if let Some(previous) = previous_loading_internal_modules {
      self.loading_internal_modules.set(previous);
    }
    result
  }

  fn lazy_load_es_module_with_code_inner(
    &self,
    scope: &mut v8::PinScope,
    specifier: ModuleSpecifier,
    source_code: ModuleCodeString,
    code_cache_info: Option<CodeCacheInfo>,
  ) -> Result<v8::Global<v8::Value>, CoreError> {
    let mod_id = self
      .new_es_module(
        scope,
        false,
        specifier.into(),
        source_code,
        false,
        code_cache_info,
      )
      .map_err(|e| e.into_error(scope, false, true))?;

    self.instantiate_module(scope, mod_id).map_err(|e| {
      let exception = v8::Local::new(scope, e);
      exception_to_err(scope, exception, false, true)
    })?;

    let module_handle = self.get_handle(mod_id).unwrap();
    let module_local = v8::Local::<v8::Module>::new(scope, module_handle);

    let status = module_local.get_status();
    assert_eq!(status, v8::ModuleStatus::Instantiated);

    let value = module_local.evaluate(scope).unwrap();
    // Under Explicit microtask policy, drain microtasks so the module
    // evaluation promise resolves for synchronous modules.
    //
    // However, skip the checkpoint when we are inside a top-level
    // `module.evaluate()` call (i.e. `evaluating_top_level` is set).
    // Draining microtasks at this point can prematurely resolve
    // TLA-related microtasks (e.g. `await` resume jobs from eagerly-
    // resolved async ops), which prevents the module evaluation promise
    // from settling correctly later.
    if !self.evaluating_top_level.get() {
      scope.perform_microtask_checkpoint();
    }
    let promise = v8::Local::<v8::Promise>::try_from(value).unwrap();
    let result = promise.result(scope);
    if !result.is_undefined() {
      return Err(
        CoreErrorKind::Js(exception_to_err(scope, result, false, true))
          .into_box(),
      );
    }

    let status = module_local.get_status();
    assert_eq!(status, v8::ModuleStatus::Evaluated);

    let mod_ns = module_local.get_module_namespace();

    Ok(v8::Global::new(scope, mod_ns))
  }

  /// Check if a lazy-loaded ESM module is known to exist for the given
  /// specifier. This checks the metadata set which survives snapshotting,
  /// not just the source code map.
  pub(crate) fn has_lazy_esm_source(&self, specifier: &str) -> bool {
    let data = self.data.borrow();
    data.known_lazy_esm.borrow().contains(specifier)
      || residual_static_table_contains(
        data.residual_lazy_esm_sources,
        specifier,
      )
  }

  /// Get a lazy-loaded ESM source by specifier. Returns a cheap clone of the
  /// source code if found, keeping it in the lazy sources map so concurrent
  /// loads of the same lazy specifier from independent `RecursiveModuleLoad`
  /// instances each get their own copy. The duplicate `ModuleSource`s are
  /// deduplicated by `new_module_with_pending`'s `get_id` check at register
  /// time.
  pub(crate) fn take_lazy_esm_source(
    &self,
    specifier: &str,
  ) -> Option<ModuleCodeString> {
    let data = self.data.borrow();
    let mut sources = data.lazy_esm_sources.borrow_mut();
    let entry = match sources.get_mut(specifier) {
      Some(entry) => entry,
      None => {
        let source = residual_source_from_static_table(
          data.residual_lazy_esm_sources,
          specifier,
        )?;
        data
          .consumed_lazy_specifiers
          .borrow_mut()
          .insert(specifier.to_string());
        return Some(source);
      }
    };
    // `into_cheap_copy` always returns two cheap handles to the same backing
    // storage (Arc or Static), so we can keep one in the map and return the
    // other. The Owned variant gets promoted to Arc in the process.
    let placeholder = ModuleCodeString::from_static("");
    let owned = std::mem::replace(entry, placeholder);
    let (keep, give) = owned.into_cheap_copy();
    *entry = keep;
    data
      .consumed_lazy_specifiers
      .borrow_mut()
      .insert(specifier.to_string());
    Some(give)
  }

  pub(crate) fn add_lazy_loaded_esm_source(
    &self,
    specifier: ModuleName,
    code: ModuleCodeString,
  ) {
    let data = self.data.borrow_mut();
    data
      .known_lazy_esm
      .borrow_mut()
      .insert(std::borrow::Cow::Owned(specifier.as_str().to_string()));
    assert!(
      data
        .lazy_esm_sources
        .borrow_mut()
        .insert(specifier, code)
        .is_none()
    );
  }

  pub(crate) fn add_residual_lazy_loaded_sources(
    &self,
    lazy_js_sources: &'static [(&'static str, &'static str)],
    lazy_esm_sources: &'static [(&'static str, &'static str)],
  ) {
    debug_assert_residual_static_table_sorted(lazy_js_sources);
    debug_assert_residual_static_table_sorted(lazy_esm_sources);
    let mut data = self.data.borrow_mut();
    data.residual_lazy_script_sources = lazy_js_sources;
    data.residual_lazy_esm_sources = lazy_esm_sources;
  }

  /// Lazy load and evaluate an ES module. Only modules that have been added
  /// during build time can be executed (the ones stored in
  /// `ModuleMapData::lazy_esm_sources`), not _any, random_ module.
  /// Sync load + evaluate path for a `synthetic_esm` module. Used by the
  /// dynamic-import fast path. Returns the module namespace.
  pub(crate) fn lazy_load_synthetic_esm_module(
    &self,
    scope: &mut v8::PinScope,
    module_specifier: &str,
  ) -> Result<v8::Global<v8::Value>, CoreError> {
    // Use existing module if already constructed.
    let cached_handle = {
      let data = self.data.borrow();
      data
        .get_id(module_specifier, RequestedModuleType::None)
        .and_then(|id| data.get_handle(id))
    };
    if let Some(handle) = cached_handle {
      let handle_local = v8::Local::new(scope, handle);
      if handle_local.get_status() == v8::ModuleStatus::Instantiated {
        let value = handle_local.evaluate(scope).unwrap();
        if !self.evaluating_top_level.get() {
          scope.perform_microtask_checkpoint();
        }
        let promise = v8::Local::<v8::Promise>::try_from(value).unwrap();
        let result = promise.result(scope);
        if !result.is_undefined() {
          return Err(
            CoreErrorKind::Js(exception_to_err(scope, result, false, true))
              .into_box(),
          );
        }
      }
      return Ok(v8::Global::new(scope, handle_local.get_module_namespace()));
    }

    let module_id = self.build_synthetic_esm_module(scope, module_specifier)?;
    let handle = self.get_handle(module_id).unwrap();
    let handle_local = v8::Local::new(scope, handle);
    let value = handle_local.evaluate(scope).unwrap();
    if !self.evaluating_top_level.get() {
      scope.perform_microtask_checkpoint();
    }
    let promise = v8::Local::<v8::Promise>::try_from(value).unwrap();
    let result = promise.result(scope);
    if !result.is_undefined() {
      return Err(
        CoreErrorKind::Js(exception_to_err(scope, result, false, true))
          .into_box(),
      );
    }
    Ok(v8::Global::new(scope, handle_local.get_module_namespace()))
  }

  pub(crate) fn lazy_load_esm_module(
    &self,
    scope: &mut v8::PinScope,
    module_specifier: &str,
  ) -> Result<v8::Global<v8::Value>, CoreError> {
    if !self.has_lazy_esm_source(module_specifier) {
      return Err(
        JsErrorBox::generic(format!(
          "Specifier \"{module_specifier}\" cannot be lazy-loaded as it was not included in the binary."
        ))
        .into(),
      );
    }

    let (lazy_esm_sources, residual_lazy_esm_sources) = {
      let data = self.data.borrow();
      (
        data.lazy_esm_sources.clone(),
        data.residual_lazy_esm_sources,
      )
    };
    let loader =
      LazyEsmModuleLoader::new(lazy_esm_sources, residual_lazy_esm_sources);

    // Check if this module has already been loaded. We release the
    // `self.data` borrow before doing anything that could re-enter the
    // module map (notably `module.evaluate(scope)`, which can recursively
    // compile dependent modules and would otherwise panic with a
    // `RefCell already borrowed` at `new_module_from_js_source`).
    let cached_id_and_handle = {
      let module_map_data = self.data.borrow();
      module_map_data
        .get_id(module_specifier, RequestedModuleType::None)
        .and_then(|id| module_map_data.get_handle(id).map(|h| (id, h)))
    };
    if let Some((cached_id, handle)) = cached_id_and_handle {
      crate::modules::import_graph::record_lazy_esm_cached(
        scope,
        module_specifier,
      );
      let handle_local = v8::Local::new(scope, handle);
      // The module may be present in the map but not yet instantiated --
      // e.g. when this lazy load fires from inside a sibling module's
      // resolve-callback (a `synthetic_esm` backing script synchronously
      // calling `op_lazy_load_esm` for a peer that the surrounding
      // `RecursiveModuleLoad` has registered but not yet instantiated).
      // `get_module_namespace` requires Instantiated+, so drive the module
      // forward synchronously here.
      if handle_local.get_status() == v8::ModuleStatus::Uninstantiated {
        self.instantiate_module(scope, cached_id).map_err(|e| {
          let exception = v8::Local::new(scope, e);
          exception_to_err(scope, exception, false, true)
        })?;
      }
      // Returning the namespace before evaluation leaves `export const`
      // bindings in the temporal dead zone, so trigger evaluation here.
      if handle_local.get_status() == v8::ModuleStatus::Instantiated {
        let value = handle_local.evaluate(scope).unwrap();
        if !self.evaluating_top_level.get() {
          scope.perform_microtask_checkpoint();
        }
        let promise = v8::Local::<v8::Promise>::try_from(value).unwrap();
        let result = promise.result(scope);
        if !result.is_undefined() {
          return Err(
            CoreErrorKind::Js(exception_to_err(scope, result, false, true))
              .into_box(),
          );
        }
      }
      let module = v8::Global::new(scope, handle_local.get_module_namespace());
      return Ok(module);
    }

    // Cache miss: real load incoming. This is the call that pays the
    // parse/compile/evaluate cost at runtime.
    crate::modules::import_graph::record_lazy_esm(scope, module_specifier);

    let specifier = ModuleSpecifier::parse(module_specifier)?;

    let load_response = loader.load(
      &specifier,
      None,
      ModuleLoadOptions {
        is_dynamic_import: false,
        is_synchronous: false,
        requested_module_type: RequestedModuleType::None,
      },
    );

    let source = match load_response {
      ModuleLoadResponse::Sync(result) => result,
      ModuleLoadResponse::Async(fut) => futures::executor::block_on(fut),
    }?;

    // `LazyEsmModuleLoader` only knows the in-binary static sources and has no
    // DENO_DIR access, so `source.code_cache` is always `None` here. Read the
    // persisted V8 code cache from the REAL loader (Cli/EmbeddedModuleLoader)
    // instead — the same seam the residual `lazy_loaded_js` path uses. Without
    // this, residual ESM (node:process, node:module, the stream/net/tty
    // closure) re-pays parse+compile in every isolate on every cold start.
    let source_code = ModuleSource::get_string_source(source.code);
    // Hash key for the on-disk cache. `v8_string` borrows `&source_code`, so it
    // stays usable for the compile below.
    let v8_source = source_code.v8_string(scope).unwrap();
    // Build a `CodeCacheInfo` whenever the loader returns `Some` — even when
    // `data` is `None` (cold run). The `Some`-with-`data: None` case is what
    // arms the write side so the first run stores; warm runs consume.
    let code_cache_info = self
      .loader
      .borrow()
      .get_code_cache(&specifier, &v8_source)
      .map(|info| {
        let loader = self.loader.borrow().clone();
        CodeCacheInfo {
          data: info.data,
          // `specifier` is unused after this, so move it straight in.
          ready_callback: Box::new(move |cache| {
            loader.code_cache_ready(specifier, info.hash, cache)
          }),
        }
      });

    self.lazy_load_es_module_with_code(
      scope,
      module_specifier,
      source_code,
      code_cache_info,
    )
  }

  pub(crate) fn add_lazy_loaded_script_source(
    &self,
    specifier: ModuleName,
    code: ModuleCodeString,
  ) {
    let data = self.data.borrow_mut();
    assert!(
      data
        .lazy_script_sources
        .borrow_mut()
        .insert(specifier, code)
        .is_none(),
      "Duplicate lazy script source"
    );
  }

  /// Check if a specifier was registered as a `synthetic_esm` module.
  pub(crate) fn has_synthetic_esm_module(&self, specifier: &str) -> bool {
    let data = self.data.borrow();
    let modules = data.synthetic_esm_modules.borrow();
    let key: ModuleName = String::from(specifier).into();
    modules.contains_key(&key)
  }

  /// Register a `(module_specifier -> backing_script_specifier)` mapping
  /// for the `synthetic_esm` dispatch. Called at extension init from each
  /// extension's `synthetic_esm_modules` list.
  pub(crate) fn add_synthetic_esm_module(
    &self,
    module_specifier: ModuleName,
    backing_specifier: ModuleName,
  ) {
    let data = self.data.borrow();
    assert!(
      data
        .synthetic_esm_modules
        .borrow_mut()
        .insert(module_specifier, backing_specifier)
        .is_none(),
      "Duplicate synthetic_esm module mapping"
    );
  }

  /// Build a synthetic module for a `synthetic_esm`-registered specifier
  /// by evaluating its backing script (cache hit on second+ call) and
  /// deriving exports from the returned IIFE object. Returns the new
  /// `ModuleId`, or an error if the specifier is not registered or the
  /// backing script did not return an object.
  pub(crate) fn build_synthetic_esm_module(
    &self,
    scope: &mut v8::PinScope,
    specifier: &str,
  ) -> Result<ModuleId, CoreError> {
    let backing_specifier = {
      let data = self.data.borrow();
      let modules = data.synthetic_esm_modules.borrow();
      let key: ModuleName = String::from(specifier).into();
      modules.get(&key).map(|v| v.as_str().to_string())
    }
    .ok_or_else(|| {
      CoreError::from(JsErrorBox::generic(format!(
        "Specifier {specifier} is not a synthetic_esm module"
      )))
    })?;

    let exports_global = self.load_ext_script(scope, &backing_specifier)?;
    let exports_local = v8::Local::new(scope, exports_global);
    let exports_obj = v8::Local::<v8::Object>::try_from(exports_local)
      .map_err(|_| {
        CoreError::from(JsErrorBox::type_error(format!(
          "synthetic_esm backing script {backing_specifier} did not return an object"
        )))
      })?;

    Ok(self.new_synthetic_module_from_exports_object(
      scope,
      String::from(specifier),
      exports_obj,
    ))
  }

  /// Convenience wrapper around `build_synthetic_esm_module` for the V8
  /// `module_resolve_callback` path: returns the module's V8 handle if
  /// `specifier` is registered as a `synthetic_esm` target, or `None` if
  /// not. Throws into `scope` on evaluation failure.
  pub(super) fn try_resolve_synthetic_esm<'s, 'i>(
    &self,
    scope: &mut v8::PinScope<'s, 'i>,
    specifier: &str,
  ) -> Option<v8::Local<'s, v8::Module>> {
    if !self.has_synthetic_esm_module(specifier) {
      return None;
    }
    match self.build_synthetic_esm_module(scope, specifier) {
      Ok(module_id) => {
        let handle = self.get_handle(module_id)?;
        Some(v8::Local::new(scope, handle))
      }
      Err(e) => {
        crate::error::throw_js_error_class(scope, &e);
        None
      }
    }
  }

  /// Load and evaluate a script on demand. The evaluated result is
  /// cached in `loaded_script_results` so later callers (the JS
  /// `Deno.core.loadExtScript()` op, the `synthetic_esm` dispatch, etc.)
  /// share a single evaluation — the source is consumed on first eval,
  /// and re-evaluating polyfill IIFEs would clobber registered hooks and
  /// duplicate class identities. Circular dependencies are detected and
  /// cause an error.
  pub(crate) fn load_ext_script(
    &self,
    scope: &mut v8::PinScope,
    specifier: &str,
  ) -> Result<v8::Global<v8::Value>, CoreError> {
    crate::modules::import_graph::record_lazy_script(scope, specifier);
    let specifier_str = String::from(specifier);
    let data = self.data.borrow();

    // Cache hit: return the previously evaluated result.
    {
      let specifier_key: ModuleName = specifier_str.clone().into();
      if let Some(cached) =
        data.loaded_script_results.borrow().get(&specifier_key)
      {
        return Ok(cached.clone());
      }
    }

    // Circular dependency detection.
    {
      let specifier_key: ModuleName = specifier_str.clone().into();
      let loading = data.lazy_script_loading.borrow();
      if loading.contains(&specifier_key) {
        return Err(
          JsErrorBox::generic(format!(
            "Circular dependency detected when loading script \"{specifier}\""
          ))
          .into(),
        );
      }
    }

    // Look up source.
    let source = {
      let specifier_key: ModuleName = specifier_str.clone().into();
      let mut sources = data.lazy_script_sources.borrow_mut();
      sources.remove(&specifier_key).or_else(|| {
        residual_source_from_static_table(
          data.residual_lazy_script_sources,
          specifier,
        )
      })
    };
    let source = match source {
      Some(s) => s,
      None => {
        return Err(
          JsErrorBox::generic(format!(
            "Script \"{specifier}\" cannot be lazy-loaded as it was not included in the binary."
          ))
          .into(),
        );
      }
    };

    data
      .consumed_lazy_specifiers
      .borrow_mut()
      .insert(specifier_str.clone());

    // Mark as loading for circular dep detection.
    data
      .lazy_script_loading
      .borrow_mut()
      .insert(specifier_str.clone().into());

    // We need to drop `data` before executing the script, since the script
    // may call back into `load_ext_script` for its own dependencies.
    drop(data);

    // Each script's IIFE preamble destructures the snapshot-time
    // `__bootstrap` view (a frozen clone of `core.ops` captured before
    // `removeImportedOps()` runs). To avoid leaving `__bootstrap` on
    // `globalThis` (where user code or `Object.keys` would see it), we
    // compile the source as the body of a function with one named
    // parameter `__bootstrap` and invoke it with the captured value. The
    // script's `(function () { ... })()` is the function body's single
    // expression, so we wrap it with `return ( ... );` to surface the
    // IIFE's result as the function's return value.
    let name = v8::String::new(scope, specifier).unwrap();
    let origin = v8::ScriptOrigin::new(
      scope,
      name.into(),
      0,
      0,
      false,
      -1,
      None,
      false,
      false,
      false,
      None,
    );

    v8::tc_scope!(let tc_scope, scope);

    // The compile_function body is `"use strict"; return (<IIFE>);`. For
    // residual lazy scripts this wrapping is performed at build time
    // (`cli/snapshot/build.rs`), so `source` is a `&'static` external string we
    // can hand to V8 without an owned heap copy (avoiding a per-script source
    // string in the V8 heap). Sources that arrive unwrapped (e.g. consumed
    // during snapshot creation, before that path is build-time wrapped) are
    // wrapped here at runtime via `wrap_lazy_ext_script`.
    let v8_source = if AsRef::<str>::as_ref(&source)
      .starts_with("\"use strict\"; return (")
    {
      // Build-time wrapped residual: hand V8 the `&'static` external string
      // directly so the source stays off the V8 heap (file-backed/clean).
      source.v8_string(tc_scope).unwrap()
    } else {
      // Unwrapped (e.g. consumed during snapshot creation): wrap at runtime.
      let wrapped_source = wrap_lazy_ext_script(AsRef::<str>::as_ref(&source));
      v8::String::new(tc_scope, &wrapped_source).unwrap()
    };
    let bootstrap_param = v8::String::new(tc_scope, "__bootstrap").unwrap();
    // Persist a V8 code cache for this residual `lazy_loaded_js` module through
    // the same on-disk (DENO_DIR) cache user scripts use, keyed by specifier +
    // source hash. Residuals ship source-only in the binary, so without this a
    // node-heavy program re-pays parse+compile of the whole node-polyfill
    // closure at runtime on every cold start. The first run compiles and
    // stores; warm runs consume and skip parse+compile. Producing and consuming
    // binary are identical, so the cache is always accepted.
    let cache_specifier = crate::ModuleSpecifier::parse(&specifier_str).ok();
    let code_cache_info = cache_specifier
      .as_ref()
      .and_then(|spec| self.loader.borrow().get_code_cache(spec, &v8_source));
    let (mut compile_source, compile_options) =
      match code_cache_info.as_ref().and_then(|i| i.data.as_ref()) {
        Some(data) => (
          v8::script_compiler::Source::new_with_cached_data(
            v8_source,
            Some(&origin),
            v8::CachedData::new(data),
          ),
          v8::script_compiler::CompileOptions::ConsumeCodeCache,
        ),
        None => (
          v8::script_compiler::Source::new(v8_source, Some(&origin)),
          v8::script_compiler::CompileOptions::NoCompileOptions,
        ),
      };
    let function = match v8::script_compiler::compile_function(
      tc_scope,
      &mut compile_source,
      &[bootstrap_param],
      &[],
      compile_options,
      v8::script_compiler::NoCacheReason::NoReason,
    ) {
      Some(f) => f,
      None => {
        let exception = tc_scope.exception().unwrap();
        let err = JsError::from_v8_exception(tc_scope, exception);
        self
          .data
          .borrow()
          .lazy_script_loading
          .borrow_mut()
          .remove(&ModuleName::from(specifier_str.clone()));
        return Err(CoreErrorKind::Js(err).into_box());
      }
    };
    // Store the freshly-compiled cache on the first run (cold), or if V8
    // rejected the existing cache (e.g. the source changed).
    let rejected = compile_source
      .get_cached_data()
      .map(|d| d.rejected())
      .unwrap_or(true);
    let had_data = code_cache_info
      .as_ref()
      .map(|i| i.data.is_some())
      .unwrap_or(false);
    if (!had_data || rejected)
      && let (Some(spec), Some(info)) =
        (cache_specifier.as_ref(), code_cache_info.as_ref())
      && let Some(cache) = function.create_code_cache()
    {
      let fut = self.loader.borrow().code_cache_ready(
        spec.clone(),
        info.hash,
        &cache[..],
      );
      self.code_cache_ready_futs.push(fut);
    }

    let captured = self.data.borrow().captured_bootstrap.borrow().clone();
    let bootstrap_arg = match &captured {
      Some(global) => v8::Local::new(tc_scope, global),
      None => v8::undefined(tc_scope).into(),
    };
    let undefined: v8::Local<v8::Value> = v8::undefined(tc_scope).into();
    let result = match function.call(tc_scope, undefined, &[bootstrap_arg]) {
      Some(value) => v8::Global::new(tc_scope, value),
      None => {
        assert!(tc_scope.has_caught());
        let exception = tc_scope.exception().unwrap();
        let err = JsError::from_v8_exception(tc_scope, exception);
        self
          .data
          .borrow()
          .lazy_script_loading
          .borrow_mut()
          .remove(&ModuleName::from(specifier_str.clone()));
        return Err(CoreErrorKind::Js(err).into_box());
      }
    };

    // Remove from loading set and cache the result.
    {
      let data = self.data.borrow();
      data
        .lazy_script_loading
        .borrow_mut()
        .remove(&ModuleName::from(specifier_str.clone()));
      data
        .loaded_script_results
        .borrow_mut()
        .insert(ModuleName::from(specifier_str), result.clone());
    }

    Ok(result)
  }

  /// Stash the snapshot-time `__bootstrap` view registered from JS via
  /// `op_set_captured_bootstrap`. `load_ext_script` injects this value as
  /// the `__bootstrap` parameter when invoking each lazy script's compiled
  /// function — keeping the value off `globalThis` so user code never sees
  /// it.
  pub(crate) fn set_captured_bootstrap(&self, value: v8::Global<v8::Value>) {
    *self.data.borrow().captured_bootstrap.borrow_mut() = Some(value);
  }

  /// The snapshot-time `__bootstrap` view stashed by `set_captured_bootstrap`,
  /// if any. Used by the deferred fast-call upgrade to also update the cloned
  /// `core.ops` that residual ext modules read through.
  pub(crate) fn captured_bootstrap(&self) -> Option<v8::Global<v8::Value>> {
    self.data.borrow().captured_bootstrap.borrow().clone()
  }
}

/// Skip past a lazy-loaded script's leading prologue (line/block comments and
/// directive prologue like `"use strict";`) and return a slice that starts at
/// the IIFE's opening `(function`. Returns `None` if no such opener is found
/// — callers fall back to using the original source unchanged.
fn strip_script_prologue(source: &str) -> Option<&str> {
  let mut rest = source;
  loop {
    let trimmed = rest.trim_start();
    if let Some(after) = trimmed.strip_prefix("//") {
      // Line comment — skip to end of line.
      let nl = after.find('\n').map(|i| i + 1).unwrap_or(after.len());
      rest = &after[nl..];
      continue;
    }
    if let Some(after) = trimmed.strip_prefix("/*") {
      // Block comment — skip to closing `*/`.
      let end = after.find("*/").map(|i| i + 2).unwrap_or(after.len());
      rest = &after[end..];
      continue;
    }
    // Directive prologue: `"use strict";` or `'use strict';` (or any other
    // string-literal directive). A directive is a string literal followed
    // by a `;`/newline at the statement boundary.
    if trimmed.starts_with('"') || trimmed.starts_with('\'') {
      let quote = trimmed.as_bytes()[0];
      let after_quote = &trimmed[1..];
      if let Some(close) = after_quote.find(quote as char) {
        let after_str = after_quote[close + 1..].trim_start();
        if let Some(after_semi) = after_str.strip_prefix(';') {
          rest = after_semi;
          continue;
        }
      }
    }
    if trimmed.starts_with("(function") {
      return Some(trimmed);
    }
    return None;
  }
}

/// Wrap a lazy ext-script (`loadExtScript`) source into the `compile_function`
/// body we evaluate at load time: `"use strict"; return (<IIFE>);`.
///
/// The IIFE's completion value is the script's exports object (what the
/// original `Script::run` produced). We strip the leading prologue (comments +
/// any `"use strict";` directive — a bare directive is a statement and can't
/// sit inside `return ( ... )`) and the trailing `;`/whitespace so the IIFE is
/// the single operand of `return ( ... )`, and re-emit `"use strict";` at the
/// head so the body still runs in strict mode.
///
/// Performed at build time for residual lazy scripts so the stored source is a
/// `&'static` string that compiles without an owned heap copy; also used as the
/// runtime fallback for sources that arrive unwrapped.
pub fn wrap_lazy_ext_script(source: &str) -> String {
  let expr_start = strip_script_prologue(source).unwrap_or(source);
  let trimmed =
    expr_start.trim_end_matches(|c: char| c.is_whitespace() || c == ';');
  format!("\"use strict\"; return ({trimmed});")
}
