use deno_core::error::generic_error;
use deno_core::error::AnyError;
use deno_core::include_js_files;
use deno_core::normalize_path;
use deno_core::op;
use deno_core::url::Url;
use deno_core::Extension;
use deno_core::JsRuntimeInspector;
use deno_core::OpState;
use once_cell::sync::Lazy;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
pub mod errors;
mod package_json;
mod path;
mod resolution;
pub use package_json::PackageJson;
pub use path::PathClean;
pub use resolution::get_closest_package_json;
pub use resolution::get_package_scope_config;
pub use resolution::legacy_main_resolve;
pub use resolution::package_exports_resolve;
pub use resolution::package_imports_resolve;
pub use resolution::package_resolve;
pub use resolution::path_to_declaration_path;
pub use resolution::NodeModuleKind;
pub use resolution::NodeResolutionMode;
pub use resolution::DEFAULT_CONDITIONS;
use std::cell::RefCell;
pub trait NodePermissions {
  fn check_read(&mut self, path: &Path) -> Result<(), AnyError>;
}
pub trait RequireNpmResolver {
  fn resolve_package_folder_from_package(
    &self,
    specifier: &str,
    referrer: &Path,
    mode: NodeResolutionMode,
  ) -> Result<PathBuf, AnyError>;
  fn resolve_package_folder_from_path(
    &self,
    path: &Path,
  ) -> Result<PathBuf, AnyError>;
  fn in_npm_package(&self, path: &Path) -> bool;
  fn ensure_read_permission(
    &self,
    permissions: &mut dyn NodePermissions,
    path: &Path,
  ) -> Result<(), AnyError>;
}
pub const MODULE_ES_SHIM: &str = include_str!("./module_es_shim.js");
pub static NODE_GLOBAL_THIS_NAME: Lazy<String> = Lazy::new(|| {
  let now = std::time::SystemTime::now();
  let seconds = now
    .duration_since(std::time::SystemTime::UNIX_EPOCH)
    .unwrap()
    .as_secs();
    format!("__DENO_NODE_GLOBAL_THIS_{}__", seconds)
});
pub static NODE_ENV_VAR_ALLOWLIST: Lazy<HashSet<String>> = Lazy::new(|| {
      let mut set = HashSet::new();
  set.insert("NODE_DEBUG".to_string());
  set.insert("NODE_OPTIONS".to_string());
  set
});
pub fn init<P: NodePermissions + 'static>(
  maybe_npm_resolver: Option<Rc<dyn RequireNpmResolver>>,
) -> Extension {
  Extension::builder(env!("CARGO_PKG_NAME"))
    .js(include_js_files!(
      prefix "deno:ext/node",
      "01_node.js",
      "02_require.js",
    ))
    .ops(vec![
      op_require_init_paths::decl(),
      op_require_node_module_paths::decl::<P>(),
      op_require_proxy_path::decl(),
      op_require_is_deno_dir_package::decl(),
      op_require_resolve_deno_dir::decl(),
      op_require_is_request_relative::decl(),
      op_require_resolve_lookup_paths::decl(),
      op_require_try_self_parent_path::decl::<P>(),
      op_require_try_self::decl::<P>(),
      op_require_real_path::decl::<P>(),
      op_require_path_is_absolute::decl(),
      op_require_path_dirname::decl(),
      op_require_stat::decl::<P>(),
      op_require_path_resolve::decl(),
      op_require_path_basename::decl(),
      op_require_read_file::decl::<P>(),
      op_require_as_file_path::decl(),
      op_require_resolve_exports::decl::<P>(),
      op_require_read_closest_package_json::decl::<P>(),
      op_require_read_package_scope::decl::<P>(),
      op_require_package_imports_resolve::decl::<P>(),
      op_require_break_on_next_statement::decl(),
    ])
    .state(move |state| {
      if let Some(npm_resolver) = maybe_npm_resolver.clone() {
        state.put(npm_resolver);
      }
      Ok(())
    })
    .build()
}
fn ensure_read_permission<P>(
  state: &mut OpState,
  file_path: &Path,
) -> Result<(), AnyError>
where
  P: NodePermissions + 'static,
{
  let resolver = {
    let resolver = state.borrow::<Rc<dyn RequireNpmResolver>>();
    resolver.clone()
  };
  let permissions = state.borrow_mut::<P>();
  resolver.ensure_read_permission(permissions, file_path)
}
#[op]
pub fn op_require_init_paths() -> Vec<String> {
    
                      
            
  
        
        
                    
  vec![]
}
#[op]
pub fn op_require_node_module_paths<P>(
  state: &mut OpState,
  from: String,
) -> Result<Vec<String>, AnyError>
where
  P: NodePermissions + 'static,
{
    let from = deno_core::resolve_path(&from)
    .unwrap()
    .to_file_path()
    .unwrap();
  ensure_read_permission::<P>(state, &from)?;
  if cfg!(windows) {
        let from_str = from.to_str().unwrap();
    if from_str.len() >= 3 {
      let bytes = from_str.as_bytes();
      if bytes[from_str.len() - 1] == b'\\' && bytes[from_str.len() - 2] == b':'
      {
        let p = from_str.to_owned() + "node_modules";
        return Ok(vec![p]);
      }
    }
  } else {
            if from.to_string_lossy() == "/" {
      return Ok(vec!["/node_modules".to_string()]);
    }
  }
  let mut paths = vec![];
  let mut current_path = from.as_path();
  let mut maybe_parent = Some(current_path);
  while let Some(parent) = maybe_parent {
    if !parent.ends_with("/node_modules") {
      paths.push(parent.join("node_modules").to_string_lossy().to_string());
      current_path = parent;
      maybe_parent = current_path.parent();
    }
  }
  if !cfg!(windows) {
        paths.push("/node_modules".to_string());
  }
  Ok(paths)
}
#[op]
fn op_require_proxy_path(filename: String) -> String {
    let trailing_slash = if cfg!(windows) {
                filename.ends_with('\\') || filename.ends_with('/')
  } else {
    filename.ends_with('/')
  };
  if trailing_slash {
    let p = PathBuf::from(filename);
    p.join("noop.js").to_string_lossy().to_string()
  } else {
    filename
  }
}
#[op]
fn op_require_is_request_relative(request: String) -> bool {
  if request.starts_with("./") || request.starts_with("../") || request == ".."
  {
    return true;
  }
  if cfg!(windows) {
    if request.starts_with(".\\") {
      return true;
    }
    if request.starts_with("..\\") {
      return true;
    }
  }
  false
}
#[op]
fn op_require_resolve_deno_dir(
  state: &mut OpState,
  request: String,
  parent_filename: String,
) -> Option<String> {
  let resolver = state.borrow::<Rc<dyn RequireNpmResolver>>();
  resolver
    .resolve_package_folder_from_package(
      &request,
      &PathBuf::from(parent_filename),
      NodeResolutionMode::Execution,
    )
    .ok()
    .map(|p| p.to_string_lossy().to_string())
}
#[op]
fn op_require_is_deno_dir_package(state: &mut OpState, path: String) -> bool {
  let resolver = state.borrow::<Rc<dyn RequireNpmResolver>>();
  resolver.in_npm_package(&PathBuf::from(path))
}
#[op]
fn op_require_resolve_lookup_paths(
  request: String,
  maybe_parent_paths: Option<Vec<String>>,
  parent_filename: String,
) -> Option<Vec<String>> {
  if !request.starts_with('.')
    || (request.len() > 1
      && !request.starts_with("..")
      && !request.starts_with("./")
      && (!cfg!(windows) || !request.starts_with(".\\")))
  {
    let module_paths = vec![];
    let mut paths = module_paths;
    if let Some(mut parent_paths) = maybe_parent_paths {
      if !parent_paths.is_empty() {
        paths.append(&mut parent_paths);
      }
    }
    if !paths.is_empty() {
      return Some(paths);
    } else {
      return None;
    }
  }
          
      
  let p = PathBuf::from(parent_filename);
  Some(vec![p.parent().unwrap().to_string_lossy().to_string()])
}
#[op]
fn op_require_path_is_absolute(p: String) -> bool {
  PathBuf::from(p).is_absolute()
}
#[op]
fn op_require_stat<P>(
  state: &mut OpState,
  path: String,
) -> Result<i32, AnyError>
where
  P: NodePermissions + 'static,
{
  let path = PathBuf::from(path);
  ensure_read_permission::<P>(state, &path)?;
  if let Ok(metadata) = std::fs::metadata(&path) {
    if metadata.is_file() {
      return Ok(0);
    } else {
      return Ok(1);
    }
  }
  Ok(-1)
}
#[op]
fn op_require_real_path<P>(
  state: &mut OpState,
  request: String,
) -> Result<String, AnyError>
where
  P: NodePermissions + 'static,
{
  let path = PathBuf::from(request);
  ensure_read_permission::<P>(state, &path)?;
  let mut canonicalized_path = path.canonicalize()?;
  if cfg!(windows) {
    canonicalized_path = PathBuf::from(
      canonicalized_path
        .display()
        .to_string()
        .trim_start_matches("\\\\?\\"),
    );
  }
  Ok(canonicalized_path.to_string_lossy().to_string())
}
fn path_resolve(parts: Vec<String>) -> String {
  assert!(!parts.is_empty());
  let mut p = PathBuf::from(&parts[0]);
  if parts.len() > 1 {
    for part in &parts[1..] {
      p = p.join(part);
    }
  }
  normalize_path(p).to_string_lossy().to_string()
}
#[op]
fn op_require_path_resolve(parts: Vec<String>) -> String {
  path_resolve(parts)
}
#[op]
fn op_require_path_dirname(request: String) -> Result<String, AnyError> {
  let p = PathBuf::from(request);
  if let Some(parent) = p.parent() {
    Ok(parent.to_string_lossy().to_string())
  } else {
    Err(generic_error("Path doesn't have a parent"))
  }
}
#[op]
fn op_require_path_basename(request: String) -> Result<String, AnyError> {
  let p = PathBuf::from(request);
  if let Some(path) = p.file_name() {
    Ok(path.to_string_lossy().to_string())
  } else {
    Err(generic_error("Path doesn't have a file name"))
  }
}
#[op]
fn op_require_try_self_parent_path<P>(
  state: &mut OpState,
  has_parent: bool,
  maybe_parent_filename: Option<String>,
  maybe_parent_id: Option<String>,
) -> Result<Option<String>, AnyError>
where
  P: NodePermissions + 'static,
{
  if !has_parent {
    return Ok(None);
  }
  if let Some(parent_filename) = maybe_parent_filename {
    return Ok(Some(parent_filename));
  }
  if let Some(parent_id) = maybe_parent_id {
    if parent_id == "<repl>" || parent_id == "internal/preload" {
      if let Ok(cwd) = std::env::current_dir() {
        ensure_read_permission::<P>(state, &cwd)?;
        return Ok(Some(cwd.to_string_lossy().to_string()));
      }
    }
  }
  Ok(None)
}
#[op]
fn op_require_try_self<P>(
  state: &mut OpState,
  parent_path: Option<String>,
  request: String,
) -> Result<Option<String>, AnyError>
where
  P: NodePermissions + 'static,
{
  if parent_path.is_none() {
    return Ok(None);
  }
  let resolver = state.borrow::<Rc<dyn RequireNpmResolver>>().clone();
  let permissions = state.borrow_mut::<P>();
  let pkg = resolution::get_package_scope_config(
    &Url::from_file_path(parent_path.unwrap()).unwrap(),
    &*resolver,
    permissions,
  )
  .ok();
  if pkg.is_none() {
    return Ok(None);
  }
  let pkg = pkg.unwrap();
  if pkg.exports.is_none() {
    return Ok(None);
  }
  if pkg.name.is_none() {
    return Ok(None);
  }
  let pkg_name = pkg.name.as_ref().unwrap().to_string();
  let mut expansion = ".".to_string();
  if request == pkg_name {
      } else if request.starts_with(&format!("{}/", pkg_name)) {
    expansion += &request[pkg_name.len()..];
  } else {
    return Ok(None);
  }
  let referrer = deno_core::url::Url::from_file_path(&pkg.path).unwrap();
  if let Some(exports) = &pkg.exports {
    resolution::package_exports_resolve(
      &pkg.path,
      expansion,
      exports,
      &referrer,
      NodeModuleKind::Cjs,
      resolution::REQUIRE_CONDITIONS,
      NodeResolutionMode::Execution,
      &*resolver,
      permissions,
    )
    .map(|r| Some(r.to_string_lossy().to_string()))
  } else {
    Ok(None)
  }
}
#[op]
fn op_require_read_file<P>(
  state: &mut OpState,
  file_path: String,
) -> Result<String, AnyError>
where
  P: NodePermissions + 'static,
{
  let file_path = PathBuf::from(file_path);
  ensure_read_permission::<P>(state, &file_path)?;
  Ok(std::fs::read_to_string(file_path)?)
}
#[op]
pub fn op_require_as_file_path(file_or_url: String) -> String {
  if let Ok(url) = Url::parse(&file_or_url) {
    if let Ok(p) = url.to_file_path() {
      return p.to_string_lossy().to_string();
    }
  }
  file_or_url
}
#[op]
fn op_require_resolve_exports<P>(
  state: &mut OpState,
  uses_local_node_modules_dir: bool,
  modules_path: String,
  _request: String,
  name: String,
  expansion: String,
  parent_path: String,
) -> Result<Option<String>, AnyError>
where
  P: NodePermissions + 'static,
{
  let resolver = state.borrow::<Rc<dyn RequireNpmResolver>>().clone();
  let permissions = state.borrow_mut::<P>();
  let pkg_path = if resolver.in_npm_package(&PathBuf::from(&modules_path))
    && !uses_local_node_modules_dir
  {
    modules_path
  } else {
    path_resolve(vec![modules_path, name])
  };
  let pkg = PackageJson::load(
    &*resolver,
    permissions,
    PathBuf::from(&pkg_path).join("package.json"),
  )?;
  if let Some(exports) = &pkg.exports {
    let referrer = Url::from_file_path(parent_path).unwrap();
    resolution::package_exports_resolve(
      &pkg.path,
      format!(".{}", expansion),
      exports,
      &referrer,
      NodeModuleKind::Cjs,
      resolution::REQUIRE_CONDITIONS,
      NodeResolutionMode::Execution,
      &*resolver,
      permissions,
    )
    .map(|r| Some(r.to_string_lossy().to_string()))
  } else {
    Ok(None)
  }
}
#[op]
fn op_require_read_closest_package_json<P>(
  state: &mut OpState,
  filename: String,
) -> Result<PackageJson, AnyError>
where
  P: NodePermissions + 'static,
{
  ensure_read_permission::<P>(
    state,
    PathBuf::from(&filename).parent().unwrap(),
  )?;
  let resolver = state.borrow::<Rc<dyn RequireNpmResolver>>().clone();
  let permissions = state.borrow_mut::<P>();
  resolution::get_closest_package_json(
    &Url::from_file_path(filename).unwrap(),
    &*resolver,
    permissions,
  )
}
#[op]
fn op_require_read_package_scope<P>(
  state: &mut OpState,
  package_json_path: String,
) -> Option<PackageJson>
where
  P: NodePermissions + 'static,
{
  let resolver = state.borrow::<Rc<dyn RequireNpmResolver>>().clone();
  let permissions = state.borrow_mut::<P>();
  let package_json_path = PathBuf::from(package_json_path);
  PackageJson::load(&*resolver, permissions, package_json_path).ok()
}
#[op]
fn op_require_package_imports_resolve<P>(
  state: &mut OpState,
  parent_filename: String,
  request: String,
) -> Result<Option<String>, AnyError>
where
  P: NodePermissions + 'static,
{
  let parent_path = PathBuf::from(&parent_filename);
  ensure_read_permission::<P>(state, &parent_path)?;
  let resolver = state.borrow::<Rc<dyn RequireNpmResolver>>().clone();
  let permissions = state.borrow_mut::<P>();
  let pkg = PackageJson::load(
    &*resolver,
    permissions,
    parent_path.join("package.json"),
  )?;
  if pkg.imports.is_some() {
    let referrer =
      deno_core::url::Url::from_file_path(&parent_filename).unwrap();
    let r = resolution::package_imports_resolve(
      &request,
      &referrer,
      NodeModuleKind::Cjs,
      resolution::REQUIRE_CONDITIONS,
      NodeResolutionMode::Execution,
      &*resolver,
      permissions,
    )
    .map(|r| Some(Url::from_file_path(r).unwrap().to_string()));
    state.put(resolver);
    r
  } else {
    Ok(None)
  }
}
#[op]
fn op_require_break_on_next_statement(state: &mut OpState) {
  let inspector = state.borrow::<Rc<RefCell<JsRuntimeInspector>>>();
  inspector
    .borrow_mut()
    .wait_for_session_and_break_on_next_statement()
}