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

#![allow(clippy::all, reason = "test napi code")]
#![allow(clippy::print_stdout, reason = "test napi code")]
#![allow(clippy::print_stderr, reason = "test napi code")]
#![allow(clippy::undocumented_unsafe_blocks, reason = "test napi code")]
// napi_sys declares functions as `pub safe fn` inside `unsafe extern "C"` blocks
// on non-windows, so `unsafe { napi_call(...) }` wrappers become redundant.
#![allow(unused_unsafe, reason = "napi_sys safe fn in unsafe extern blocks")]

use std::ffi::c_void;

use napi_sys::*;

pub mod array;
pub mod arraybuffer;
pub mod r#async;
pub mod bigint;
pub mod buffer;
pub mod callback;
pub mod callback_scope;
pub mod cleanup_hook_async;
pub mod coerce;
pub mod dataview;
pub mod date;
pub mod env;
pub mod error;
pub mod exception;
pub mod fatal;
pub mod finalizer;
pub mod general;
pub mod handle_scope;
pub mod instance_data;
pub mod make_callback;
pub mod mem;
pub mod numbers;
pub mod object;
pub mod object_wrap;
pub mod primitives;
pub mod promise;
pub mod properties;
pub mod reference;
pub mod strings;
pub mod symbol;
pub mod tsfn;
pub mod typedarray;
pub mod uv;

#[macro_export]
macro_rules! cstr {
  ($s: literal) => {{ std::ffi::CString::new($s).unwrap().into_raw() }};
}

#[macro_export]
macro_rules! assert_napi_ok {
  ($call: expr) => {{
    assert_eq!(
      {
        #[allow(
          unused_unsafe,
          reason = "napi_sys safe fn in unsafe extern blocks"
        )]
        unsafe {
          $call
        }
      },
      napi_sys::Status::napi_ok
    );
  }};
}

#[macro_export]
macro_rules! napi_get_callback_info {
  ($env: expr, $callback_info: expr, $size: literal) => {{
    let mut args = [std::ptr::null_mut(); $size];
    let mut argc = $size;
    let mut this = std::ptr::null_mut();
    crate::assert_napi_ok!(napi_get_cb_info(
      $env,
      $callback_info,
      &mut argc,
      args.as_mut_ptr(),
      &mut this,
      std::ptr::null_mut(),
    ));
    (args, argc, this)
  }};
}

#[macro_export]
macro_rules! napi_new_property {
  ($env: expr, $name: expr, $value: expr) => {
    napi_property_descriptor {
      utf8name: concat!($name, "\0").as_ptr() as *const std::os::raw::c_char,
      name: std::ptr::null_mut(),
      method: Some($value),
      getter: None,
      setter: None,
      data: std::ptr::null_mut(),
      attributes: 0,
      value: std::ptr::null_mut(),
    }
  };
}

extern "C" fn cleanup(arg: *mut c_void) {
  println!("cleanup({})", arg as i64);
}

extern "C" fn remove_this_hook(arg: *mut c_void) {
  let env = arg as napi_env;
  unsafe { napi_remove_env_cleanup_hook(env, Some(remove_this_hook), arg) };
}

static SECRET: i64 = 42;
static WRONG_SECRET: i64 = 17;
static THIRD_SECRET: i64 = 18;

extern "C" fn install_cleanup_hook(
  env: napi_env,
  info: napi_callback_info,
) -> napi_value {
  let (_args, argc, _) = napi_get_callback_info!(env, info, 1);
  assert_eq!(argc, 0);

  unsafe {
    napi_add_env_cleanup_hook(env, Some(cleanup), WRONG_SECRET as *mut c_void);
    napi_add_env_cleanup_hook(env, Some(cleanup), SECRET as *mut c_void);
    napi_add_env_cleanup_hook(env, Some(cleanup), THIRD_SECRET as *mut c_void);
    napi_add_env_cleanup_hook(env, Some(remove_this_hook), env as *mut c_void);
    napi_remove_env_cleanup_hook(
      env,
      Some(cleanup),
      WRONG_SECRET as *mut c_void,
    );
  }

  std::ptr::null_mut()
}

pub fn init_cleanup_hook(env: napi_env, exports: napi_value) {
  let properties = &[napi_new_property!(
    env,
    "installCleanupHook",
    install_cleanup_hook
  )];

  assert_napi_ok!(napi_define_properties(
    env,
    exports,
    properties.len(),
    properties.as_ptr()
  ));
}

#[unsafe(no_mangle)]
unsafe extern "C" fn napi_register_module_v1(
  env: napi_env,
  _: napi_value,
) -> napi_value {
  unsafe {
    #[cfg(windows)]
    napi_sys::setup();
    libuv_sys_lite::setup();
  }

  // We create a fresh exports object and leave the passed
  // exports object empty.
  //
  // https://github.com/denoland/deno/issues/17349
  let mut exports = std::ptr::null_mut();
  assert_napi_ok!(napi_create_object(env, &mut exports));

  strings::init(env, exports);
  numbers::init(env, exports);
  typedarray::init(env, exports);
  arraybuffer::init(env, exports);
  array::init(env, exports);
  env::init(env, exports);
  error::init(env, exports);
  finalizer::init(env, exports);
  primitives::init(env, exports);
  properties::init(env, exports);
  promise::init(env, exports);
  coerce::init(env, exports);
  object_wrap::init(env, exports);
  callback::init(env, exports);
  r#async::init(env, exports);
  date::init(env, exports);
  tsfn::init(env, exports);
  mem::init(env, exports);
  bigint::init(env, exports);
  symbol::init(env, exports);
  make_callback::init(env, exports);
  object::init(env, exports);
  uv::init(env, exports);

  instance_data::init(env, exports);
  buffer::init(env, exports);
  dataview::init(env, exports);
  general::init(env, exports);
  handle_scope::init(env, exports);
  reference::init(env, exports);
  exception::init(env, exports);
  callback_scope::init(env, exports);
  fatal::init(env, exports);

  cleanup_hook_async::init(env, exports);

  init_cleanup_hook(env, exports);

  exports
}
