//! Implementation of Ruby macros.
//!
//! Since macros are rely on the C preprocessor, or defined as `inline` C
//! functions, they are not available when linking libruby. In order to use the
//! libruby macros from Rust, `rb-sys` implements them using the following
//! strategies:
//!
//! 1. For stable versions of Ruby, the macros are implemented as Rust functions
//! 2. For ruby-head, the macros are implemented as C functions that are linked
//!    into the crate.

#![allow(rustdoc::broken_intra_doc_links)]
#![allow(non_upper_case_globals)]
#![allow(non_snake_case)]

use crate::rb_data_type_t;
use crate::ruby_value_type;
use crate::stable_api::get_default as api;
use crate::StableApiDefinition;
use crate::VALUE;
use std::ffi::c_void;
use std::os::raw::{c_char, c_long};

/// Emulates Ruby's "if" statement.
///
/// - @param[in]  obj    An arbitrary ruby object.
/// - @retval     false  `obj` is either ::RUBY_Qfalse or ::RUBY_Qnil.
/// - @retval     true   Anything else.
///
/// ```
/// use rb_sys::special_consts::*;
///
/// assert!(!TEST(Qfalse));
/// assert!(!TEST(Qnil));
/// assert!(TEST(Qtrue));
/// ```
#[inline(always)]
pub fn TEST(obj: VALUE) -> bool {
    api().rb_test(obj)
}

/// Checks if the given object is nil.
///
/// - @param[in]  obj    An arbitrary ruby object.
/// - @retval     true   `obj` is ::RUBY_Qnil.
/// - @retval     false  Anything else.
///
/// ### Example
///
/// ```
/// use rb_sys::special_consts::*;
///
/// assert!(NIL_P(Qnil));
/// assert!(!NIL_P(Qtrue));
/// ```
#[inline(always)]
pub fn NIL_P(obj: VALUE) -> bool {
    api().nil_p(obj)
}

/// Checks if the given object is a so-called Fixnum.
///
/// - @param[in]  obj    An arbitrary ruby object.
/// - @retval     true   `obj` is a Fixnum.
/// - @retval     false  Anything else.
/// - @note       Fixnum was  a thing  in the  20th century, but  it is  rather an implementation detail today.
#[inline(always)]
pub fn FIXNUM_P(obj: VALUE) -> bool {
    api().fixnum_p(obj)
}

/// Checks if the given object is a static symbol.
///
/// - @param[in]  obj    An arbitrary ruby object.
/// - @retval     true   `obj` is a static symbol
/// - @retval     false  Anything else.
/// - @see        RB_DYNAMIC_SYM_P()
/// - @see        RB_SYMBOL_P()
/// - @note       These days  there are static  and dynamic symbols, just  like we once had Fixnum/Bignum back in the old days.
#[inline(always)]
pub fn STATIC_SYM_P(obj: VALUE) -> bool {
    api().static_sym_p(obj)
}

/// Get the backend storage of a Ruby array.
///
/// ### Safety
///
/// This function is unsafe because it dereferences a raw pointer and returns
/// raw pointers to Ruby memory. The caller must ensure that the pointer stays live
/// for the duration of usage the the underlying array (by either GC marking or
/// keeping the RArray on the stack).
///
/// - @param[in]  a  An object of ::RArray.
/// - @return     Its backend storage.
#[inline(always)]
pub unsafe fn RARRAY_CONST_PTR(obj: VALUE) -> *const VALUE {
    api().rarray_const_ptr(obj)
}

/// Get the length of a Ruby array.
///
/// ### Safety
///
/// This function is unsafe because it dereferences a raw pointer in order to
/// access internal Ruby memory.
///
/// - @param[in]  a  An object of ::RArray.
/// - @return     Its length.
#[inline(always)]
pub unsafe fn RARRAY_LEN(obj: VALUE) -> c_long {
    api().rarray_len(obj)
}

/// Read array element at index (akin to `RARRAY_AREF`).
///
/// ### Safety
///
/// This function is unsafe because it dereferences a raw pointer in order to
/// access internal Ruby memory.
///
/// - @param[in]  obj  An object of ::RArray.
/// - @param[in]  idx  Index within the array (must be within bounds: 0..RARRAY_LEN(obj)).
/// - @return     The element at the given index.
#[inline(always)]
pub unsafe fn RARRAY_AREF(obj: VALUE, idx: isize) -> VALUE {
    api().rarray_aref(obj.into(), idx)
}

/// Write array element at index (akin to `RARRAY_ASET`).
///
/// This function includes the GC write barrier for correctness.
///
/// ### Safety
///
/// This function is unsafe because it dereferences a raw pointer in order to
/// access internal Ruby memory.
///
/// - @param[in]  obj  An object of ::RArray.
/// - @param[in]  idx  Index within the array (must be within bounds: 0..RARRAY_LEN(obj)).
/// - @param[in]  val  The value to set at the given index.
#[inline(always)]
pub unsafe fn RARRAY_ASET(obj: VALUE, idx: isize, val: VALUE) {
    api().rarray_aset(obj.into(), idx, val)
}

/// Get the length of a Ruby string.
///
/// ### Safety
///
/// This function is unsafe because it dereferences a raw pointer in order to
/// access internal Ruby memory.
///
/// - @param[in]  a  An object of ::RString.
/// - @return     Its length.
#[inline(always)]
pub unsafe fn RSTRING_LEN(obj: VALUE) -> c_long {
    api().rstring_len(obj)
}

/// Get the backend storage of a Ruby string.
///
/// ### Safety
///
/// This function is unsafe because it dereferences a raw pointer and returns
/// raw pointers to Ruby memory. The caller must ensure that the pointer stays live
/// for the duration of usage the the underlying array (by either GC marking or
/// keeping the RArray on the stack).
///
/// - @param[in]  a  An object of ::RString.
/// - @return     Its backend storage
#[inline(always)]
pub unsafe fn RSTRING_PTR(obj: VALUE) -> *const c_char {
    api().rstring_ptr(obj)
}

/// Checks if the given object is a so-called Flonum.
///
/// @param[in]  obj    An arbitrary ruby object.
/// @retval     true   `obj` is a Flonum.
/// @retval     false  Anything else.
/// @see        RB_FLOAT_TYPE_P()
/// @note       These days there are Flonums and non-Flonum floats, just like we
///             once had Fixnum/Bignum back in the old days.
#[inline(always)]
pub fn FLONUM_P(obj: VALUE) -> bool {
    api().flonum_p(obj)
}

/// Checks if  the given  object is  an immediate  i.e. an  object which  has no
/// corresponding storage inside of the object space.
///
/// @param[in]  obj    An arbitrary ruby object.
/// @retval     true   `obj` is a Flonum.
/// @retval     false  Anything else.
/// @see        RB_FLOAT_TYPE_P()
/// @note       The concept of "immediate" is purely C specific.
#[inline(always)]
pub fn IMMEDIATE_P(obj: VALUE) -> bool {
    api().immediate_p(obj)
}

/// Checks if the given object is of enum ::ruby_special_consts.
///
/// @param[in]  obj    An arbitrary ruby object.
/// @retval     true   `obj` is a special constant.
/// @retval     false  Anything else.
///
/// ### Example
///
/// ```
/// use rb_sys::special_consts::*;
///
/// assert!(SPECIAL_CONST_P(Qnil));
/// assert!(SPECIAL_CONST_P(Qtrue));
/// assert!(SPECIAL_CONST_P(Qfalse));
/// ```
#[inline(always)]
pub fn SPECIAL_CONST_P(obj: VALUE) -> bool {
    api().special_const_p(obj)
}

/// Queries the type of the object.
///
/// @param[in]  obj  Object in question.
/// @pre        `obj` must not be a special constant.
/// @return     The type of `obj`.
///
/// # Safety
/// This function is unsafe because it could dereference a raw pointer when
/// attemping to access the underlying [`RBasic`] struct.
#[inline(always)]
pub unsafe fn RB_BUILTIN_TYPE(obj: VALUE) -> ruby_value_type {
    api().builtin_type(obj)
}

/// Queries if the object is an instance of ::rb_cInteger.
///
/// @param[in]  obj    Object in question.
/// @retval     true   It is.
/// @retval     false  It isn't.
///
/// # Safety
/// This function is unsafe because it could dereference a raw pointer when
/// attemping to access the underlying [`RBasic`] struct.
#[inline(always)]
pub unsafe fn RB_INTEGER_TYPE_P(obj: VALUE) -> bool {
    api().integer_type_p(obj)
}

/// Queries if the object is a dynamic symbol.
///
/// @param[in]  obj    Object in question.
/// @retval     true   It is.
/// @retval     false  It isn't.
///
/// # Safety
/// This function is unsafe because it could dereference a raw pointer when
/// attemping to access the underlying [`RBasic`] struct.
#[inline(always)]
pub unsafe fn RB_DYNAMIC_SYM_P(obj: VALUE) -> bool {
    api().dynamic_sym_p(obj)
}

/// Queries if the object is an instance of ::rb_cSymbol.
///
/// @param[in]  obj    Object in question.
/// @retval     true   It is.
/// @retval     false  It isn't.
///
/// # Safety
/// This function is unsafe because it could dereference a raw pointer when
/// attemping to access the underlying [`RBasic`] struct.
#[inline(always)]
pub unsafe fn RB_SYMBOL_P(obj: VALUE) -> bool {
    api().symbol_p(obj)
}

/// Identical to RB_BUILTIN_TYPE(), except it can also accept special constants.
///
/// @param[in]  obj  Object in question.
/// @return     The type of `obj`.
///
/// # Safety
/// This function is unsafe because it could dereference a raw pointer when
/// attemping to access the underlying [`RBasic`] struct.
#[inline(always)]
pub unsafe fn RB_TYPE(value: VALUE) -> ruby_value_type {
    api().rb_type(value)
}

/// Queries if the given object is of given type.
///
/// @param[in]  obj    An object.
/// @param[in]  t      A type.
/// @retval     true   `obj` is of type `t`.
/// @retval     false  Otherwise.
///
/// # Safety
/// This function is unsafe because it could dereference a raw pointer when
/// attemping to access the underlying [`RBasic`] struct.
#[inline(always)]
#[cfg(ruby_engine = "mri")] // truffleruby provides its own implementation
pub unsafe fn RB_TYPE_P(obj: VALUE, ty: ruby_value_type) -> bool {
    api().type_p(obj, ty)
}

/// Queries if the object is an instance of ::rb_cFloat.
///
/// @param[in]  obj    Object in question.
/// @retval     true   It is.
/// @retval     false  It isn't.
///
/// # Safety
/// This function is unsafe because it could dereference a raw pointer when
/// attemping to access the underlying [`RBasic`] struct.
#[inline(always)]
pub unsafe fn RB_FLOAT_TYPE_P(obj: VALUE) -> bool {
    api().float_type_p(obj)
}

/// Checks if the given object is an RTypedData.
///
/// @param[in]  obj    Object in question.
/// @retval     true   It is an RTypedData.
/// @retval     false  It isn't an RTypedData.
///
/// # Safety
/// This function is unsafe because it could dereference a raw pointer when
/// accessing the underlying data structure.
#[inline(always)]
pub unsafe fn RTYPEDDATA_P(obj: VALUE) -> bool {
    api().rtypeddata_p(obj)
}

/// Gets the data type information from an RTypedData object.
///
/// @param[in]  obj    An RTypedData object.
/// @return     Pointer to the rb_data_type_t structure for this object.
///
/// # Safety
/// This function is unsafe because it dereferences a raw pointer to get
/// access to the underlying data type. The caller must ensure the object
/// is a valid RTypedData.
#[inline(always)]
pub unsafe fn RTYPEDDATA_TYPE(obj: VALUE) -> *const rb_data_type_t {
    api().rtypeddata_type(obj)
}

/// Gets the data pointer from an RTypedData object.
///
/// @param[in]  obj    An RTypedData object.
/// @return     Pointer to the wrapped C struct.
///
/// # Safety
/// This function is unsafe because it dereferences a raw pointer to get
/// access to the underlying data. The caller must ensure the object
/// is a valid RTypedData.
#[inline(always)]
pub unsafe fn RTYPEDDATA_GET_DATA(obj: VALUE) -> *mut c_void {
    api().rtypeddata_get_data(obj)
}

/// Checks if the bignum is positive.
///
/// @param[in]  b      An object of RBignum.
/// @retval     false  `b` is less than zero.
/// @retval     true   Otherwise.
///
/// # Safety
/// This function is unsafe because it could dereference a raw pointer when
/// accessing the underlying bignum structure.
#[inline(always)]
pub unsafe fn RBIGNUM_POSITIVE_P(b: VALUE) -> bool {
    api().bignum_positive_p(b)
}

/// Checks if the bignum is negative.
///
/// @param[in]  b      An object of RBignum.
/// @retval     true   `b` is less than zero.
/// @retval     false  Otherwise.
///
/// # Safety
/// This function is unsafe because it could dereference a raw pointer when
/// accessing the underlying bignum structure.
#[inline(always)]
pub unsafe fn RBIGNUM_NEGATIVE_P(b: VALUE) -> bool {
    api().bignum_negative_p(b)
}

/// Convert ID to Symbol (akin to `ID2SYM` or `RB_ID2SYM`).
///
/// Converts an internal ID to its corresponding Symbol VALUE.
/// This is a safe operation - just bit manipulation for static symbols.
///
/// @param[in]  id     An ID value.
/// @return     The Symbol VALUE corresponding to the ID.
#[inline(always)]
pub fn ID2SYM(id: crate::ID) -> VALUE {
    api().id2sym(id)
}

/// Alias for ID2SYM for compatibility with Ruby naming conventions.
#[inline(always)]
pub fn RB_ID2SYM(id: crate::ID) -> VALUE {
    api().id2sym(id)
}

/// Convert Symbol to ID (akin to `SYM2ID` or `RB_SYM2ID`).
///
/// Converts a Symbol VALUE to its internal ID representation.
///
/// @param[in]  obj    A Symbol VALUE.
/// @return     The ID corresponding to the Symbol.
///
/// # Safety
/// - `obj` must be a valid Symbol VALUE
/// - For dynamic symbols, this may access the heap
#[inline(always)]
pub unsafe fn SYM2ID(obj: VALUE) -> crate::ID {
    api().sym2id(obj)
}

/// Alias for SYM2ID for compatibility with Ruby naming conventions.
///
/// # Safety
/// - `obj` must be a valid Symbol VALUE
/// - For dynamic symbols, this may access the heap
#[inline(always)]
pub unsafe fn RB_SYM2ID(obj: VALUE) -> crate::ID {
    api().sym2id(obj)
}

/// Convert Fixnum to long (akin to `FIX2LONG`).
///
/// Extracts the integer value from a Fixnum VALUE by performing an arithmetic right shift.
///
/// # Safety
/// - `obj` must be a valid Fixnum VALUE (checked with FIXNUM_P)
/// - Behavior is undefined if called on non-Fixnum values
#[inline(always)]
pub unsafe fn FIX2LONG(obj: VALUE) -> std::os::raw::c_long {
    api().fix2long(obj)
}

/// Convert Fixnum to unsigned long (akin to `FIX2ULONG`).
///
/// Extracts the unsigned integer value from a Fixnum VALUE.
///
/// # Safety
/// - `obj` must be a valid positive Fixnum VALUE
/// - Behavior is undefined for negative fixnums
#[inline(always)]
pub unsafe fn FIX2ULONG(obj: VALUE) -> std::os::raw::c_ulong {
    api().fix2ulong(obj)
}

/// Convert long to Fixnum (akin to `LONG2FIX`).
///
/// Creates a Fixnum VALUE from a long integer.
///
/// # Safety
/// - `val` must be within the valid Fixnum range (use FIXABLE to check)
/// - Behavior is undefined if value is out of range
#[inline(always)]
pub unsafe fn LONG2FIX(val: std::os::raw::c_long) -> VALUE {
    api().long2fix(val)
}

/// Check if long value can be represented as Fixnum (akin to `FIXABLE`).
///
/// Returns true if the value fits within the Fixnum range.
#[inline(always)]
pub fn FIXABLE(val: std::os::raw::c_long) -> bool {
    api().fixable(val)
}

/// Check if unsigned long value can be represented as positive Fixnum (akin to `POSFIXABLE`).
///
/// Returns true if the unsigned value fits within the positive Fixnum range.
#[inline(always)]
pub fn POSFIXABLE(val: std::os::raw::c_ulong) -> bool {
    api().posfixable(val)
}

/// Convert Ruby Integer to long (akin to `NUM2LONG`).
///
/// Converts any Ruby Integer (Fixnum or Bignum) to a C long.
/// May raise a RangeError exception if the value is out of range.
///
/// # Safety
/// - `obj` must be a valid Integer VALUE
/// - May call into Ruby runtime and potentially raise exceptions
/// - May trigger garbage collection
#[inline(always)]
pub unsafe fn NUM2LONG(obj: VALUE) -> std::os::raw::c_long {
    api().num2long(obj)
}

/// Convert Ruby Integer to unsigned long (akin to `NUM2ULONG`).
///
/// Converts any Ruby Integer (Fixnum or Bignum) to a C unsigned long.
/// May raise a RangeError exception if the value is out of range or negative.
///
/// # Safety
/// - `obj` must be a valid Integer VALUE
/// - May call into Ruby runtime and potentially raise exceptions
/// - May trigger garbage collection
#[inline(always)]
pub unsafe fn NUM2ULONG(obj: VALUE) -> std::os::raw::c_ulong {
    api().num2ulong(obj)
}

/// Convert long to Ruby Integer (akin to `LONG2NUM`).
///
/// Creates a Ruby Integer (Fixnum or Bignum) from a C long.
/// Uses Fixnum if possible, otherwise allocates a Bignum.
#[inline(always)]
pub fn LONG2NUM(val: std::os::raw::c_long) -> VALUE {
    api().long2num(val)
}

/// Convert unsigned long to Ruby Integer (akin to `ULONG2NUM`).
///
/// Creates a Ruby Integer (Fixnum or Bignum) from a C unsigned long.
/// Uses Fixnum if possible, otherwise allocates a Bignum.
#[inline(always)]
pub fn ULONG2NUM(val: std::os::raw::c_ulong) -> VALUE {
    api().ulong2num(val)
}

/// Execute GC write barrier when storing a reference (akin to `RB_OBJ_WRITE`).
///
/// Writes `young` into `*slot` and notifies the GC so generational/incremental
/// collection stays correct. Without this, the GC may collect objects that are
/// still referenced.
///
/// @param[in]  old    The object being modified (must be heap-allocated).
/// @param[in]  slot   Pointer to the VALUE slot within `old` being written to.
/// @param[in]  young  The VALUE being stored.
/// @return     `old` — matches CRuby's `RB_OBJ_WRITE` semantics.
///
/// # Safety
/// - `old` must be a valid heap-allocated Ruby object
/// - `slot` must be a valid pointer to a VALUE within `old`
/// - `young` must be a valid VALUE
#[inline(always)]
pub unsafe fn RB_OBJ_WRITE(old: VALUE, slot: *mut VALUE, young: VALUE) -> VALUE {
    api().rb_obj_write(old, slot, young)
}

/// Declare a write barrier without actually writing (akin to `RB_OBJ_WRITTEN`).
///
/// Use this when you've already written a reference but need to inform the GC.
/// This is useful when the write happens through a different mechanism but
/// the GC still needs to be notified.
///
/// @param[in]  old    The object being modified (must be heap-allocated).
/// @param[in]  oldv   The previous value (can be any VALUE).
/// @param[in]  young  The VALUE that was written.
/// @return     `old` — matches CRuby's `RB_OBJ_WRITTEN` semantics.
///
/// # Safety
/// - `old` must be a valid heap-allocated Ruby object
/// - `oldv` is the previous value (can be any VALUE)
/// - `young` must be a valid VALUE that was written
#[inline(always)]
pub unsafe fn RB_OBJ_WRITTEN(old: VALUE, oldv: VALUE, young: VALUE) -> VALUE {
    api().rb_obj_written(old, oldv, young)
}

/// Check if an object can have flags (akin to `RB_FL_ABLE`).
///
/// Returns false for immediate values (nil, true, false, Fixnum, Symbol, Flonum).
/// Returns true for heap-allocated objects that can have flags set.
///
/// @param[in]  obj    An object to check.
/// @retval     true   The object can have flags.
/// @retval     false  The object is an immediate value.
#[inline(always)]
pub fn FL_ABLE(obj: VALUE) -> bool {
    api().fl_able(obj)
}

/// Get pointer to end of string contents (akin to `RSTRING_END`).
///
/// # Safety
/// - `obj` must be a valid Ruby String object
#[inline(always)]
pub unsafe fn RSTRING_END(obj: VALUE) -> *const c_char {
    api().rstring_end(obj)
}

/// Get data pointer from RData/TypedData object (akin to `DATA_PTR`).
///
/// # Safety
/// - `obj` must be a valid RData or TypedData object
#[inline(always)]
pub unsafe fn DATA_PTR(obj: VALUE) -> *mut c_void {
    api().rdata_ptr(obj)
}

/// Freeze an object (akin to `RB_OBJ_FREEZE`).
///
/// # Safety
/// - `obj` must be a valid heap-allocated Ruby object
#[inline(always)]
pub unsafe fn RB_OBJ_FREEZE(obj: VALUE) {
    api().rb_obj_freeze(obj)
}

/// Check if object is promoted to old GC generation (akin to `RB_OBJ_PROMOTED`).
///
/// # Safety
/// - `obj` must be a valid VALUE
#[inline(always)]
pub unsafe fn RB_OBJ_PROMOTED(obj: VALUE) -> bool {
    api().rb_obj_promoted(obj)
}

/// Raw version assuming FL_ABLE (akin to `RB_OBJ_PROMOTED_RAW`).
///
/// # Safety
/// - `obj` must be a valid heap-allocated Ruby object (FL_ABLE must be true)
#[inline(always)]
pub unsafe fn RB_OBJ_PROMOTED_RAW(obj: VALUE) -> bool {
    api().rb_obj_promoted_raw(obj)
}

/// Convert Ruby numeric to C double (akin to `NUM2DBL`).
///
/// Works for Float (including Flonum), Fixnum, and other numeric types.
///
/// @param[in]  obj    A Ruby numeric object.
/// @return     C double representation.
///
/// # Safety
/// This function is unsafe because it may dereference a raw pointer to access
/// underlying Ruby data. The caller must ensure the VALUE is a valid Ruby numeric.
#[inline(always)]
pub unsafe fn NUM2DBL(obj: VALUE) -> std::os::raw::c_double {
    api().num2dbl(obj)
}

/// Convert C double to Ruby Float VALUE (akin to `DBL2NUM`).
///
/// May return a Flonum (tagged pointer) for small values on 64-bit platforms,
/// or a heap-allocated Float object.
///
/// @param[in]  val    A C double value.
/// @return     A Ruby Float VALUE.
#[inline(always)]
pub fn DBL2NUM(val: std::os::raw::c_double) -> VALUE {
    api().dbl2num(val)
}

/// Get hash size (akin to `RHASH_SIZE`).
///
/// Returns the number of entries in the hash.
///
/// @param[in]  obj    A Ruby Hash object.
/// @return     Number of entries.
///
/// # Safety
/// This function is unsafe because it dereferences a raw pointer to access
/// underlying Ruby data. The caller must ensure the VALUE is a valid Hash.
#[inline(always)]
pub unsafe fn RHASH_SIZE(obj: VALUE) -> usize {
    api().rhash_size(obj)
}

/// Check if hash is empty (akin to `RHASH_EMPTY_P`).
///
/// @param[in]  obj    A Ruby Hash object.
/// @retval     true   The hash has no entries.
/// @retval     false  The hash has at least one entry.
///
/// # Safety
/// This function is unsafe because it dereferences a raw pointer to access
/// underlying Ruby data. The caller must ensure the VALUE is a valid Hash.
#[inline(always)]
pub unsafe fn RHASH_EMPTY_P(obj: VALUE) -> bool {
    api().rhash_empty_p(obj)
}

/// Get encoding index from object (akin to `ENCODING_GET`).
///
/// Returns the encoding index stored in the object's flags.
///
/// @param[in]  obj    A Ruby object with encoding (String, Regexp, Symbol).
/// @return     Encoding index.
///
/// # Safety
/// This function is unsafe because it dereferences a raw pointer to access
/// the RBasic flags. The caller must ensure the VALUE is a valid object
/// with encoding support.
#[inline(always)]
pub unsafe fn ENCODING_GET(obj: VALUE) -> std::os::raw::c_int {
    api().encoding_get(obj)
}
