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

use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt::Debug;
use std::io::BufReader;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;

use anyhow::Error as AnyError;
use anyhow::bail;
use deno_media_type::MediaType;
use deno_package_json::BrowserMapEntry;
use deno_package_json::PackageJson;
use deno_package_json::PackageJsonRc;
use deno_path_util::url_to_file_path;
use deno_semver::Version;
use deno_semver::VersionReq;
use lazy_regex::Lazy;
use regex::Regex;
use serde_json::Map;
use serde_json::Value;
use sys_traits::FileType;
use sys_traits::FsCanonicalize;
use sys_traits::FsDirEntry;
use sys_traits::FsMetadata;
use sys_traits::FsOpen;
use sys_traits::FsRead;
use sys_traits::FsReadDir;
use sys_traits::OpenOptions;
use url::Url;

use crate::InNpmPackageChecker;
use crate::IsBuiltInNodeModuleChecker;
use crate::NpmPackageFolderResolver;
use crate::PackageJsonResolverRc;
use crate::PathClean;
use crate::cache::NodeResolutionSys;
use crate::errors;
use crate::errors::BrowserMapDisabledError;
use crate::errors::DataUrlReferrerError;
use crate::errors::FinalizeResolutionError;
use crate::errors::InvalidModuleSpecifierError;
use crate::errors::InvalidPackageTargetError;
use crate::errors::LegacyResolveError;
use crate::errors::MissingPkgJsonError;
use crate::errors::ModuleNotFoundError;
use crate::errors::NodeJsErrorCode;
use crate::errors::NodeJsErrorCoded;
use crate::errors::NodeResolveError;
use crate::errors::NodeResolveRelativeJoinError;
use crate::errors::PackageExportsResolveError;
use crate::errors::PackageImportNotDefinedError;
use crate::errors::PackageImportsResolveError;
use crate::errors::PackageImportsResolveErrorKind;
use crate::errors::PackagePathNotExportedError;
use crate::errors::PackageResolveError;
use crate::errors::PackageSubpathFromDenoModuleResolveError;
use crate::errors::PackageSubpathResolveError;
use crate::errors::PackageSubpathResolveErrorKind;
use crate::errors::PackageTargetNotFoundError;
use crate::errors::PackageTargetResolveError;
use crate::errors::PackageTargetResolveErrorKind;
use crate::errors::ResolvePkgJsonBinExportError;
use crate::errors::ResolvePkgNpmBinaryCommandsError;
use crate::errors::TypesNotFoundError;
use crate::errors::TypesNotFoundErrorData;
use crate::errors::UnknownBuiltInNodeModuleError;
use crate::errors::UnsupportedDirImportError;
use crate::errors::UnsupportedEsmUrlSchemeError;
use crate::path::UrlOrPath;
use crate::path::UrlOrPathRef;

pub static IMPORT_CONDITIONS: &[Cow<'static, str>] = &[
  Cow::Borrowed("deno"),
  Cow::Borrowed("node"),
  Cow::Borrowed("import"),
  Cow::Borrowed("module-sync"),
];
pub static REQUIRE_CONDITIONS: &[Cow<'static, str>] = &[
  Cow::Borrowed("require"),
  Cow::Borrowed("node"),
  Cow::Borrowed("module-sync"),
];
static TYPES_ONLY_CONDITIONS: &[Cow<'static, str>] = &[Cow::Borrowed("types")];

#[derive(Debug, Default, Clone)]
pub struct NodeConditionOptions {
  pub conditions: Vec<Cow<'static, str>>,
  /// Provide a value to override the default import conditions.
  ///
  /// Defaults to `["deno", "node", "import", "module-sync"]`
  pub import_conditions_override: Option<Vec<Cow<'static, str>>>,
  /// Provide a value to override the default require conditions.
  ///
  /// Defaults to `["require", "node", "module-sync"]`
  pub require_conditions_override: Option<Vec<Cow<'static, str>>>,
}

#[derive(Debug, Clone)]
struct ConditionResolver {
  import_conditions: Cow<'static, [Cow<'static, str>]>,
  require_conditions: Cow<'static, [Cow<'static, str>]>,
}

impl ConditionResolver {
  pub fn new(options: NodeConditionOptions) -> Self {
    fn combine_conditions(
      user_conditions: Cow<'_, [Cow<'static, str>]>,
      override_default: Option<Vec<Cow<'static, str>>>,
      default_conditions: &'static [Cow<'static, str>],
    ) -> Cow<'static, [Cow<'static, str>]> {
      let default_conditions = override_default
        .map(Cow::Owned)
        .unwrap_or(Cow::Borrowed(default_conditions));
      if user_conditions.is_empty() {
        default_conditions
      } else {
        let mut new =
          Vec::with_capacity(user_conditions.len() + default_conditions.len());
        let mut append =
          |conditions: Cow<'_, [Cow<'static, str>]>| match conditions {
            Cow::Borrowed(conditions) => new.extend(conditions.iter().cloned()),
            Cow::Owned(conditions) => new.extend(conditions),
          };
        append(user_conditions);
        append(default_conditions);
        Cow::Owned(new)
      }
    }

    Self {
      import_conditions: combine_conditions(
        Cow::Borrowed(&options.conditions),
        options.import_conditions_override,
        IMPORT_CONDITIONS,
      ),
      require_conditions: combine_conditions(
        Cow::Owned(options.conditions),
        options.require_conditions_override,
        REQUIRE_CONDITIONS,
      ),
    }
  }

  pub fn resolve(
    &self,
    resolution_mode: ResolutionMode,
  ) -> &[Cow<'static, str>] {
    match resolution_mode {
      ResolutionMode::Import => &self.import_conditions,
      ResolutionMode::Require => &self.require_conditions,
    }
  }

  pub fn require_conditions(&self) -> &[Cow<'static, str>] {
    &self.require_conditions
  }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ResolutionMode {
  Import,
  Require,
}

impl ResolutionMode {
  pub fn default_conditions(&self) -> &'static [Cow<'static, str>] {
    match self {
      ResolutionMode::Import => IMPORT_CONDITIONS,
      ResolutionMode::Require => REQUIRE_CONDITIONS,
    }
  }

  #[cfg(feature = "graph")]
  pub fn from_deno_graph(mode: deno_graph::source::ResolutionMode) -> Self {
    use deno_graph::source::ResolutionMode::*;
    match mode {
      Import => Self::Import,
      Require => Self::Require,
    }
  }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NodeResolutionKind {
  Execution,
  Types,
}

impl NodeResolutionKind {
  pub fn is_types(&self) -> bool {
    matches!(self, NodeResolutionKind::Types)
  }

  #[cfg(feature = "graph")]
  pub fn from_deno_graph(kind: deno_graph::source::ResolutionKind) -> Self {
    use deno_graph::source::ResolutionKind::*;
    match kind {
      Execution => Self::Execution,
      Types => Self::Types,
    }
  }
}

#[derive(Debug)]
pub enum NodeResolution {
  Module(UrlOrPath),
  BuiltIn(String),
}

impl NodeResolution {
  pub fn into_url(self) -> Result<Url, NodeResolveError> {
    match self {
      Self::Module(u) => Ok(u.into_url()?),
      Self::BuiltIn(specifier) => Ok(if specifier.starts_with("node:") {
        Url::parse(&specifier).unwrap()
      } else {
        Url::parse(&format!("node:{specifier}")).unwrap()
      }),
    }
  }
}

struct LocalPath {
  path: PathBuf,
  known_exists: bool,
}

enum LocalUrlOrPath {
  Url(Url),
  Path(LocalPath),
}

impl LocalUrlOrPath {
  pub fn into_url_or_path(self) -> UrlOrPath {
    match self {
      LocalUrlOrPath::Url(url) => UrlOrPath::Url(url),
      LocalUrlOrPath::Path(local_path) => UrlOrPath::Path(local_path.path),
    }
  }
}

/// This struct helps ensure we remember to probe for
/// declaration files and to prevent accidentally probing
/// multiple times.
struct MaybeTypesResolvedUrl(LocalUrlOrPath);

/// Kind of method that resolution succeeded with.
enum ResolvedMethod {
  Url,
  RelativeOrAbsolute,
  PackageImports,
  PackageExports,
  PackageSubPath,
}

#[derive(Debug, Default, Clone)]
pub struct NodeResolverOptions {
  pub conditions: NodeConditionOptions,
  pub is_browser_platform: bool,
  pub bundle_mode: bool,
  /// TypeScript version to use for typesVersions resolution and
  /// `types@req` exports resolution.
  pub typescript_version: Option<Version>,
}

/// Result of consulting the `browser` map on a `package.json`.
struct BrowserMapMatch {
  entry: BrowserMapEntry,
  pkg_json: PackageJsonRc,
}

/// Extensions probed when matching a relative-path `browser` map key against a
/// resolved file path. Mirrors the suffixes Node's `require()` algorithm tries
/// after the bare path, so an entry like `"./foo": false` also catches
/// `./foo.js`, `./foo.cjs`, `./foo/index.js`, etc.
const BROWSER_MAP_PROBE_EXTS: &[&str] =
  &[".js", ".mjs", ".cjs", ".json", ".node"];

fn browser_map_key_matches(key_path: &Path, resolved_path: &Path) -> bool {
  if key_path == resolved_path {
    return true;
  }
  for ext in BROWSER_MAP_PROBE_EXTS {
    // strip the leading '.' so OsString::push("js") gives "foo.js".
    let ext = &ext[1..];
    let mut with_ext = key_path.as_os_str().to_owned();
    with_ext.push(".");
    with_ext.push(ext);
    if Path::new(&with_ext) == resolved_path {
      return true;
    }
    let index_path = key_path.join("index").with_extension(ext);
    if index_path == resolved_path {
      return true;
    }
  }
  false
}

#[derive(Debug)]
struct ResolutionConfig {
  pub bundle_mode: AtomicBool,
  pub prefer_browser_field: bool,
  pub typescript_version: Option<Version>,
}

#[sys_traits::auto_impl]
pub trait NodeResolverSys:
  FsCanonicalize + FsMetadata + FsRead + FsReadDir + FsOpen
{
}

#[allow(clippy::disallowed_types, reason = "definition")]
pub type NodeResolverRc<
  TInNpmPackageChecker,
  TIsBuiltInNodeModuleChecker,
  TNpmPackageFolderResolver,
  TSys,
> = deno_maybe_sync::MaybeArc<
  NodeResolver<
    TInNpmPackageChecker,
    TIsBuiltInNodeModuleChecker,
    TNpmPackageFolderResolver,
    TSys,
  >,
>;

#[derive(Debug)]
pub struct NodeResolver<
  TInNpmPackageChecker: InNpmPackageChecker,
  TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
  TNpmPackageFolderResolver: NpmPackageFolderResolver,
  TSys: NodeResolverSys,
> {
  in_npm_pkg_checker: TInNpmPackageChecker,
  is_built_in_node_module_checker: TIsBuiltInNodeModuleChecker,
  npm_pkg_folder_resolver: TNpmPackageFolderResolver,
  pkg_json_resolver: PackageJsonResolverRc<TSys>,
  sys: NodeResolutionSys<TSys>,
  condition_resolver: ConditionResolver,
  resolution_config: ResolutionConfig,
}

impl<
  TInNpmPackageChecker: InNpmPackageChecker,
  TIsBuiltInNodeModuleChecker: IsBuiltInNodeModuleChecker,
  TNpmPackageFolderResolver: NpmPackageFolderResolver,
  TSys: NodeResolverSys,
>
  NodeResolver<
    TInNpmPackageChecker,
    TIsBuiltInNodeModuleChecker,
    TNpmPackageFolderResolver,
    TSys,
  >
{
  pub fn new(
    in_npm_pkg_checker: TInNpmPackageChecker,
    is_built_in_node_module_checker: TIsBuiltInNodeModuleChecker,
    npm_pkg_folder_resolver: TNpmPackageFolderResolver,
    pkg_json_resolver: PackageJsonResolverRc<TSys>,
    sys: NodeResolutionSys<TSys>,
    options: NodeResolverOptions,
  ) -> Self {
    Self {
      in_npm_pkg_checker,
      is_built_in_node_module_checker,
      npm_pkg_folder_resolver,
      pkg_json_resolver,
      sys,
      condition_resolver: ConditionResolver::new(NodeConditionOptions {
        conditions: options.conditions.conditions,
        import_conditions_override: options
          .conditions
          .import_conditions_override
          .or_else(|| {
            if options.is_browser_platform {
              Some(vec![Cow::Borrowed("browser"), Cow::Borrowed("import")])
            } else {
              None
            }
          }),
        require_conditions_override: options
          .conditions
          .require_conditions_override
          .or_else(|| {
            if options.is_browser_platform {
              Some(vec![Cow::Borrowed("browser"), Cow::Borrowed("require")])
            } else {
              None
            }
          }),
      }),
      resolution_config: ResolutionConfig {
        bundle_mode: AtomicBool::new(options.bundle_mode),
        prefer_browser_field: options.is_browser_platform,
        typescript_version: options.typescript_version,
      },
    }
  }

  pub fn require_conditions(&self) -> &[Cow<'static, str>] {
    self.condition_resolver.require_conditions()
  }

  /// Updates whether the resolver should treat resolutions as occurring
  /// inside a bundler (e.g. when `moduleResolution: "bundler"` is set). When
  /// enabled, directory imports do not error and a CommonJS-style fallback
  /// resolution is used for extensionless files.
  pub fn set_bundle_mode(&self, bundle_mode: bool) {
    self
      .resolution_config
      .bundle_mode
      .store(bundle_mode, Ordering::Relaxed);
  }

  fn bundle_mode(&self) -> bool {
    self.resolution_config.bundle_mode.load(Ordering::Relaxed)
  }

  pub fn in_npm_package(&self, specifier: &Url) -> bool {
    self.in_npm_pkg_checker.in_npm_package(specifier)
  }

  #[inline(always)]
  pub fn is_builtin_node_module(&self, specifier: &str) -> bool {
    self
      .is_built_in_node_module_checker
      .is_builtin_node_module(specifier)
  }

  /// This function is an implementation of `defaultResolve` in
  /// `lib/internal/modules/esm/resolve.js` from Node.
  pub fn resolve(
    &self,
    specifier: &str,
    referrer: &Url,
    resolution_mode: ResolutionMode,
    resolution_kind: NodeResolutionKind,
  ) -> Result<NodeResolution, NodeResolveError> {
    self.resolve_internal(
      specifier,
      referrer,
      resolution_mode,
      resolution_kind,
      true,
    )
  }

  fn resolve_internal(
    &self,
    specifier: &str,
    referrer: &Url,
    resolution_mode: ResolutionMode,
    resolution_kind: NodeResolutionKind,
    apply_browser_pre: bool,
  ) -> Result<NodeResolution, NodeResolveError> {
    // Note: if we are here, then the referrer is an esm module
    // TODO(bartlomieju): skipped "policy" part as we don't plan to support it

    if apply_browser_pre
      && self.resolution_config.prefer_browser_field
      && let Some(action) = self.lookup_browser_map_pre(specifier, referrer)?
    {
      return self.apply_browser_pre_action(
        specifier,
        referrer,
        resolution_mode,
        resolution_kind,
        action,
      );
    }

    if self.is_builtin_node_module(specifier) {
      return Ok(NodeResolution::BuiltIn(specifier.to_string()));
    }

    if let Ok(url) = Url::parse(specifier) {
      if url.scheme() == "data" {
        return Ok(NodeResolution::Module(UrlOrPath::Url(url)));
      }

      if let Some(module_name) =
        self.get_module_name_from_builtin_node_module_url(&url)?
      {
        return Ok(NodeResolution::BuiltIn(module_name.to_string()));
      }

      let protocol = url.scheme();

      if protocol != "file" && protocol != "data" {
        return Err(
          UnsupportedEsmUrlSchemeError {
            url_scheme: protocol.to_string(),
          }
          .into(),
        );
      }

      // todo(dsherret): this seems wrong
      if referrer.scheme() == "data" {
        let url = referrer
          .join(specifier)
          .map_err(|source| DataUrlReferrerError { source })?;
        return Ok(NodeResolution::Module(UrlOrPath::Url(url)));
      }
    }

    let conditions = self.condition_resolver.resolve(resolution_mode);
    let referrer_ref = UrlOrPathRef::from_url(referrer);
    let (url, resolved_kind) = self.module_resolve(
      specifier,
      &referrer_ref,
      resolution_mode,
      conditions,
      resolution_kind,
    )?;

    let url_or_path = self.finalize_resolution(
      url,
      resolved_kind,
      resolution_mode,
      conditions,
      resolution_kind,
      Some(&referrer_ref),
    )?;

    if self.resolution_config.prefer_browser_field
      && let Some(action) = self.lookup_browser_map_post(&url_or_path)
      && let Some(res) = self.apply_browser_post_action(specifier, action)?
    {
      return Ok(res);
    }

    let resolve_response = NodeResolution::Module(url_or_path);
    // TODO(bartlomieju): skipped checking errors for commonJS resolution and
    // "preserveSymlinksMain"/"preserveSymlinks" options.
    Ok(resolve_response)
  }

  /// Resolve a bare package specifier, skipping the built-in module check
  /// and URL scheme handling. Only suitable for bare specifiers like
  /// "events" or "assert" — not for URLs or relative paths.
  ///
  /// Used when a specifier resolved as a built-in but may be shadowed
  /// by an npm package with the same name.
  pub fn resolve_package(
    &self,
    specifier: &str,
    referrer: &Url,
    resolution_mode: ResolutionMode,
    resolution_kind: NodeResolutionKind,
  ) -> Result<NodeResolution, NodeResolveError> {
    let conditions = self.condition_resolver.resolve(resolution_mode);
    let referrer_ref = UrlOrPathRef::from_url(referrer);
    let (url, resolved_kind) = self.module_resolve(
      specifier,
      &referrer_ref,
      resolution_mode,
      conditions,
      resolution_kind,
    )?;

    let url_or_path = self.finalize_resolution(
      url,
      resolved_kind,
      resolution_mode,
      conditions,
      resolution_kind,
      Some(&referrer_ref),
    )?;

    if self.resolution_config.prefer_browser_field
      && let Some(action) = self.lookup_browser_map_post(&url_or_path)
      && let Some(res) = self.apply_browser_post_action(specifier, action)?
    {
      return Ok(res);
    }
    Ok(NodeResolution::Module(url_or_path))
  }

  /// Pre-resolution `browser` map lookup. Matches bare-specifier keys
  /// ("fs", "foo") against the importer's nearest `package.json`. The
  /// `node:` prefix is stripped before lookup.
  fn lookup_browser_map_pre(
    &self,
    specifier: &str,
    referrer: &Url,
  ) -> Result<Option<BrowserMapMatch>, NodeResolveError> {
    if specifier.starts_with("./")
      || specifier.starts_with("../")
      || specifier.starts_with('/')
    {
      return Ok(None);
    }
    let Ok(referrer_path) = url_to_file_path(referrer) else {
      return Ok(None);
    };
    let probe = if self.sys.is_dir(Cow::Borrowed(&referrer_path)) {
      referrer_path.join("__")
    } else {
      referrer_path
    };
    let pkg_json = match self.pkg_json_resolver.get_closest_package_json(&probe)
    {
      Ok(Some(pkg)) => pkg,
      _ => return Ok(None),
    };
    let Some(map) = pkg_json.browser_map.as_ref() else {
      return Ok(None);
    };
    let lookup_key = specifier.strip_prefix("node:").unwrap_or(specifier);
    let entry = map.get(specifier).or_else(|| map.get(lookup_key));
    Ok(entry.map(|e| BrowserMapMatch {
      entry: e.clone(),
      pkg_json: pkg_json.clone(),
    }))
  }

  /// Post-resolution `browser` map lookup. Matches relative-path keys
  /// ("./bar.js") against the resolved file's nearest `package.json`.
  fn lookup_browser_map_post(
    &self,
    resolved: &UrlOrPath,
  ) -> Option<BrowserMapMatch> {
    let resolved_path = match resolved {
      UrlOrPath::Path(p) => p.clone(),
      UrlOrPath::Url(u) if u.scheme() == "file" => url_to_file_path(u).ok()?,
      _ => return None,
    };
    let resolved_clean =
      deno_path_util::normalize_path(Cow::Borrowed(resolved_path.as_path()));
    let pkg_json = self
      .pkg_json_resolver
      .get_closest_package_json(&resolved_path)
      .ok()
      .flatten()?;
    let map = pkg_json.browser_map.as_ref()?;
    let pkg_dir = pkg_json.path.parent()?;
    for (key, entry) in map {
      if !key.starts_with("./") && !key.starts_with("../") {
        continue;
      }
      let joined = pkg_dir.join(key);
      let key_path =
        deno_path_util::normalize_path(Cow::Borrowed(joined.as_path()));
      // Per the proposal, relative-path keys are matched after node-style
      // probing — `"./foo": false` should disable `./foo.js`, `./foo/index.js`,
      // etc. Compare the resolved file against the key plus the same extension
      // and `/index.*` variants `require()` would try.
      if browser_map_key_matches(&key_path, &resolved_clean) {
        return Some(BrowserMapMatch {
          entry: entry.clone(),
          pkg_json: pkg_json.clone(),
        });
      }
    }
    None
  }

  fn apply_browser_pre_action(
    &self,
    specifier: &str,
    referrer: &Url,
    resolution_mode: ResolutionMode,
    resolution_kind: NodeResolutionKind,
    action: BrowserMapMatch,
  ) -> Result<NodeResolution, NodeResolveError> {
    match action.entry {
      BrowserMapEntry::Disabled => Err(
        BrowserMapDisabledError {
          specifier: specifier.to_string(),
          pkg_json_path: action.pkg_json.path.clone(),
        }
        .into(),
      ),
      BrowserMapEntry::Replace(replacement) => {
        if replacement.starts_with("./") || replacement.starts_with("../") {
          // Relative to the package containing the map.
          let pkg_dir = action
            .pkg_json
            .path
            .parent()
            .unwrap_or_else(|| std::path::Path::new("/"));
          let abs = deno_path_util::normalize_path(Cow::Owned(
            pkg_dir.join(&replacement),
          ));
          Ok(NodeResolution::Module(UrlOrPath::Path(abs.into_owned())))
        } else {
          // Bare module name — resolve normally. Skip pre-check to avoid
          // self-referential loops like `{"foo": "foo"}`.
          self.resolve_internal(
            &replacement,
            referrer,
            resolution_mode,
            resolution_kind,
            false,
          )
        }
      }
    }
  }

  fn apply_browser_post_action(
    &self,
    original_specifier: &str,
    action: BrowserMapMatch,
  ) -> Result<Option<NodeResolution>, NodeResolveError> {
    match action.entry {
      BrowserMapEntry::Disabled => Err(
        BrowserMapDisabledError {
          specifier: original_specifier.to_string(),
          pkg_json_path: action.pkg_json.path.clone(),
        }
        .into(),
      ),
      BrowserMapEntry::Replace(replacement) => {
        // Relative-key replacements only fire post-resolve; the value
        // should likewise be relative to the package directory.
        let pkg_dir = action
          .pkg_json
          .path
          .parent()
          .unwrap_or_else(|| std::path::Path::new("/"));
        let abs = deno_path_util::normalize_path(Cow::Owned(
          pkg_dir.join(&replacement),
        ));
        Ok(Some(NodeResolution::Module(UrlOrPath::Path(
          abs.into_owned(),
        ))))
      }
    }
  }

  fn module_resolve(
    &self,
    specifier: &str,
    referrer: &UrlOrPathRef,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), NodeResolveError> {
    if should_be_treated_as_relative_or_absolute_path(specifier) {
      let referrer_url = referrer.url()?;
      let url = node_join_url(referrer_url, specifier).map_err(|err| {
        NodeResolveRelativeJoinError {
          path: specifier.to_string(),
          base: referrer_url.clone(),
          source: err,
        }
      })?;
      let url = self.maybe_resolve_types(
        LocalUrlOrPath::Url(url),
        Some(referrer),
        resolution_mode,
        conditions,
        resolution_kind,
      )?;
      Ok((url, ResolvedMethod::RelativeOrAbsolute))
    } else if specifier.starts_with('#') {
      let pkg_config = self
        .pkg_json_resolver
        .get_closest_package_json(referrer.path()?)
        .map_err(PackageImportsResolveErrorKind::PkgJsonLoad)
        .map_err(|err| PackageImportsResolveError(Box::new(err)))?;
      Ok((
        self.package_imports_resolve_internal(
          specifier,
          Some(referrer),
          resolution_mode,
          pkg_config.as_deref(),
          conditions,
          resolution_kind,
        )?,
        ResolvedMethod::PackageImports,
      ))
    } else if let Ok(url) = Url::parse(specifier) {
      let url_or_path = self.maybe_resolve_types(
        LocalUrlOrPath::Url(url),
        Some(referrer),
        resolution_mode,
        conditions,
        resolution_kind,
      )?;
      Ok((url_or_path, ResolvedMethod::Url))
    } else {
      Ok(self.package_resolve(
        specifier,
        referrer,
        resolution_mode,
        conditions,
        resolution_kind,
      )?)
    }
  }

  fn finalize_resolution(
    &self,
    resolved: MaybeTypesResolvedUrl,
    resolved_method: ResolvedMethod,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
    maybe_referrer: Option<&UrlOrPathRef>,
  ) -> Result<UrlOrPath, FinalizeResolutionError> {
    let encoded_sep_re = lazy_regex::regex!(r"%2F|%2C");

    let resolved = resolved.0;
    let text = match &resolved {
      LocalUrlOrPath::Url(url) => Cow::Borrowed(url.as_str()),
      LocalUrlOrPath::Path(LocalPath { path, .. }) => path.to_string_lossy(),
    };
    if encoded_sep_re.is_match(&text) {
      return Err(
        errors::InvalidModuleSpecifierError {
          request: text.into_owned(),
          reason: Cow::Borrowed(
            "must not include encoded \"/\" or \"\\\\\" characters",
          ),
          maybe_referrer: maybe_referrer.map(|r| match r.path() {
            // in this case, prefer showing the path string
            Ok(path) => path.display().to_string(),
            Err(_) => r.display().to_string(),
          }),
        }
        .into(),
      );
    }

    let (path, maybe_url) = match resolved {
      LocalUrlOrPath::Url(url) => {
        if url.scheme() == "file" {
          (url_to_file_path(&url)?, Some(url))
        } else {
          return Ok(UrlOrPath::Url(url));
        }
      }
      LocalUrlOrPath::Path(LocalPath { path, known_exists }) => {
        if known_exists {
          // no need to do the finalization checks
          return Ok(UrlOrPath::Path(path));
        } else {
          (path, None)
        }
      }
    };

    // TODO(bartlomieju): currently not supported
    // if (getOptionValue('--experimental-specifier-resolution') === 'node') {
    //   ...
    // }

    let p_str = path.to_str().unwrap();
    let path = match p_str.strip_suffix('/') {
      Some(s) => Cow::Borrowed(Path::new(s)),
      None => Cow::Owned(path),
    };

    let maybe_file_type = self.sys.get_file_type(Cow::Borrowed(&path));
    match maybe_file_type {
      Ok(FileType::Dir) => {
        if resolution_mode == ResolutionMode::Import && !self.bundle_mode() {
          let suggestion = self.directory_import_suggestion(&path);
          Err(
            UnsupportedDirImportError {
              dir_url: UrlOrPath::Path(path.into_owned()),
              maybe_referrer: maybe_referrer.map(|r| r.display()),
              suggestion,
            }
            .into(),
          )
        } else {
          // prefer the file over the directory
          let path_with_ext = with_known_extension(&path, "js");
          if self.sys.is_file(Cow::Borrowed(&path_with_ext)) {
            Ok(UrlOrPath::Path(path_with_ext))
          } else {
            let (resolved_url, resolved_method) = self
              .resolve_package_dir_subpath(
                &path,
                ".",
                maybe_referrer,
                resolution_mode,
                conditions,
                resolution_kind,
              )?;
            self.finalize_resolution(
              resolved_url,
              resolved_method,
              resolution_mode,
              conditions,
              resolution_kind,
              maybe_referrer,
            )
          }
        }
      }
      Ok(FileType::File) => {
        // prefer returning the url to avoid re-allocating in the CLI crate
        Ok(
          maybe_url
            .map(UrlOrPath::Url)
            .unwrap_or(UrlOrPath::Path(path.into_owned())),
        )
      }
      _ => {
        if let Err(e) = maybe_file_type
          && (resolution_mode == ResolutionMode::Require || self.bundle_mode())
          && e.kind() == std::io::ErrorKind::NotFound
        {
          // Match Node's `require()` resolution order: try .js, then .node
          // for native addons (`require('./build/Release/foo')`).
          for ext in ["js", "node"] {
            let file_with_ext = with_known_extension(&path, ext);
            if self.sys.is_file(Cow::Borrowed(&file_with_ext)) {
              return Ok(UrlOrPath::Path(file_with_ext));
            }
          }
        }

        Err(
          ModuleNotFoundError {
            suggested_ext: self
              .module_not_found_ext_suggestion(&path, resolved_method),
            specifier: UrlOrPath::Path(path.into_owned()),
            maybe_referrer: maybe_referrer.map(|r| r.display()),
          }
          .into(),
        )
      }
    }
  }

  fn module_not_found_ext_suggestion(
    &self,
    path: &Path,
    resolved_method: ResolvedMethod,
  ) -> Option<&'static str> {
    fn should_probe(path: &Path, resolved_method: ResolvedMethod) -> bool {
      if MediaType::from_path(path) != MediaType::Unknown {
        return false;
      }
      match resolved_method {
        ResolvedMethod::Url
        | ResolvedMethod::RelativeOrAbsolute
        | ResolvedMethod::PackageSubPath => true,
        ResolvedMethod::PackageImports | ResolvedMethod::PackageExports => {
          false
        }
      }
    }

    if should_probe(path, resolved_method) {
      ["js", "mjs", "cjs"].into_iter().find(|ext| {
        self
          .sys
          .is_file(Cow::Owned(with_known_extension(path, ext)))
      })
    } else {
      None
    }
  }

  fn directory_import_suggestion(
    &self,
    dir_import_path: &Path,
  ) -> Option<String> {
    let dir_index_paths = ["index.mjs", "index.js", "index.cjs"]
      .into_iter()
      .map(|file_name| dir_import_path.join(file_name));
    let file_paths = [
      with_known_extension(dir_import_path, "js"),
      with_known_extension(dir_import_path, "mjs"),
      with_known_extension(dir_import_path, "cjs"),
    ];
    dir_index_paths
      .chain(file_paths)
      .chain(
        std::iter::once_with(|| {
          // check if this directory has a package.json
          let package_json_path = dir_import_path.join("package.json");
          let pkg_json = self
            .pkg_json_resolver
            .load_package_json(&package_json_path)
            .ok()??;
          let main = pkg_json.main.as_ref()?;
          Some(dir_import_path.join(main))
        })
        .flatten(),
      )
      .map(|p| deno_path_util::normalize_path(Cow::Owned(p)))
      .find(|p| self.sys.is_file(Cow::Borrowed(p.as_ref())))
      .and_then(|suggested_file_path| {
        let pkg_json = self
          .pkg_json_resolver
          .get_closest_package_jsons(&suggested_file_path)
          .filter_map(|pkg_json| pkg_json.ok())
          .find(|p| p.name.is_some())?;
        let pkg_name = pkg_json.name.as_ref()?;
        let sub_path = suggested_file_path
          .strip_prefix(pkg_json.dir_path())
          .ok()?
          .to_string_lossy()
          .replace("\\", "/");
        Some(format!("{}/{}", pkg_name, sub_path))
      })
  }

  pub fn resolve_package_subpath_from_deno_module(
    &self,
    package_dir: &Path,
    package_subpath: Option<&str>,
    maybe_referrer: Option<&Url>,
    resolution_mode: ResolutionMode,
    resolution_kind: NodeResolutionKind,
  ) -> Result<UrlOrPath, PackageSubpathFromDenoModuleResolveError> {
    // todo(dsherret): don't allocate a string here (maybe use an
    // enum that says the subpath is not prefixed with a ./)
    let package_subpath = package_subpath
      .map(|s| Cow::Owned(format!("./{}", decode_package_subpath(s))))
      .unwrap_or_else(|| Cow::Borrowed("."));
    let maybe_referrer = maybe_referrer.map(UrlOrPathRef::from_url);
    let conditions = self.condition_resolver.resolve(resolution_mode);
    let (resolved_url, resolved_method) = self.resolve_package_dir_subpath(
      package_dir,
      &package_subpath,
      maybe_referrer.as_ref(),
      resolution_mode,
      conditions,
      resolution_kind,
    )?;
    let url_or_path = self.finalize_resolution(
      resolved_url,
      resolved_method,
      resolution_mode,
      conditions,
      resolution_kind,
      maybe_referrer.as_ref(),
    )?;
    if self.resolution_config.prefer_browser_field
      && let Some(action) = self.lookup_browser_map_post(&url_or_path)
    {
      let pkg_json_path = action.pkg_json.path.clone();
      match action.entry {
        BrowserMapEntry::Disabled => {
          return Err(
            BrowserMapDisabledError {
              specifier: package_subpath.into_owned(),
              pkg_json_path,
            }
            .into(),
          );
        }
        BrowserMapEntry::Replace(replacement) => {
          let pkg_dir = pkg_json_path
            .parent()
            .unwrap_or_else(|| std::path::Path::new("/"));
          let abs = deno_path_util::normalize_path(Cow::Owned(
            pkg_dir.join(&replacement),
          ));
          return Ok(UrlOrPath::Path(abs.into_owned()));
        }
      }
    }
    Ok(url_or_path)
  }

  pub fn resolve_binary_export(
    &self,
    package_folder: &Path,
    sub_path: Option<&str>,
  ) -> Result<BinValue, ResolvePkgJsonBinExportError> {
    let (pkg_json, items) = self
      .resolve_npm_binary_commands_for_package_with_pkg_json(package_folder)?;
    let path =
      resolve_bin_entry_value(&pkg_json, &items, sub_path).map_err(|err| {
        ResolvePkgJsonBinExportError::InvalidBinProperty {
          message: err.to_string(),
        }
      })?;
    Ok(path.clone())
  }

  pub fn resolve_npm_binary_commands_for_package(
    &self,
    package_folder: &Path,
  ) -> Result<BTreeMap<String, BinValue>, ResolvePkgNpmBinaryCommandsError> {
    let (_pkg_json, items) = self
      .resolve_npm_binary_commands_for_package_with_pkg_json(package_folder)?;
    Ok(items)
  }

  fn resolve_npm_binary_commands_for_package_with_pkg_json(
    &self,
    package_folder: &Path,
  ) -> Result<
    (PackageJsonRc, BTreeMap<String, BinValue>),
    ResolvePkgNpmBinaryCommandsError,
  > {
    let pkg_json_path = package_folder.join("package.json");
    let Some(package_json) =
      self.pkg_json_resolver.load_package_json(&pkg_json_path)?
    else {
      return Err(ResolvePkgNpmBinaryCommandsError::MissingPkgJson(
        MissingPkgJsonError { pkg_json_path },
      ));
    };
    let bins = package_json.resolve_bins()?;
    // TODO(bartlomieju): skipped checking errors for commonJS resolution and
    // "preserveSymlinksMain"/"preserveSymlinks" options.
    let items = match bins {
      deno_package_json::PackageJsonBins::Directory(path_buf) => {
        self.resolve_npm_commands_from_bin_dir(&path_buf)
      }
      deno_package_json::PackageJsonBins::Bins(items) => items
        .into_iter()
        .filter_map(|(command, path)| {
          let bin_value = read_bin_value(&path, &self.sys)?;
          Some((command, bin_value))
        })
        .collect(),
    };
    Ok((package_json, items))
  }

  pub fn resolve_npm_commands_from_bin_dir(
    &self,
    bin_dir: &Path,
  ) -> BTreeMap<String, BinValue> {
    log::debug!("Resolving npm commands in '{}'.", bin_dir.display());
    let mut result = BTreeMap::new();
    match self.sys.fs_read_dir(bin_dir) {
      Ok(entries) => {
        for entry in entries {
          let Ok(entry) = entry else {
            continue;
          };
          if let Some((command, bin_value)) =
            self.resolve_bin_dir_entry_command(entry)
          {
            result.insert(command, bin_value);
          }
        }
      }
      Err(err) => {
        log::debug!("Failed read_dir for '{}': {:#}", bin_dir.display(), err);
      }
    }
    result
  }

  fn resolve_bin_dir_entry_command(
    &self,
    entry: TSys::ReadDirEntry,
  ) -> Option<(String, BinValue)> {
    if entry.path().extension().is_some() {
      return None; // only look at files without extensions (even on Windows)
    }
    let file_type = entry.file_type().ok()?;
    let path = if file_type.is_file() {
      entry.path()
    } else if file_type.is_symlink() {
      Cow::Owned(self.sys.fs_canonicalize(entry.path()).ok()?)
    } else {
      return None;
    };
    let command_name = entry.file_name().to_string_lossy().into_owned();
    let bin_value = read_bin_value(&path, &self.sys)?;
    Some((command_name, bin_value))
  }

  /// Resolves an npm package folder path from the specified referrer.
  pub fn resolve_package_folder_from_package(
    &self,
    package_name: &str,
    referrer: &UrlOrPathRef,
  ) -> Result<PathBuf, errors::PackageFolderResolveError> {
    self
      .npm_pkg_folder_resolver
      .resolve_package_folder_from_package(package_name, referrer)
  }

  fn maybe_resolve_types(
    &self,
    url: LocalUrlOrPath,
    maybe_referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<MaybeTypesResolvedUrl, TypesNotFoundError> {
    if resolution_kind.is_types() {
      let file_path = match url {
        LocalUrlOrPath::Url(url) => {
          match deno_path_util::url_to_file_path(&url) {
            Ok(path) => LocalPath {
              path,
              known_exists: false,
            },
            Err(_) => {
              return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Url(url)));
            }
          }
        }
        LocalUrlOrPath::Path(path) => path,
      };
      self.path_to_declaration_path(
        file_path,
        maybe_referrer,
        resolution_mode,
        conditions,
      )
    } else {
      Ok(MaybeTypesResolvedUrl(url))
    }
  }

  /// Checks if the resolved file has a corresponding declaration file.
  fn path_to_declaration_path(
    &self,
    local_path: LocalPath,
    maybe_referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
  ) -> Result<MaybeTypesResolvedUrl, TypesNotFoundError> {
    fn probe_extensions<TSys: FsMetadata>(
      sys: &NodeResolutionSys<TSys>,
      path: &Path,
      media_type: MediaType,
      resolution_mode: ResolutionMode,
    ) -> Option<PathBuf> {
      let mut searched_for_d_mts = false;
      let mut searched_for_d_cts = false;
      if media_type == MediaType::Mjs {
        let d_mts_path = with_known_extension(path, "d.mts");
        if sys.exists_(Cow::Borrowed(&d_mts_path)) {
          return Some(d_mts_path);
        }
        searched_for_d_mts = true;
      } else if media_type == MediaType::Cjs {
        let d_cts_path = with_known_extension(path, "d.cts");
        if sys.exists_(Cow::Borrowed(&d_cts_path)) {
          return Some(d_cts_path);
        }
        searched_for_d_cts = true;
      }

      let dts_path = with_known_extension(path, "d.ts");
      if sys.exists_(Cow::Borrowed(&dts_path)) {
        return Some(dts_path);
      }

      let specific_dts_path = match resolution_mode {
        ResolutionMode::Require if !searched_for_d_cts => {
          Some(with_known_extension(path, "d.cts"))
        }
        ResolutionMode::Import if !searched_for_d_mts => {
          Some(with_known_extension(path, "d.mts"))
        }
        _ => None, // already searched above
      };
      if let Some(specific_dts_path) = specific_dts_path
        && sys.exists_(Cow::Borrowed(&specific_dts_path))
      {
        return Some(specific_dts_path);
      }
      let ts_path = with_known_extension(path, "ts");
      if sys.is_file(Cow::Borrowed(&ts_path)) {
        return Some(ts_path);
      }
      None
    }

    let media_type = MediaType::from_path(&local_path.path);
    if media_type.is_declaration() {
      return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(local_path)));
    }
    if let Some(path) =
      probe_extensions(&self.sys, &local_path.path, media_type, resolution_mode)
    {
      return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
        path,
        known_exists: true,
      })));
    }
    if self.sys.is_dir(Cow::Borrowed(&local_path.path)) {
      let resolution_result = self.resolve_package_dir_subpath(
        &local_path.path,
        /* sub path */ ".",
        maybe_referrer,
        resolution_mode,
        conditions,
        NodeResolutionKind::Types,
      );
      if let Ok((url_or_path, _)) = resolution_result {
        return Ok(url_or_path);
      }
      let index_path = local_path.path.join("index.js");
      if let Some(path) = probe_extensions(
        &self.sys,
        &index_path,
        MediaType::from_path(&index_path),
        resolution_mode,
      ) {
        return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
          path,
          known_exists: true,
        })));
      }
    }
    // allow resolving .ts-like or .css files for types resolution
    if media_type.is_typed() || media_type == MediaType::Css {
      return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(local_path)));
    }
    Err(TypesNotFoundError(Box::new(TypesNotFoundErrorData {
      code_specifier: UrlOrPathRef::from_path(&local_path.path).display(),
      maybe_referrer: maybe_referrer.map(|r| r.display()),
    })))
  }

  #[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
  pub fn resolve_package_import(
    &self,
    name: &str,
    maybe_referrer: Option<&UrlOrPathRef>,
    referrer_pkg_json: Option<&PackageJson>,
    resolution_mode: ResolutionMode,
    resolution_kind: NodeResolutionKind,
  ) -> Result<UrlOrPath, PackageImportsResolveError> {
    self
      .package_imports_resolve_internal(
        name,
        maybe_referrer,
        resolution_mode,
        referrer_pkg_json,
        self.condition_resolver.resolve(resolution_mode),
        resolution_kind,
      )
      .map(|url| url.0.into_url_or_path())
  }

  #[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
  fn package_imports_resolve_internal(
    &self,
    name: &str,
    maybe_referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    referrer_pkg_json: Option<&PackageJson>,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<MaybeTypesResolvedUrl, PackageImportsResolveError> {
    if name == "#" || name.ends_with('/') {
      let reason = "is not a valid internal imports specifier name";
      return Err(
        errors::InvalidModuleSpecifierError {
          request: name.to_string(),
          reason: Cow::Borrowed(reason),
          maybe_referrer: maybe_referrer.map(to_specifier_display_string),
        }
        .into(),
      );
    }

    if let Some(pkg_json) = &referrer_pkg_json
      && let Some(resolved_import) = resolve_pkg_json_import(pkg_json, name)
    {
      let maybe_resolved = self.resolve_package_target(
        &pkg_json.path,
        resolved_import.target,
        resolved_import.sub_path,
        resolved_import.package_sub_path,
        maybe_referrer,
        resolution_mode,
        resolved_import.is_pattern,
        true,
        conditions,
        resolution_kind,
      )?;
      if let Some(resolved) = maybe_resolved {
        return Ok(resolved);
      }
    }

    Err(
      PackageImportNotDefinedError {
        name: name.to_string(),
        package_json_path: referrer_pkg_json.map(|p| p.path.clone()),
        maybe_referrer: maybe_referrer.map(|r| r.display()),
      }
      .into(),
    )
  }

  #[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
  fn resolve_package_target_string(
    &self,
    target: &str,
    subpath: &str,
    match_: &str,
    package_json_path: &Path,
    maybe_referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    pattern: bool,
    internal: bool,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<MaybeTypesResolvedUrl, PackageTargetResolveError> {
    if !subpath.is_empty() && !pattern && !target.ends_with('/') {
      return Err(
        InvalidPackageTargetError {
          pkg_json_path: package_json_path.to_path_buf(),
          sub_path: match_.to_string(),
          target: target.to_string(),
          is_import: internal,
          maybe_referrer: maybe_referrer.map(|r| r.display()),
        }
        .into(),
      );
    }
    let invalid_segment_re =
      lazy_regex::regex!(r"(^|\\|/)(\.\.?|node_modules)(\\|/|$)");
    let pattern_re = lazy_regex::regex!(r"\*");
    if !target.starts_with("./") {
      if internal && !target.starts_with("../") && !target.starts_with('/') {
        let target_url = Url::parse(target);
        match target_url {
          Ok(url) => {
            if self
              .get_module_name_from_builtin_node_module_url(&url)?
              .is_some()
            {
              return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Url(url)));
            }
          }
          Err(_) => {
            let export_target = if pattern {
              pattern_re.replace(target, |_caps: &regex::Captures| subpath)
            } else if subpath.is_empty() {
              Cow::Borrowed(target)
            } else {
              Cow::Owned(format!("{target}{subpath}"))
            };
            let result = match self.package_resolve(
              &export_target,
              &UrlOrPathRef::from_path(package_json_path),
              resolution_mode,
              conditions,
              resolution_kind,
            ) {
              Ok((url, _)) => Ok(url),
              Err(err) => match err.code() {
                NodeJsErrorCode::ERR_INVALID_FILE_URL_PATH
                | NodeJsErrorCode::ERR_INVALID_MODULE_SPECIFIER
                | NodeJsErrorCode::ERR_INVALID_PACKAGE_CONFIG
                | NodeJsErrorCode::ERR_INVALID_PACKAGE_TARGET
                | NodeJsErrorCode::ERR_PACKAGE_IMPORT_NOT_DEFINED
                | NodeJsErrorCode::ERR_PACKAGE_PATH_NOT_EXPORTED
                | NodeJsErrorCode::ERR_UNKNOWN_FILE_EXTENSION
                | NodeJsErrorCode::ERR_UNSUPPORTED_DIR_IMPORT
                | NodeJsErrorCode::ERR_UNSUPPORTED_ESM_URL_SCHEME
                | NodeJsErrorCode::ERR_UNKNOWN_BUILTIN_MODULE
                | NodeJsErrorCode::ERR_TYPES_NOT_FOUND => {
                  Err(PackageTargetResolveErrorKind::PackageResolve(err).into())
                }
                NodeJsErrorCode::ERR_MODULE_NOT_FOUND => Err(
                  PackageTargetResolveErrorKind::NotFound(
                    PackageTargetNotFoundError {
                      pkg_json_path: package_json_path.to_path_buf(),
                      target: export_target.to_string(),
                      maybe_resolved: err
                        .maybe_specifier()
                        .map(|c| c.into_owned()),
                      maybe_referrer: maybe_referrer.map(|r| r.display()),
                      resolution_mode,
                      resolution_kind,
                    },
                  )
                  .into(),
                ),
              },
            };

            return match result {
              Ok(url) => Ok(url),
              Err(err) => {
                if self
                  .is_built_in_node_module_checker
                  .is_builtin_node_module(target)
                {
                  Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Url(
                    Url::parse(&format!("node:{}", target)).unwrap(),
                  )))
                } else {
                  Err(err)
                }
              }
            };
          }
        }
      }
      return Err(
        InvalidPackageTargetError {
          pkg_json_path: package_json_path.to_path_buf(),
          sub_path: match_.to_string(),
          target: target.to_string(),
          is_import: internal,
          maybe_referrer: maybe_referrer.map(|r| r.display()),
        }
        .into(),
      );
    }
    if invalid_segment_re.is_match(&target[2..]) {
      return Err(
        InvalidPackageTargetError {
          pkg_json_path: package_json_path.to_path_buf(),
          sub_path: match_.to_string(),
          target: target.to_string(),
          is_import: internal,
          maybe_referrer: maybe_referrer.map(|r| r.display()),
        }
        .into(),
      );
    }
    let package_path = package_json_path.parent().unwrap();
    let resolved_path = package_path.join(target).clean();
    if !resolved_path.starts_with(package_path) {
      return Err(
        InvalidPackageTargetError {
          pkg_json_path: package_json_path.to_path_buf(),
          sub_path: match_.to_string(),
          target: target.to_string(),
          is_import: internal,
          maybe_referrer: maybe_referrer.map(|r| r.display()),
        }
        .into(),
      );
    }
    let path = if subpath.is_empty() {
      LocalPath {
        path: resolved_path,
        known_exists: false,
      }
    } else if invalid_segment_re.is_match(subpath) {
      let request = if pattern {
        match_.replace('*', subpath)
      } else {
        format!("{match_}{subpath}")
      };
      return Err(
        throw_invalid_subpath(
          request,
          package_json_path,
          internal,
          maybe_referrer,
        )
        .into(),
      );
    } else if pattern {
      let resolved_path_str = resolved_path.to_string_lossy();
      let replaced = pattern_re
        .replace(&resolved_path_str, |_caps: &regex::Captures| subpath);
      LocalPath {
        path: PathBuf::from(replaced.as_ref()),
        known_exists: false,
      }
    } else {
      LocalPath {
        path: resolved_path.join(subpath).clean(),
        known_exists: false,
      }
    };
    Ok(self.maybe_resolve_types(
      LocalUrlOrPath::Path(path),
      maybe_referrer,
      resolution_mode,
      conditions,
      resolution_kind,
    )?)
  }

  #[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
  fn resolve_package_target(
    &self,
    package_json_path: &Path,
    target: &Value,
    subpath: &str,
    package_subpath: &str,
    maybe_referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    pattern: bool,
    internal: bool,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<Option<MaybeTypesResolvedUrl>, PackageTargetResolveError> {
    let result = self.resolve_package_target_inner(
      package_json_path,
      target,
      subpath,
      package_subpath,
      maybe_referrer,
      resolution_mode,
      pattern,
      internal,
      conditions,
      resolution_kind,
    );
    match result {
      Ok(maybe_resolved) => Ok(maybe_resolved),
      Err(err) => {
        if resolution_kind.is_types()
          && err.code() == NodeJsErrorCode::ERR_TYPES_NOT_FOUND
          && conditions != TYPES_ONLY_CONDITIONS
        {
          // try resolving with just "types" conditions for when someone misconfigures
          // and puts the "types" condition in the wrong place
          if let Ok(Some(resolved)) = self.resolve_package_target_inner(
            package_json_path,
            target,
            subpath,
            package_subpath,
            maybe_referrer,
            resolution_mode,
            pattern,
            internal,
            TYPES_ONLY_CONDITIONS,
            resolution_kind,
          ) {
            return Ok(Some(resolved));
          }
        }

        Err(err)
      }
    }
  }

  #[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
  fn resolve_package_target_inner(
    &self,
    package_json_path: &Path,
    target: &Value,
    subpath: &str,
    package_subpath: &str,
    maybe_referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    pattern: bool,
    internal: bool,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<Option<MaybeTypesResolvedUrl>, PackageTargetResolveError> {
    if let Some(target) = target.as_str() {
      let url_or_path = self.resolve_package_target_string(
        target,
        subpath,
        package_subpath,
        package_json_path,
        maybe_referrer,
        resolution_mode,
        pattern,
        internal,
        conditions,
        resolution_kind,
      )?;
      return Ok(Some(url_or_path));
    } else if let Some(target_arr) = target.as_array() {
      if target_arr.is_empty() {
        return Ok(None);
      }

      let mut last_error = None;
      for target_item in target_arr {
        let resolved_result = self.resolve_package_target(
          package_json_path,
          target_item,
          subpath,
          package_subpath,
          maybe_referrer,
          resolution_mode,
          pattern,
          internal,
          conditions,
          resolution_kind,
        );

        match resolved_result {
          Ok(Some(resolved)) => return Ok(Some(resolved)),
          Ok(None) => {
            last_error = None;
            continue;
          }
          Err(e) => {
            if e.code() == NodeJsErrorCode::ERR_INVALID_PACKAGE_TARGET {
              last_error = Some(e);
              continue;
            } else {
              return Err(e);
            }
          }
        }
      }
      if last_error.is_none() {
        return Ok(None);
      }
      return Err(last_error.unwrap());
    } else if let Some(target_obj) = target.as_object() {
      for (key, condition_target) in target_obj {
        // TODO(bartlomieju): verify that keys are not numeric
        // return Err(errors::err_invalid_package_config(
        //   to_file_path_string(package_json_url),
        //   Some(base.as_str().to_string()),
        //   Some("\"exports\" cannot contain numeric property keys.".to_string()),
        // ));

        if key == "default"
          || conditions.contains(&Cow::Borrowed(key))
          || resolution_kind.is_types() && self.matches_types_key(key)
        {
          let resolved = self.resolve_package_target(
            package_json_path,
            condition_target,
            subpath,
            package_subpath,
            maybe_referrer,
            resolution_mode,
            pattern,
            internal,
            conditions,
            resolution_kind,
          )?;
          match resolved {
            Some(resolved) => return Ok(Some(resolved)),
            None => {
              continue;
            }
          }
        }
      }
    }

    Ok(None)
  }

  fn matches_types_key(&self, key: &str) -> bool {
    if key == "types" {
      return true;
    }
    let Some(ts_version) = &self.resolution_config.typescript_version else {
      return false;
    };
    let Some(constraint) = key.strip_prefix("types@") else {
      return false;
    };
    let Ok(version_req) = VersionReq::parse_from_npm(constraint) else {
      return false;
    };
    version_req.matches(ts_version)
  }

  #[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
  pub fn package_exports_resolve(
    &self,
    package_json_path: &Path,
    package_subpath: &str,
    package_exports: &Map<String, Value>,
    maybe_referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<UrlOrPath, PackageExportsResolveError> {
    self
      .package_exports_resolve_internal(
        package_json_path,
        package_subpath,
        package_exports,
        maybe_referrer,
        resolution_mode,
        conditions,
        resolution_kind,
      )
      .map(|url| url.0.into_url_or_path())
  }

  #[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
  fn package_exports_resolve_internal(
    &self,
    package_json_path: &Path,
    package_subpath: &str,
    package_exports: &Map<String, Value>,
    maybe_referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<MaybeTypesResolvedUrl, PackageExportsResolveError> {
    if let Some(target) = package_exports.get(package_subpath)
      && package_subpath.find('*').is_none()
      && !package_subpath.ends_with('/')
    {
      let resolved = self.resolve_package_target(
        package_json_path,
        target,
        "",
        package_subpath,
        maybe_referrer,
        resolution_mode,
        false,
        false,
        conditions,
        resolution_kind,
      )?;
      return match resolved {
        Some(resolved) => Ok(resolved),
        None => Err(
          PackagePathNotExportedError {
            pkg_json_path: package_json_path.to_path_buf(),
            subpath: package_subpath.to_string(),
            maybe_referrer: maybe_referrer.map(|r| r.display()),
            resolution_kind,
          }
          .into(),
        ),
      };
    }

    let mut best_match = "";
    let mut best_match_data = None;
    for (key, target) in package_exports {
      let Some(pattern_index) = key.find('*') else {
        continue;
      };
      let key_sub = &key[0..pattern_index];
      if !package_subpath.starts_with(key_sub) {
        continue;
      }

      // When this reaches EOL, this can throw at the top of the whole function:
      //
      // if (StringPrototypeEndsWith(packageSubpath, '/'))
      //   throwInvalidSubpath(packageSubpath)
      //
      // To match "imports" and the spec.
      if package_subpath.ends_with('/') {
        // TODO(bartlomieju):
        // emitTrailingSlashPatternDeprecation();
      }
      let pattern_trailer = &key[pattern_index + 1..];
      if package_subpath.len() >= key.len()
        && package_subpath.ends_with(&pattern_trailer)
        && pattern_key_compare(best_match, key) == 1
        && key.rfind('*') == Some(pattern_index)
      {
        best_match = key;
        best_match_data = Some((
          target,
          &package_subpath
            [pattern_index..(package_subpath.len() - pattern_trailer.len())],
        ));
      }
    }

    if let Some((target, subpath)) = best_match_data {
      let maybe_resolved = self.resolve_package_target(
        package_json_path,
        target,
        subpath,
        best_match,
        maybe_referrer,
        resolution_mode,
        true,
        false,
        conditions,
        resolution_kind,
      )?;
      if let Some(resolved) = maybe_resolved {
        return Ok(resolved);
      } else {
        return Err(
          PackagePathNotExportedError {
            pkg_json_path: package_json_path.to_path_buf(),
            subpath: package_subpath.to_string(),
            maybe_referrer: maybe_referrer.map(|r| r.display()),
            resolution_kind,
          }
          .into(),
        );
      }
    }

    Err(
      PackagePathNotExportedError {
        pkg_json_path: package_json_path.to_path_buf(),
        subpath: package_subpath.to_string(),
        maybe_referrer: maybe_referrer.map(|r| r.display()),
        resolution_kind,
      }
      .into(),
    )
  }

  fn package_resolve(
    &self,
    specifier: &str,
    referrer: &UrlOrPathRef,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageResolveError> {
    let (package_name, package_subpath, _is_scoped) =
      parse_npm_pkg_name(specifier, referrer)?;

    if let Some(package_config) = self
      .pkg_json_resolver
      .get_closest_package_json(referrer.path()?)?
    {
      // ResolveSelf
      if package_config.name.as_deref() == Some(package_name)
        && let Some(exports) = &package_config.exports
      {
        return self
          .package_exports_resolve_internal(
            &package_config.path,
            &package_subpath,
            exports,
            Some(referrer),
            resolution_mode,
            conditions,
            resolution_kind,
          )
          .map(|url| (url, ResolvedMethod::PackageExports))
          .map_err(|err| err.into());
      }
    }

    self.resolve_package_subpath_for_package(
      package_name,
      &package_subpath,
      referrer,
      resolution_mode,
      conditions,
      resolution_kind,
    )
  }

  #[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
  fn resolve_package_subpath_for_package(
    &self,
    package_name: &str,
    package_subpath: &str,
    referrer: &UrlOrPathRef,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageResolveError> {
    let resolve = |package_dir: &Path| {
      self.resolve_package_dir_subpath(
        package_dir,
        package_subpath,
        Some(referrer),
        resolution_mode,
        conditions,
        resolution_kind,
      )
    };
    let result: Result<_, PackageResolveError> = self
      .npm_pkg_folder_resolver
      .resolve_package_folder_from_package(package_name, referrer)
      .map_err(|err| err.into())
      .and_then(|package_dir| resolve(&package_dir).map_err(|e| e.into()));
    if resolution_kind.is_types() && result.is_err() {
      // try to resolve with the @types package based on the package name
      let maybe_types_package_dir = self
        .resolve_types_package_folder_with_name_and_version(
          package_name,
          None,
          Some(referrer),
        );
      if let Some(types_package_dir) = maybe_types_package_dir
        && let Ok(result) = resolve(&types_package_dir)
      {
        return Ok(result);
      }
    }
    result
  }

  #[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
  fn resolve_package_dir_subpath(
    &self,
    package_dir_path: &Path,
    package_subpath: &str,
    maybe_referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageSubpathResolveError>
  {
    let package_json_path = package_dir_path.join("package.json");
    match self
      .pkg_json_resolver
      .load_package_json(&package_json_path)?
    {
      Some(pkg_json) => {
        let result = self.resolve_package_subpath(
          &pkg_json,
          package_subpath,
          maybe_referrer,
          resolution_mode,
          conditions,
          resolution_kind,
        );
        if resolution_kind.is_types()
          && result.is_err()
          && let Some(types_pkg_dir) = self
            .resolve_types_package_folder_from_package_json(
              &pkg_json,
              maybe_referrer,
            )
          && let Ok(result) = self.resolve_package_dir_subpath(
            &types_pkg_dir,
            package_subpath,
            maybe_referrer,
            resolution_mode,
            conditions,
            resolution_kind,
          )
        {
          Ok(result)
        } else {
          result
        }
      }
      None => self
        .resolve_package_subpath_no_pkg_json(
          package_dir_path,
          package_subpath,
          maybe_referrer,
          resolution_mode,
          conditions,
          resolution_kind,
        )
        .map(|url| (url, ResolvedMethod::PackageSubPath))
        .map_err(|err| {
          PackageSubpathResolveErrorKind::LegacyResolve(err).into()
        }),
    }
  }

  #[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
  fn resolve_package_subpath(
    &self,
    package_json: &PackageJson,
    package_subpath: &str,
    referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<(MaybeTypesResolvedUrl, ResolvedMethod), PackageSubpathResolveError>
  {
    if let Some(exports) = &package_json.exports {
      let result = self.package_exports_resolve_internal(
        &package_json.path,
        package_subpath,
        exports,
        referrer,
        resolution_mode,
        conditions,
        resolution_kind,
      );
      match result {
        Ok(found) => return Ok((found, ResolvedMethod::PackageExports)),
        Err(exports_err) => {
          if resolution_kind.is_types() && package_subpath == "." {
            return self
              .legacy_main_resolve(
                package_json,
                referrer,
                resolution_mode,
                conditions,
                resolution_kind,
              )
              .map(|url| (url, ResolvedMethod::PackageSubPath))
              .map_err(|err| {
                PackageSubpathResolveErrorKind::LegacyResolve(err).into()
              });
          }
          return Err(
            PackageSubpathResolveErrorKind::Exports(exports_err).into(),
          );
        }
      }
    }

    if package_subpath == "." {
      self
        .legacy_main_resolve(
          package_json,
          referrer,
          resolution_mode,
          conditions,
          resolution_kind,
        )
        .map(|url| (url, ResolvedMethod::PackageSubPath))
        .map_err(|err| {
          PackageSubpathResolveErrorKind::LegacyResolve(err).into_box()
        })
    } else {
      self
        .resolve_subpath_exact(
          package_json.path.parent().unwrap(),
          package_subpath,
          Some(package_json),
          referrer,
          resolution_mode,
          conditions,
          resolution_kind,
        )
        .map(|url| (url, ResolvedMethod::PackageSubPath))
        .map_err(|err| {
          PackageSubpathResolveErrorKind::LegacyResolve(err.into()).into_box()
        })
    }
  }

  fn pkg_json_types_versions<'a>(
    &'a self,
    pkg_json: &'a PackageJson,
    resolution_kind: NodeResolutionKind,
  ) -> Option<TypesVersions<'a, TSys>> {
    if !resolution_kind.is_types() {
      return None;
    }
    pkg_json
      .types_versions
      .as_ref()
      .and_then(|entries| {
        let ts_version = self.resolution_config.typescript_version.as_ref()?;
        entries
          .iter()
          .filter_map(|(k, v)| {
            let version_req = VersionReq::parse_from_npm(k).ok()?;
            version_req.matches(ts_version).then_some(v)
          })
          .next()
      })
      .and_then(|value| value.as_object())
      .map(|value| TypesVersions {
        value,
        dir_path: pkg_json.dir_path(),
        sys: &self.sys,
      })
  }

  #[allow(clippy::too_many_arguments, reason = "all arguments are needed")]
  fn resolve_subpath_exact(
    &self,
    directory: &Path,
    package_subpath: &str,
    package_json: Option<&PackageJson>,
    referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<MaybeTypesResolvedUrl, TypesNotFoundError> {
    assert_ne!(package_subpath, ".");
    let types_versions = package_json.and_then(|pkg_json| {
      self.pkg_json_types_versions(pkg_json, resolution_kind)
    });
    let package_subpath = types_versions
      .and_then(|v| v.map(package_subpath))
      .unwrap_or(Cow::Borrowed(package_subpath));
    let file_path = directory.join(package_subpath.as_ref());
    self.maybe_resolve_types(
      LocalUrlOrPath::Path(LocalPath {
        path: file_path,
        known_exists: false,
      }),
      referrer,
      resolution_mode,
      conditions,
      resolution_kind,
    )
  }

  fn resolve_package_subpath_no_pkg_json(
    &self,
    directory: &Path,
    package_subpath: &str,
    maybe_referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
    if package_subpath == "." {
      self.legacy_index_resolve(
        directory,
        maybe_referrer,
        resolution_mode,
        resolution_kind,
      )
    } else {
      self
        .resolve_subpath_exact(
          directory,
          package_subpath,
          None,
          maybe_referrer,
          resolution_mode,
          conditions,
          resolution_kind,
        )
        .map_err(|err| err.into())
    }
  }

  pub(crate) fn legacy_fallback_resolve<'a>(
    &self,
    package_json: &'a PackageJson,
  ) -> Option<&'a str> {
    fn filter_empty(value: Option<&str>) -> Option<&str> {
      value.map(|v| v.trim()).filter(|v| !v.is_empty())
    }
    if self.bundle_mode() {
      let maybe_browser = if self.resolution_config.prefer_browser_field {
        filter_empty(package_json.browser.as_deref())
      } else {
        None
      };
      maybe_browser
        .or(filter_empty(package_json.module.as_deref()))
        .or(filter_empty(package_json.main.as_deref()))
    } else {
      filter_empty(package_json.main.as_deref())
    }
  }

  fn legacy_main_resolve(
    &self,
    package_json: &PackageJson,
    maybe_referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    conditions: &[Cow<'static, str>],
    resolution_kind: NodeResolutionKind,
  ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
    let maybe_main = if resolution_kind.is_types() {
      match package_json.types.as_ref() {
        Some(types) => {
          let types_versions =
            self.pkg_json_types_versions(package_json, resolution_kind);
          Some(
            types_versions
              .and_then(|v| v.map(types.as_ref()))
              .unwrap_or(Cow::Borrowed(types.as_str())),
          )
        }
        None => {
          // fallback to checking the main entrypoint for
          // a corresponding declaration file
          if let Some(main) = self.legacy_fallback_resolve(package_json) {
            let main = package_json.path.parent().unwrap().join(main).clean();
            let decl_path_result = self.path_to_declaration_path(
              LocalPath {
                path: main,
                known_exists: false,
              },
              maybe_referrer,
              resolution_mode,
              conditions,
            );
            // don't surface errors, fallback to checking the index now
            if let Ok(url_or_path) = decl_path_result {
              return Ok(url_or_path);
            }
          }
          None
        }
      }
    } else {
      self
        .legacy_fallback_resolve(package_json)
        .map(Cow::Borrowed)
    };

    if let Some(main) = maybe_main.as_deref() {
      let package_path = package_json.path.parent().unwrap();

      // Find the package root: if the package.json is inside a
      // node_modules directory, the root is the package folder directly
      // under node_modules (e.g. node_modules/pkg/ or
      // node_modules/@scope/pkg/). This allows nested package.json files
      // (subpath exports) to have "main" fields that reference sibling
      // directories within the same package.
      let package_root = find_package_root_from_node_modules(package_path)
        .unwrap_or_else(|| package_path.to_path_buf());

      let guess = package_path.join(main).clean();

      // Ensure the resolved main path doesn't escape the package
      // directory via path traversal (e.g. "main": "../../../secret.json")
      if !guess.starts_with(&package_root) {
        return Err(
          ModuleNotFoundError {
            specifier: UrlOrPath::Path(guess),
            maybe_referrer: maybe_referrer.map(|r| r.display()),
            suggested_ext: None,
          }
          .into(),
        );
      }

      if self.sys.is_file(Cow::Borrowed(&guess)) {
        return Ok(self.maybe_resolve_types(
          LocalUrlOrPath::Path(LocalPath {
            path: guess,
            known_exists: true,
          }),
          maybe_referrer,
          resolution_mode,
          conditions,
          resolution_kind,
        )?);
      }

      // todo(dsherret): investigate exactly how node and typescript handles this
      let endings = if resolution_kind.is_types() {
        match resolution_mode {
          ResolutionMode::Require => {
            vec![".d.ts", ".d.cts", "/index.d.ts", "/index.d.cts"]
          }
          ResolutionMode::Import => vec![
            ".d.ts",
            ".d.mts",
            "/index.d.ts",
            "/index.d.mts",
            ".d.cts",
            "/index.d.cts",
          ],
        }
      } else {
        vec![".js", "/index.js"]
      };
      for ending in endings {
        let guess = package_path.join(format!("{main}{ending}")).clean();
        if guess.starts_with(&package_root)
          && self.sys.is_file(Cow::Borrowed(&guess))
        {
          // TODO(bartlomieju): emitLegacyIndexDeprecation()
          return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
            path: guess,
            known_exists: true,
          })));
        }
      }
    }

    self.legacy_index_resolve(
      package_json.path.parent().unwrap(),
      maybe_referrer,
      resolution_mode,
      resolution_kind,
    )
  }

  fn legacy_index_resolve(
    &self,
    directory: &Path,
    maybe_referrer: Option<&UrlOrPathRef>,
    resolution_mode: ResolutionMode,
    resolution_kind: NodeResolutionKind,
  ) -> Result<MaybeTypesResolvedUrl, LegacyResolveError> {
    let index_file_names = if resolution_kind.is_types() {
      // todo(dsherret): investigate exactly how typescript does this
      match resolution_mode {
        ResolutionMode::Require => vec!["index.d.ts", "index.d.cts"],
        ResolutionMode::Import => {
          vec!["index.d.ts", "index.d.mts", "index.d.cts"]
        }
      }
    } else {
      // Node only resolves index.js and not index.cjs/mjs
      vec!["index.js"]
    };
    for index_file_name in index_file_names {
      let guess = directory.join(index_file_name).clean();
      if self.sys.is_file(Cow::Borrowed(&guess)) {
        // TODO(bartlomieju): emitLegacyIndexDeprecation()
        return Ok(MaybeTypesResolvedUrl(LocalUrlOrPath::Path(LocalPath {
          path: guess,
          known_exists: true,
        })));
      }
    }

    if resolution_kind.is_types() {
      Err(
        TypesNotFoundError(Box::new(TypesNotFoundErrorData {
          code_specifier: UrlOrPathRef::from_path(&directory.join("index.js"))
            .display(),
          maybe_referrer: maybe_referrer.map(|r| r.display()),
        }))
        .into(),
      )
    } else {
      Err(
        ModuleNotFoundError {
          specifier: UrlOrPath::Path(directory.join("index.js")),
          maybe_referrer: maybe_referrer.map(|r| r.display()),
          suggested_ext: None,
        }
        .into(),
      )
    }
  }

  fn resolve_types_package_folder_from_package_json(
    &self,
    pkg_json: &PackageJson,
    maybe_referrer: Option<&UrlOrPathRef>,
  ) -> Option<PathBuf> {
    let package_name = pkg_json.name.as_deref()?;
    let maybe_version = pkg_json
      .version
      .as_ref()
      .and_then(|v| Version::parse_from_npm(v).ok());
    self.resolve_types_package_folder_with_name_and_version(
      package_name,
      maybe_version.as_ref(),
      maybe_referrer,
    )
  }

  fn resolve_types_package_folder_with_name_and_version(
    &self,
    package_name: &str,
    maybe_version: Option<&Version>,
    maybe_referrer: Option<&UrlOrPathRef>,
  ) -> Option<PathBuf> {
    let types_package_name = types_package_name(package_name)?;
    log::debug!(
      "Attempting to resolve types package '{}@{}'.",
      types_package_name,
      maybe_version
        .as_ref()
        .map(|s| s.to_string())
        .as_deref()
        .unwrap_or("*")
    );
    self.npm_pkg_folder_resolver.resolve_types_package_folder(
      &types_package_name,
      maybe_version,
      maybe_referrer,
    )
  }

  /// Resolves a specifier that is pointing into a node_modules folder by canonicalizing it.
  ///
  /// Returns `None` when the specifier is not in a node_modules folder.
  pub fn handle_if_in_node_modules(&self, specifier: &Url) -> Option<Url> {
    // skip canonicalizing if we definitely know it's unnecessary
    if specifier.scheme() == "file"
      && specifier.path().contains("/node_modules/")
    {
      // Specifiers in the node_modules directory are canonicalized
      // so canonicalize then check if it's in the node_modules directory.
      let specifier = resolve_specifier_into_node_modules(&self.sys, specifier);
      return Some(specifier);
    }

    None
  }

  /// Ex. returns `fs` for `node:fs`
  fn get_module_name_from_builtin_node_module_url<'url>(
    &self,
    url: &'url Url,
  ) -> Result<Option<&'url str>, UnknownBuiltInNodeModuleError> {
    if url.scheme() != "node" {
      return Ok(None);
    }

    let module_name = url.path();

    if !self
      .is_built_in_node_module_checker
      .is_builtin_node_module(module_name)
    {
      return Err(UnknownBuiltInNodeModuleError {
        module_name: module_name.to_string(),
      });
    }
    Ok(Some(module_name))
  }
}

struct ResolvedPkgJsonImport<'a> {
  pub target: &'a serde_json::Value,
  pub sub_path: &'a str,
  pub package_sub_path: &'a str,
  pub is_pattern: bool,
}

fn resolve_pkg_json_import<'a>(
  pkg_json: &'a PackageJson,
  name: &'a str,
) -> Option<ResolvedPkgJsonImport<'a>> {
  let imports = pkg_json.imports.as_ref()?;
  if let Some((name, target)) =
    imports.get_key_value(name).filter(|_| !name.contains('*'))
  {
    Some(ResolvedPkgJsonImport {
      target,
      sub_path: "",
      package_sub_path: name,
      is_pattern: false,
    })
  } else {
    let mut best_match: &'a str = "";
    let mut best_match_subpath: &'a str = "";
    for key in imports.keys() {
      let pattern_index = key.find('*');
      if let Some(pattern_index) = pattern_index {
        let key_sub = &key[0..pattern_index];
        if name.starts_with(key_sub) {
          let pattern_trailer = &key[pattern_index + 1..];
          // The wildcard `*` in the pattern key matches a non-empty
          // substring of `name`, so `name.len()` must be at least
          // `key.len()` (one extra character to fill the wildcard).
          // Using a strict `>` here required the wildcard to match two
          // or more characters, so `"#E"` failed to match `"#*"`. See
          // denoland/deno#30160.
          if name.len() >= key.len()
            && name.ends_with(&pattern_trailer)
            && pattern_key_compare(best_match, key) == 1
            && key.rfind('*') == Some(pattern_index)
          {
            best_match = key;
            best_match_subpath =
              &name[pattern_index..(name.len() - pattern_trailer.len())];
          }
        }
      }
    }

    if !best_match.is_empty() {
      let target = imports.get(best_match).unwrap();
      Some(ResolvedPkgJsonImport {
        target,
        sub_path: best_match_subpath,
        package_sub_path: best_match,
        is_pattern: true,
      })
    } else {
      None
    }
  }
}

/// Reads a file from disk and classifies it as a [`BinValue`] —
/// `Executable` for native binaries, `JsFile` for JavaScript bin scripts
/// (resolving through npx shims when applicable). Returns `None` if the
/// file does not exist.
pub fn read_bin_value<TSys: FsOpen>(
  path: &Path,
  sys: &NodeResolutionSys<TSys>,
) -> Option<BinValue> {
  let mut file = match sys.fs_open(path, OpenOptions::new().read()) {
    Ok(file) => file,
    Err(err) => {
      if err.kind() == std::io::ErrorKind::NotFound {
        return None;
      }
      log::debug!(
        "Failed to open bin file '{}': {:#}; treating as executable",
        path.display(),
        err,
      );
      return Some(BinValue::Executable(path.to_path_buf()));
    }
  };
  let mut buf = [0; 4];
  let (is_binary, buf): (bool, &[u8]) = {
    let result = file.read_exact(&mut buf);
    if let Err(err) = result {
      log::debug!("Failed to read binary file '{}': {:#}", path.display(), err);
      // safer fallback to assume it's a binary
      (true, &[])
    } else {
      (
        is_binary(&buf) || (!(buf[0] == b'#' && buf[1] == b'!')),
        &buf[..],
      )
    }
  };

  if is_binary {
    return Some(BinValue::Executable(path.to_path_buf()));
  }
  let mut buf_read = BufReader::new(file);
  let mut contents = Vec::new();
  contents.extend_from_slice(buf);
  if let Ok(len) = buf_read.read_to_end(&mut contents)
    && len > 0
    && let Ok(contents) = String::from_utf8(contents)
    && let Some(path) =
      resolve_execution_path_from_npx_shim(Cow::Borrowed(path), &contents)
  {
    return Some(BinValue::JsFile(path));
  }

  Some(BinValue::Executable(path.to_path_buf()))
}

/// This is not ideal, but it works ok because it allows us to bypass
/// the shebang and execute the script directly with Deno.
fn resolve_execution_path_from_npx_shim(
  file_path: Cow<Path>,
  text: &str,
) -> Option<PathBuf> {
  static SCRIPT_PATH_RE: Lazy<Regex> =
    lazy_regex::lazy_regex!(r#"exec\s+node\s+"\$basedir\/([^"]+)" "\$@""#);

  let maybe_first_line = {
    let index = text.find("\n")?;
    Some(&text[0..index])
  };

  if let Some(first_line) = maybe_first_line {
    // NOTE(bartlomieju): this is not perfect, but handle two most common scenarios
    // where Node is run without any args.
    if first_line == "#!/usr/bin/env node"
      || first_line == "#!/usr/bin/env -S node"
    {
      // launch this file itself because it's a JS file
      return Some(file_path.into_owned());
    }
  }

  // Search for...
  // > "$basedir/../next/dist/bin/next" "$@"
  // ...which is what it will look like on Windows
  SCRIPT_PATH_RE
    .captures(text)
    .and_then(|c| c.get(1))
    .map(|relative_path| {
      file_path.parent().unwrap().join(relative_path.as_str())
    })
}

fn resolve_bin_entry_value<'a>(
  package_json: &PackageJson,
  bins: &'a BTreeMap<String, BinValue>,
  bin_name: Option<&str>,
) -> Result<&'a BinValue, AnyError> {
  if bins.is_empty() {
    bail!(
      "'{}' did not have a bin property with a string or non-empty object value",
      package_json.path.display()
    );
  }
  let default_bin = package_json.resolve_default_bin_name().ok();
  let searching_bin = bin_name.or(default_bin);
  match searching_bin.and_then(|bin_name| bins.get(bin_name)) {
    Some(bin) => Ok(bin),
    _ => {
      if bins.len() > 1
        && let Some(first) = bins.values().next()
        && bins.values().all(|bin| bin == first)
      {
        return Ok(first);
      }
      if bin_name.is_none()
        && bins.len() == 1
        && let Some(first) = bins.values().next()
      {
        return Ok(first);
      }
      let default_bin = package_json.resolve_default_bin_name().ok();
      let prefix = package_json
        .name
        .as_ref()
        .map(|n| {
          let mut prefix = format!("npm:{}", n);
          if let Some(version) = &package_json.version {
            prefix.push('@');
            prefix.push_str(version);
          }
          prefix
        })
        .unwrap_or_default();
      let keys = bins
        .keys()
        .map(|k| {
          if prefix.is_empty() {
            format!(" * {k}")
          } else if Some(k.as_str()) == default_bin {
            format!(" * {prefix}")
          } else {
            format!(" * {prefix}/{k}")
          }
        })
        .collect::<Vec<_>>();
      bail!(
        "'{}' did not have a bin entry for '{}'{}",
        package_json.path.display(),
        searching_bin.unwrap_or("<unspecified>"),
        if keys.is_empty() {
          "".to_string()
        } else {
          format!("\n\nPossibilities:\n{}", keys.join("\n"))
        }
      )
    }
  }
}

fn should_be_treated_as_relative_or_absolute_path(specifier: &str) -> bool {
  if specifier.is_empty() {
    return false;
  }

  if specifier.starts_with('/') {
    return true;
  }

  deno_path_util::is_relative_specifier(specifier)
}

/// Alternate `PathBuf::with_extension` that will handle known extensions
/// more intelligently.
fn with_known_extension(path: &Path, ext: &str) -> PathBuf {
  const NON_DECL_EXTS: &[&str] = &[
    "cjs", "js", "json", "jsx", "mjs", "tsx", /* ex. types.d */ "d",
  ];
  const DECL_EXTS: &[&str] = &["cts", "mts", "ts"];

  let file_name = match path.file_name() {
    Some(value) => value.to_string_lossy(),
    None => return path.to_path_buf(),
  };
  let lowercase_file_name = file_name.to_lowercase();
  let period_index = lowercase_file_name.rfind('.').and_then(|period_index| {
    let ext = &lowercase_file_name[period_index + 1..];
    if DECL_EXTS.contains(&ext) {
      if let Some(next_period_index) =
        lowercase_file_name[..period_index].rfind('.')
      {
        if &lowercase_file_name[next_period_index + 1..period_index] == "d" {
          Some(next_period_index)
        } else {
          Some(period_index)
        }
      } else {
        Some(period_index)
      }
    } else if NON_DECL_EXTS.contains(&ext) {
      Some(period_index)
    } else {
      None
    }
  });

  let file_name = match period_index {
    Some(period_index) => &file_name[..period_index],
    None => &file_name,
  };
  path.with_file_name(format!("{file_name}.{ext}"))
}

/// The subpath of a URL specifier (e.g. an `npm:` specifier) is percent
/// encoded, so a file name containing non-ASCII characters arrives here in
/// encoded form (for example a file whose name contains a single non-ASCII
/// byte pair shows up as `...%C3%A6...`). Decode it so that it can be matched
/// against package.json "exports" entries and joined onto a filesystem path
/// without being double encoded when later converted back to a URL.
///
/// Encoded path separators (`/` and `\`) are intentionally left untouched so
/// that `finalize_resolution` still rejects them rather than allowing path
/// traversal.
fn decode_package_subpath(sub_path: &str) -> Cow<'_, str> {
  if !sub_path.contains('%') {
    return Cow::Borrowed(sub_path);
  }
  if lazy_regex::regex!(r"(?i)%2f|%5c").is_match(sub_path) {
    return Cow::Borrowed(sub_path);
  }
  match percent_encoding::percent_decode_str(sub_path).decode_utf8() {
    Ok(decoded) => decoded,
    Err(_) => Cow::Borrowed(sub_path),
  }
}

fn to_specifier_display_string(url: &UrlOrPathRef) -> String {
  if let Ok(path) = url.path() {
    path.display().to_string()
  } else {
    url.display().to_string()
  }
}

fn throw_invalid_subpath(
  subpath: String,
  package_json_path: &Path,
  internal: bool,
  maybe_referrer: Option<&UrlOrPathRef>,
) -> InvalidModuleSpecifierError {
  let ie = if internal { "imports" } else { "exports" };
  let reason = format!(
    "request is not a valid subpath for the \"{}\" resolution of {}",
    ie,
    package_json_path.display(),
  );
  InvalidModuleSpecifierError {
    request: subpath,
    reason: Cow::Owned(reason),
    maybe_referrer: maybe_referrer.map(to_specifier_display_string),
  }
}

pub fn parse_npm_pkg_name<'a>(
  specifier: &'a str,
  referrer: &UrlOrPathRef,
) -> Result<(&'a str, Cow<'static, str>, bool), InvalidModuleSpecifierError> {
  let mut separator_index = specifier.find('/');
  let mut valid_package_name = true;
  let mut is_scoped = false;
  if specifier.is_empty() {
    valid_package_name = false;
  } else if specifier.starts_with('@') {
    is_scoped = true;
    if let Some(index) = separator_index {
      separator_index = specifier[index + 1..]
        .find('/')
        .map(|new_index| index + 1 + new_index);
    } else {
      valid_package_name = false;
    }
  }

  let (package_name, subpath) = if let Some(index) = separator_index {
    let (package_name, subpath) = specifier.split_at(index);
    (package_name, Cow::Owned(format!(".{}", subpath)))
  } else {
    (specifier, Cow::Borrowed("."))
  };

  // Package name cannot have leading . and cannot have percent-encoding or separators.
  for ch in package_name.chars() {
    if ch == '%' || ch == '\\' {
      valid_package_name = false;
      break;
    }
  }

  if !valid_package_name {
    return Err(errors::InvalidModuleSpecifierError {
      request: specifier.to_string(),
      reason: Cow::Borrowed("is not a valid package name"),
      maybe_referrer: Some(to_specifier_display_string(referrer)),
    });
  }

  Ok((package_name, subpath, is_scoped))
}

/// Resolves a specifier that is pointing into a node_modules folder.
///
/// Note: This should be called whenever getting the specifier from
/// a Module::External(module) reference because that module might
/// not be fully resolved at the time deno_graph is analyzing it
/// because the node_modules folder might not exist at that time.
pub fn resolve_specifier_into_node_modules(
  sys: &impl FsCanonicalize,
  specifier: &Url,
) -> Url {
  deno_path_util::url_to_file_path(specifier)
    .ok()
    // this path might not exist at the time the graph is being created
    // because the node_modules folder might not yet exist
    .and_then(|path| {
      deno_path_util::fs::canonicalize_path_maybe_not_exists(sys, &path).ok()
    })
    .and_then(|path| deno_path_util::url_from_file_path(&path).ok())
    .unwrap_or_else(|| specifier.clone())
}

fn pattern_key_compare(a: &str, b: &str) -> i32 {
  let a_pattern_index = a.find('*');
  let b_pattern_index = b.find('*');

  let base_len_a = if let Some(index) = a_pattern_index {
    index + 1
  } else {
    a.len()
  };
  let base_len_b = if let Some(index) = b_pattern_index {
    index + 1
  } else {
    b.len()
  };

  if base_len_a > base_len_b {
    return -1;
  }

  if base_len_b > base_len_a {
    return 1;
  }

  if a_pattern_index.is_none() {
    return 1;
  }

  if b_pattern_index.is_none() {
    return -1;
  }

  if a.len() > b.len() {
    return -1;
  }

  if b.len() > a.len() {
    return 1;
  }

  0
}

/// Gets the corresponding @types package for the provided package name
/// returning `None` when the package is already a @types package.
pub fn types_package_name(package_name: &str) -> Option<String> {
  if package_name.starts_with("@types/") {
    return None;
  }
  // Scoped packages will get two underscores for each slash
  // https://github.com/DefinitelyTyped/DefinitelyTyped/tree/15f1ece08f7b498f4b9a2147c2a46e94416ca777#what-about-scoped-packages
  capacity_builder::StringBuilder::build(|builder| {
    builder.append("@types/");
    for (i, c) in package_name.chars().enumerate() {
      match c {
        '@' if i == 0 => {
          // ignore
        }
        '/' => {
          builder.append("__");
        }
        c => builder.append(c),
      }
    }
  })
  .ok()
}

/// Node is more lenient joining paths than the url crate is,
/// so this function handles that.
fn node_join_url(url: &Url, path: &str) -> Result<Url, url::ParseError> {
  if let Some(suffix) = path.strip_prefix(".//") {
    // specifier had two leading slashes
    url.join(&format!("./{}", suffix))
  } else {
    url.join(path)
  }
}

struct TypesVersions<'a, TSys: FsMetadata> {
  dir_path: &'a Path,
  value: &'a serde_json::Map<std::string::String, serde_json::Value>,
  sys: &'a NodeResolutionSys<TSys>,
}

impl<'a, TSys: FsMetadata> TypesVersions<'a, TSys> {
  pub fn map(&self, search: &str) -> Option<Cow<'a, str>> {
    let mut search = search
      .strip_prefix("./")
      .unwrap_or(search)
      .trim_matches('/');
    for (key, value) in self.value {
      let key = key.strip_suffix("./").unwrap_or(key).trim_matches('/');
      let is_match = if key == "*" || key == search {
        true
      } else if let Some(key_prefix) = key.strip_suffix("/*") {
        if let Some(new_search) = search.strip_prefix(key_prefix) {
          search = new_search.trim_matches('/');
          true
        } else {
          false
        }
      } else {
        false
      };
      if !is_match {
        continue;
      }
      if let Some(values) = value.as_array() {
        for value in values.iter().filter_map(|s| s.as_str()) {
          let value = if let Some(asterisk_index) = value.find('*') {
            Cow::Owned(format!(
              "{}{}{}",
              &value[..asterisk_index],
              search,
              &value[asterisk_index + 1..]
            ))
          } else {
            Cow::Borrowed(value)
          };
          let path = self.dir_path.join(value.as_ref());
          if self.sys.is_file(Cow::Owned(path)) {
            return Some(value);
          }
        }
      }
    }
    None
  }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BinValue {
  JsFile(PathBuf),
  Executable(PathBuf),
}

impl BinValue {
  pub fn path(&self) -> &Path {
    match self {
      BinValue::JsFile(path) => path,
      BinValue::Executable(path) => path,
    }
  }
}
pub fn is_binary(data: &[u8]) -> bool {
  is_elf(data) || is_macho(data) || is_pe(data)
}

// vendored from libsui because they're super small
/// Check if the given data is an ELF64 binary
fn is_elf(data: &[u8]) -> bool {
  if data.len() < 4 {
    return false;
  }
  let magic = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
  magic == 0x7f454c46
}

/// Check if the given data is a 64-bit Mach-O binary
fn is_macho(data: &[u8]) -> bool {
  if data.len() < 4 {
    return false;
  }
  let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
  magic == 0xfeedfacf
}

/// Check if the given data is a PE32+ binary
fn is_pe(data: &[u8]) -> bool {
  if data.len() < 2 {
    return false;
  }
  let magic = u16::from_le_bytes([data[0], data[1]]);
  magic == 0x5a4d
}

/// Given a path inside a `node_modules` tree, find the package root by
/// locating the last `node_modules` path component and taking the next
/// segment (or two for scoped packages like `@scope/pkg`). Uses the last
/// occurrence to handle nested node_modules trees correctly. Returns
/// `None` if no `node_modules` component is found, in which case the
/// caller should fall back to the package.json's own directory.
fn find_package_root_from_node_modules(path: &Path) -> Option<PathBuf> {
  let components: Vec<_> = path.components().collect();
  // Find the last node_modules component
  let nm_idx = components
    .iter()
    .rposition(|c| c.as_os_str() == "node_modules")?;
  // Need at least one component after node_modules for the package name
  if nm_idx + 1 >= components.len() {
    return None;
  }
  let mut prefix = PathBuf::new();
  for c in &components[..=nm_idx] {
    prefix.push(c);
  }
  let first = &components[nm_idx + 1];
  let first_str = first.as_os_str().to_string_lossy();
  if first_str.starts_with('@') {
    // Scoped package: @scope/name - need two components
    if nm_idx + 2 >= components.len() {
      return None;
    }
    prefix.push(first);
    prefix.push(components[nm_idx + 2]);
  } else {
    prefix.push(first);
  }
  Some(prefix)
}

#[cfg(test)]
mod tests {
  use deno_package_json::PackageJsonBins;
  use serde_json::json;
  use sys_traits::FsCreateDirAll;
  use sys_traits::FsWrite;
  use sys_traits::impls::InMemorySys;

  use super::*;
  use crate::PackageJsonResolver;

  fn build_package_json(json: Value) -> PackageJson {
    PackageJson::load_from_value(PathBuf::from("/package.json"), json).unwrap()
  }

  fn resolve_bins(package_json: &PackageJson) -> BTreeMap<String, BinValue> {
    match package_json.resolve_bins().unwrap() {
      PackageJsonBins::Directory(_) => unreachable!(),
      PackageJsonBins::Bins(bins) => bins
        .into_iter()
        .map(|(k, v)| (k, BinValue::JsFile(v)))
        .collect(),
    }
  }

  #[derive(Debug)]
  struct TestBuiltInNodeModuleChecker;

  impl IsBuiltInNodeModuleChecker for TestBuiltInNodeModuleChecker {
    fn is_builtin_node_module(&self, _module_name: &str) -> bool {
      false
    }
  }

  struct TestNpmPackageFolderResolver;

  impl NpmPackageFolderResolver for TestNpmPackageFolderResolver {
    fn resolve_package_folder_from_package(
      &self,
      _specifier: &str,
      _referrer: &UrlOrPathRef,
    ) -> Result<PathBuf, errors::PackageFolderResolveError> {
      unreachable!()
    }

    fn resolve_types_package_folder(
      &self,
      _types_package_name: &str,
      _maybe_package_version: Option<&Version>,
      _maybe_referrer: Option<&UrlOrPathRef>,
    ) -> Option<PathBuf> {
      None
    }
  }

  struct TestInNpmPackageChecker;

  impl InNpmPackageChecker for TestInNpmPackageChecker {
    fn in_npm_package(&self, _specifier: &Url) -> bool {
      false
    }
  }

  fn test_node_resolver() -> NodeResolver<
    TestInNpmPackageChecker,
    TestBuiltInNodeModuleChecker,
    TestNpmPackageFolderResolver,
    InMemorySys,
  > {
    let sys = InMemorySys::default();
    NodeResolver::new(
      TestInNpmPackageChecker,
      TestBuiltInNodeModuleChecker,
      TestNpmPackageFolderResolver,
      deno_maybe_sync::new_rc(PackageJsonResolver::new(sys.clone(), None)),
      NodeResolutionSys::new(sys, None),
      NodeResolverOptions::default(),
    )
  }

  #[test]
  fn test_default_conditions_include_module_sync() {
    assert_eq!(
      IMPORT_CONDITIONS,
      &[
        Cow::Borrowed("deno"),
        Cow::Borrowed("node"),
        Cow::Borrowed("import"),
        Cow::Borrowed("module-sync"),
      ]
    );
    assert_eq!(
      REQUIRE_CONDITIONS,
      &[
        Cow::Borrowed("require"),
        Cow::Borrowed("node"),
        Cow::Borrowed("module-sync"),
      ]
    );
  }

  #[test]
  fn test_module_sync_condition_matches_import_and_require() {
    let resolver = test_node_resolver();
    let package_json_path =
      PathBuf::from("/node_modules/module-sync-pkg/package.json");
    let package_exports = json!({
      ".": {
        "module-sync": "./sync.mjs",
        "node": "./node.cjs"
      }
    });
    let package_exports = package_exports.as_object().unwrap();

    let import_resolved = resolver
      .package_exports_resolve(
        &package_json_path,
        ".",
        package_exports,
        None,
        ResolutionMode::Import,
        resolver.condition_resolver.resolve(ResolutionMode::Import),
        NodeResolutionKind::Execution,
      )
      .unwrap();
    let require_resolved = resolver
      .package_exports_resolve(
        &package_json_path,
        ".",
        package_exports,
        None,
        ResolutionMode::Require,
        resolver.require_conditions(),
        NodeResolutionKind::Execution,
      )
      .unwrap();

    assert_eq!(
      import_resolved.into_path().unwrap(),
      PathBuf::from("/node_modules/module-sync-pkg/sync.mjs")
    );
    assert_eq!(
      require_resolved.into_path().unwrap(),
      PathBuf::from("/node_modules/module-sync-pkg/sync.mjs")
    );
  }

  #[test]
  fn test_resolve_bin_entry_value() {
    // should resolve the specified value
    {
      let pkg_json = build_package_json(json!({
        "name": "pkg",
        "version": "1.1.1",
        "bin": {
          "bin1": "./value1",
          "bin2": "./value2",
          "pkg": "./value3",
        }
      }));
      let bins = resolve_bins(&pkg_json);
      assert_eq!(
        resolve_bin_entry_value(&pkg_json, &bins, Some("bin1"))
          .unwrap()
          .path(),
        pkg_json.dir_path().join("./value1")
      );
      assert_eq!(
        resolve_bin_entry_value(&pkg_json, &bins, Some("pkg"))
          .unwrap()
          .path(),
        pkg_json.dir_path().join("./value3")
      );

      // should not resolve when specified value does not exist
      assert_eq!(
        resolve_bin_entry_value(&pkg_json, &bins, Some("other"))
          .err()
          .unwrap()
          .to_string(),
        concat!(
          "'/package.json' did not have a bin entry for 'other'\n",
          "\n",
          "Possibilities:\n",
          " * npm:pkg@1.1.1/bin1\n",
          " * npm:pkg@1.1.1/bin2\n",
          " * npm:pkg@1.1.1"
        )
      );
    }

    // should not resolve when default value can't be determined
    {
      let pkg_json = build_package_json(json!({
        "name": "pkg",
        "version": "1.1.1",
        "bin": {
          "bin": "./value1",
          "bin2": "./value2",
        }
      }));
      let bins = resolve_bins(&pkg_json);
      assert_eq!(
        resolve_bin_entry_value(&pkg_json, &bins, Some("pkg"))
          .err()
          .unwrap()
          .to_string(),
        concat!(
          "'/package.json' did not have a bin entry for 'pkg'\n",
          "\n",
          "Possibilities:\n",
          " * npm:pkg@1.1.1/bin\n",
          " * npm:pkg@1.1.1/bin2",
        )
      );
    }

    // should resolve since all the values are the same
    {
      let pkg_json = build_package_json(json!({
        "name": "pkg",
        "version": "1.2.3",
        "bin": {
          "bin1": "./value",
          "bin2": "./value",
        }
      }));
      let bins = resolve_bins(&pkg_json);
      assert_eq!(
        resolve_bin_entry_value(&pkg_json, &bins, Some("pkg"))
          .unwrap()
          .path(),
        pkg_json.dir_path().join("./value")
      );
    }

    // should resolve when not specified and only one value
    {
      let pkg_json = build_package_json(json!({
        "name": "pkg",
        "version": "1.2.3",
        "bin": {
          "something": "./value",
        }
      }));
      let bins = resolve_bins(&pkg_json);
      assert_eq!(
        resolve_bin_entry_value(&pkg_json, &bins, None)
          .unwrap()
          .path(),
        pkg_json.dir_path().join("./value")
      );
    }

    // should not resolve when specified and is a string
    {
      let pkg_json = build_package_json(json!({
        "name": "pkg",
        "version": "1.2.3",
        "bin": "./value",
      }));
      let bins = resolve_bins(&pkg_json);
      assert_eq!(
        resolve_bin_entry_value(&pkg_json, &bins, Some("path"))
          .err()
          .unwrap()
          .to_string(),
        concat!(
          "'/package.json' did not have a bin entry for 'path'\n",
          "\n",
          "Possibilities:\n",
          " * npm:pkg@1.2.3"
        )
      );
    }

    // no version in the package.json
    {
      let pkg_json = build_package_json(json!({
        "name": "pkg",
        "bin": {
          "bin1": "./value1",
          "bin2": "./value2",
        }
      }));
      let bins = resolve_bins(&pkg_json);
      assert_eq!(
        resolve_bin_entry_value(&pkg_json, &bins, Some("pkg"))
          .err()
          .unwrap()
          .to_string(),
        concat!(
          "'/package.json' did not have a bin entry for 'pkg'\n",
          "\n",
          "Possibilities:\n",
          " * npm:pkg/bin1\n",
          " * npm:pkg/bin2",
        )
      );
    }

    // no name or version in the package.json
    {
      let pkg_json = build_package_json(json!({
        "bin": {
          "bin1": "./value1",
          "bin2": "./value2",
        }
      }));
      let bins = resolve_bins(&pkg_json);
      assert_eq!(
        resolve_bin_entry_value(&pkg_json, &bins, Some("bin"))
          .err()
          .unwrap()
          .to_string(),
        concat!(
          "'/package.json' did not have a bin entry for 'bin'\n",
          "\n",
          "Possibilities:\n",
          " * bin1\n",
          " * bin2",
        )
      );
    }
  }

  #[test]
  fn test_parse_npm_pkg_name() {
    let dummy_referrer = Url::parse("http://example.com").unwrap();
    let dummy_referrer = UrlOrPathRef::from_url(&dummy_referrer);

    assert_eq!(
      parse_npm_pkg_name("fetch-blob", &dummy_referrer).unwrap(),
      ("fetch-blob", Cow::Borrowed("."), false)
    );
    assert_eq!(
      parse_npm_pkg_name("@vue/plugin-vue", &dummy_referrer).unwrap(),
      ("@vue/plugin-vue", Cow::Borrowed("."), true)
    );
    assert_eq!(
      parse_npm_pkg_name("@astrojs/prism/dist/highlighter", &dummy_referrer)
        .unwrap(),
      (
        "@astrojs/prism",
        Cow::Owned("./dist/highlighter".to_string()),
        true
      )
    );
  }

  #[test]
  fn test_with_known_extension() {
    let cases = &[
      ("test", "d.ts", "test.d.ts"),
      ("test.d.ts", "ts", "test.ts"),
      ("test.worker", "d.ts", "test.worker.d.ts"),
      ("test.d.mts", "js", "test.js"),
    ];
    for (path, ext, expected) in cases {
      let actual = with_known_extension(Path::new(path), ext);
      assert_eq!(actual.to_string_lossy(), *expected);
    }
  }

  #[test]
  fn test_types_package_name() {
    assert_eq!(types_package_name("name").unwrap(), "@types/name");
    assert_eq!(
      types_package_name("@scoped/package").unwrap(),
      "@types/scoped__package"
    );
    assert_eq!(types_package_name("@types/node"), None);
  }

  #[test]
  fn test_types_versions() {
    let dir_path = PathBuf::from("/dir");
    let sys = InMemorySys::default();
    sys.fs_create_dir_all(dir_path.join("ts3.1")).unwrap();
    sys.fs_write(dir_path.join("file.d.ts"), "").unwrap();
    sys.fs_write(dir_path.join("ts3.1/file.d.ts"), "").unwrap();
    sys.fs_write(dir_path.join("ts3.1/file2.d.ts"), "").unwrap();
    let node_resolution_sys = NodeResolutionSys::new(sys, None);

    // asterisk key
    {
      let value = serde_json::json!({
        "*": ["ts3.1/*"]
      });
      let types_versions = TypesVersions {
        dir_path: &dir_path,
        value: value.as_object().unwrap(),
        sys: &node_resolution_sys,
      };
      assert_eq!(types_versions.map("file.d.ts").unwrap(), "ts3.1/file.d.ts");
      assert_eq!(
        types_versions.map("file2.d.ts").unwrap(),
        "ts3.1/file2.d.ts"
      );
      assert!(types_versions.map("non_existent/file.d.ts").is_none());
    }
    // specific file
    {
      let value = serde_json::json!({
        "types.d.ts": ["ts3.1/file.d.ts"]
      });
      let types_versions = TypesVersions {
        dir_path: &dir_path,
        value: value.as_object().unwrap(),
        sys: &node_resolution_sys,
      };
      assert_eq!(types_versions.map("types.d.ts").unwrap(), "ts3.1/file.d.ts");
      assert!(types_versions.map("file2.d.ts").is_none());
    }
    // multiple specific files
    {
      let value = serde_json::json!({
        "types.d.ts": ["ts3.1/file.d.ts"],
        "other.d.ts": ["ts3.1/file2.d.ts"],
      });
      let types_versions = TypesVersions {
        dir_path: &dir_path,
        value: value.as_object().unwrap(),
        sys: &node_resolution_sys,
      };
      assert_eq!(types_versions.map("types.d.ts").unwrap(), "ts3.1/file.d.ts");
      assert_eq!(
        types_versions.map("other.d.ts").unwrap(),
        "ts3.1/file2.d.ts"
      );
      assert!(types_versions.map("file2.d.ts").is_none());
    }
    // existing fallback
    {
      let value = serde_json::json!({
        "*": ["ts3.1/*", "ts3.1/file2.d.ts"]
      });
      let types_versions = TypesVersions {
        dir_path: &dir_path,
        value: value.as_object().unwrap(),
        sys: &node_resolution_sys,
      };
      assert_eq!(
        types_versions.map("testing/types.d.ts").unwrap(),
        "ts3.1/file2.d.ts"
      );
    }
    // text then asterisk in key
    {
      let value = serde_json::json!({
        "sub/*": ["ts3.1/file.d.ts"]
      });
      let types_versions = TypesVersions {
        dir_path: &dir_path,
        value: value.as_object().unwrap(),
        sys: &node_resolution_sys,
      };
      assert_eq!(
        types_versions.map("sub/types.d.ts").unwrap(),
        "ts3.1/file.d.ts"
      );
    }
    // text then asterisk in key and asterisk in value
    {
      let value = serde_json::json!({
        "sub/*": ["ts3.1/*"]
      });
      let types_versions = TypesVersions {
        dir_path: &dir_path,
        value: value.as_object().unwrap(),
        sys: &node_resolution_sys,
      };
      assert_eq!(
        types_versions.map("sub/file.d.ts").unwrap(),
        "ts3.1/file.d.ts"
      );
    }
  }

  #[test]
  fn test_resolve_pkg_json_import_single_char_wildcard() {
    // Regression test for https://github.com/denoland/deno/issues/30160
    // The pattern key `#*` must match short specifiers like `#E` whose
    // wildcard segment is a single character. Previously the length
    // check rejected these.
    let pkg = build_package_json(serde_json::json!({
      "name": "pkg",
      "imports": {
        "#mask/*": "./lib/private/mask/*.js",
        "#types/*": "./lib/private/types/*.js",
        "#*": "./lib/private/*.js"
      }
    }));
    let single_char =
      resolve_pkg_json_import(&pkg, "#E").expect("#E should match #*");
    assert_eq!(single_char.package_sub_path, "#*");
    assert_eq!(single_char.sub_path, "E");
    assert!(single_char.is_pattern);

    let multi_char =
      resolve_pkg_json_import(&pkg, "#abc").expect("#abc should match #*");
    assert_eq!(multi_char.package_sub_path, "#*");
    assert_eq!(multi_char.sub_path, "abc");

    // The more specific pattern still wins for inputs it can match.
    let scoped = resolve_pkg_json_import(&pkg, "#mask/x")
      .expect("#mask/x should match #mask/*");
    assert_eq!(scoped.package_sub_path, "#mask/*");
    assert_eq!(scoped.sub_path, "x");

    // The wildcard still has to match a non-empty segment.
    assert!(resolve_pkg_json_import(&pkg, "#").is_none());
  }

  #[test]
  fn test_resolve_execution_path_from_npx_shim() {
    // example shim on unix
    let unix_shim = r#"#!/usr/bin/env node
"use strict";
console.log('Hi!');
"#;
    let path = PathBuf::from("/node_modules/.bin/example");
    assert_eq!(
      resolve_execution_path_from_npx_shim(Cow::Borrowed(&path), unix_shim)
        .unwrap(),
      path
    );
    // example shim on unix
    let unix_shim = r#"#!/usr/bin/env -S node
"use strict";
console.log('Hi!');
"#;
    let path = PathBuf::from("/node_modules/.bin/example");
    assert_eq!(
      resolve_execution_path_from_npx_shim(Cow::Borrowed(&path), unix_shim)
        .unwrap(),
      path
    );
    // example shim on windows
    let windows_shim = r#"#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")

case `uname` in
    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac

if [ -x "$basedir/node" ]; then
  exec "$basedir/node"  "$basedir/../example/bin/example" "$@"
else
  exec node  "$basedir/../example/bin/example" "$@"
fi"#;
    assert_eq!(
      resolve_execution_path_from_npx_shim(Cow::Borrowed(&path), windows_shim)
        .unwrap(),
      path.parent().unwrap().join("../example/bin/example")
    );
  }
}
