// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::*;
use libc::INT_MAX;

#[repr(transparent)]
pub(crate) struct SendPtr<T>(pub *const T);

impl<T> SendPtr<T> {
  // silly function to get around `clippy::redundant_locals`
  pub fn take(self) -> *const T {
    self.0
  }
}

unsafe impl<T> Send for SendPtr<T> {}
unsafe impl<T> Sync for SendPtr<T> {}

pub fn get_array_buffer_ptr(ab: v8::Local<v8::ArrayBuffer>) -> *mut c_void {
  match ab.data() {
    Some(p) => p.as_ptr(),
    None => std::ptr::null_mut(),
  }
}

struct BufferFinalizer {
  env: *mut Env,
  finalize_cb: napi_finalize,
  finalize_data: *mut c_void,
  finalize_hint: *mut c_void,
}

impl Drop for BufferFinalizer {
  fn drop(&mut self) {
    unsafe {
      (self.finalize_cb)(self.env as _, self.finalize_data, self.finalize_hint);
    }
  }
}

pub(crate) extern "C" fn backing_store_deleter_callback(
  data: *mut c_void,
  _byte_length: usize,
  deleter_data: *mut c_void,
) {
  let mut finalizer =
    unsafe { Box::<BufferFinalizer>::from_raw(deleter_data as _) };

  finalizer.finalize_data = data;

  drop(finalizer);
}

pub(crate) fn make_external_backing_store(
  env: *mut Env,
  data: *mut c_void,
  byte_length: usize,
  finalize_data: *mut c_void,
  finalize_cb: napi_finalize,
  finalize_hint: *mut c_void,
) -> v8::UniqueRef<v8::BackingStore> {
  let finalizer = Box::new(BufferFinalizer {
    env,
    finalize_data,
    finalize_cb,
    finalize_hint,
  });

  unsafe {
    v8::ArrayBuffer::new_backing_store_from_ptr(
      data,
      byte_length,
      backing_store_deleter_callback,
      Box::into_raw(finalizer) as _,
    )
  }
}

#[macro_export]
macro_rules! check_env {
  ($env: expr) => {{
    let env = $env;
    if env.is_null() {
      return napi_invalid_arg;
    }
    unsafe { &mut *env }
  }};
}

#[macro_export]
macro_rules! return_error_status_if_false {
  ($env: expr, $condition: expr, $status: ident) => {
    if !$condition {
      return Err($crate::util::napi_set_last_error($env, $status).into());
    }
  };
}

#[macro_export]
macro_rules! return_status_if_false {
  ($env: expr, $condition: expr, $status: ident) => {
    if !$condition {
      return $crate::util::napi_set_last_error($env, $status);
    }
  };
}

pub(crate) unsafe fn check_new_from_utf8_len<'s>(
  env: *mut Env,
  str_: *const c_char,
  len: usize,
) -> Result<v8::Local<'s, v8::String>, napi_status> {
  let env = unsafe { &mut *env };
  return_error_status_if_false!(
    env,
    (len == NAPI_AUTO_LENGTH) || len <= INT_MAX as _,
    napi_invalid_arg
  );
  return_error_status_if_false!(env, !str_.is_null(), napi_invalid_arg);
  let string = if len == NAPI_AUTO_LENGTH {
    let result = unsafe { std::ffi::CStr::from_ptr(str_ as *const _) }.to_str();
    return_error_status_if_false!(env, result.is_ok(), napi_generic_failure);
    result.unwrap()
  } else {
    let string = unsafe { std::slice::from_raw_parts(str_ as *const u8, len) };
    let result = std::str::from_utf8(string);
    return_error_status_if_false!(env, result.is_ok(), napi_generic_failure);
    result.unwrap()
  };
  let result = {
    let env = unsafe { &mut *(env as *mut Env) };
    v8::String::new(&mut env.scope(), string)
  };
  return_error_status_if_false!(env, result.is_some(), napi_generic_failure);
  Ok(result.unwrap())
}

#[inline]
pub(crate) unsafe fn check_new_from_utf8<'s>(
  env: *mut Env,
  str_: *const c_char,
) -> Result<v8::Local<'s, v8::String>, napi_status> {
  unsafe { check_new_from_utf8_len(env, str_, NAPI_AUTO_LENGTH) }
}

pub(crate) unsafe fn v8_name_from_property_descriptor<'s>(
  env: *mut Env,
  p: &'s napi_property_descriptor,
) -> Result<v8::Local<'s, v8::Name>, napi_status> {
  if !p.utf8name.is_null() {
    unsafe { check_new_from_utf8(env, p.utf8name).map(|v| v.into()) }
  } else {
    match *p.name {
      Some(v) => match v.try_into() {
        Ok(name) => Ok(name),
        Err(_) => Err(napi_name_expected),
      },
      None => Err(napi_name_expected),
    }
  }
}

pub(crate) fn napi_clear_last_error(env: *mut Env) -> napi_status {
  let env = unsafe { &mut *env };
  env.last_error.error_code = napi_ok;
  env.last_error.engine_error_code = 0;
  env.last_error.engine_reserved = std::ptr::null_mut();
  env.last_error.error_message = std::ptr::null_mut();
  napi_ok
}

pub(crate) fn napi_set_last_error(
  env: *mut Env,
  error_code: napi_status,
) -> napi_status {
  let env = unsafe { &mut *env };
  env.last_error.error_code = error_code;
  error_code
}

#[macro_export]
macro_rules! status_call {
  ($call: expr) => {
    let status = $call;
    if status != napi_ok {
      return status;
    }
  };
}

pub trait Nullable {
  fn is_null(&self) -> bool;
}

impl<T> Nullable for *mut T {
  fn is_null(&self) -> bool {
    (*self).is_null()
  }
}

impl<T> Nullable for *const T {
  fn is_null(&self) -> bool {
    (*self).is_null()
  }
}

impl<T> Nullable for Option<T> {
  fn is_null(&self) -> bool {
    self.is_none()
  }
}

impl<'s> Nullable for napi_value<'s> {
  fn is_null(&self) -> bool {
    self.is_none()
  }
}

#[macro_export]
macro_rules! check_arg {
  ($env: expr, $ptr: expr) => {
    $crate::return_status_if_false!(
      $env,
      !$crate::util::Nullable::is_null(&$ptr),
      napi_invalid_arg
    );
  };
}

#[macro_export]
macro_rules! napi_wrap {
  ( $( # [ $attr:meta ] )* $vis:vis fn $name:ident $( < $( $x:lifetime ),* > )? ( $env:ident : & $( $lt:lifetime )? mut Env $( , $ident:ident : $ty:ty )* $(,)? ) -> napi_status $body:block ) => {
    $( # [ $attr ] )*
    #[no_mangle]
    $vis unsafe extern "C" fn $name $( < $( $x ),* > )? ( env_ptr : *mut Env , $( $ident : $ty ),* ) -> napi_status {
      let env: & $( $lt )? mut Env = $crate::check_env!(env_ptr);

      if env.last_exception.is_some() {
        return napi_pending_exception;
      }

      $crate::util::napi_clear_last_error(env);

      let scope_env = unsafe { &mut *env_ptr };
      let scope = &mut scope_env.scope();
      let try_catch = &mut v8::TryCatch::new(scope);

      #[inline(always)]
      fn inner $( < $( $x ),* > )? ( $env: & $( $lt )? mut Env , $( $ident : $ty ),* ) -> napi_status $body

      log::trace!("NAPI ENTER: {}", stringify!($name));

      let result = inner( env, $( $ident ),* );

      log::trace!("NAPI EXIT: {} {}", stringify!($name), result);

      if let Some(exception) = try_catch.exception() {
        let env = unsafe { &mut *env_ptr };
        let global = v8::Global::new(env.isolate(), exception);
        env.last_exception = Some(global);
        return $crate::util::napi_set_last_error(env_ptr, napi_pending_exception);
      }

      if result != napi_ok {
        return $crate::util::napi_set_last_error(env_ptr, result);
      }

      return result;
    }
  };

  ( $( # [ $attr:meta ] )* $vis:vis fn $name:ident $( < $( $x:lifetime ),* > )? ( $( $ident:ident : $ty:ty ),* $(,)? ) -> napi_status $body:block ) => {
    $( # [ $attr ] )*
    #[no_mangle]
    $vis unsafe extern "C" fn $name $( < $( $x ),* > )? ( $( $ident : $ty ),* ) -> napi_status {
      #[inline(always)]
      fn inner $( < $( $x ),* > )? ( $( $ident : $ty ),* ) -> napi_status $body

      log::trace!("NAPI ENTER: {}", stringify!($name));

      let result = inner( $( $ident ),* );

      log::trace!("NAPI EXIT: {} {}", stringify!($name), result);

      result
    }
  };
}