// SPDX-License-Identifier: MPL-2.0
// Copyright ijl (2024-2026)

#[repr(transparent)]
pub(crate) struct PyTypeRef {
    ptr: core::ptr::NonNull<pyo3_ffi::PyTypeObject>,
}

unsafe impl Send for PyTypeRef {}
unsafe impl Sync for PyTypeRef {}

impl PartialEq for PyTypeRef {
    fn eq(&self, other: &Self) -> bool {
        core::ptr::eq(self.ptr.as_ptr(), other.ptr.as_ptr())
    }
}

impl PyTypeRef {
    pub(crate) fn from_pyobject(ptr: *mut pyo3_ffi::PyObject) -> Self {
        unsafe {
            debug_assert!(!ptr.is_null());
            let type_ptr = (*ptr).ob_type;
            debug_assert!(!type_ptr.is_null());
            Self {
                ptr: core::ptr::NonNull::new_unchecked(type_ptr),
            }
        }
    }

    #[inline]
    pub(crate) const fn as_ptr(&self) -> *mut pyo3_ffi::PyTypeObject {
        self.ptr.as_ptr()
    }
}

#[cfg(Py_GIL_DISABLED)]
#[allow(non_upper_case_globals)]
pub(crate) const _Py_IMMORTAL_REFCNT_LOCAL: u32 = u32::MAX;

#[cfg(all(Py_3_14, target_pointer_width = "32"))]
#[allow(non_upper_case_globals)]
pub(crate) const _Py_IMMORTAL_MINIMUM_REFCNT: pyo3_ffi::Py_ssize_t =
    ((1 as core::ffi::c_long) << (30 as core::ffi::c_long)) as pyo3_ffi::Py_ssize_t;

#[cfg(all(Py_3_12, not(Py_3_14)))]
#[allow(non_upper_case_globals)]
pub(crate) const _Py_IMMORTAL_REFCNT: pyo3_ffi::Py_ssize_t = {
    if cfg!(target_pointer_width = "64") {
        core::ffi::c_uint::MAX as pyo3_ffi::Py_ssize_t
    } else {
        // for 32-bit systems, use the lower 30 bits (see comment in CPython's object.h)
        (core::ffi::c_uint::MAX >> 2) as pyo3_ffi::Py_ssize_t
    }
};
#[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn _Py_IsImmortal(op: *mut pyo3_ffi::PyObject) -> core::ffi::c_int {
    unsafe {
        #[cfg(all(target_pointer_width = "64", not(Py_GIL_DISABLED)))]
        {
            (((*op).ob_refcnt.ob_refcnt as pyo3_ffi::PY_INT32_T) < 0) as core::ffi::c_int
        }

        #[cfg(all(target_pointer_width = "32", not(Py_GIL_DISABLED)))]
        {
            #[cfg(not(Py_3_14))]
            {
                ((*op).ob_refcnt.ob_refcnt == _Py_IMMORTAL_REFCNT) as core::ffi::c_int
            }

            #[cfg(Py_3_14)]
            {
                ((*op).ob_refcnt.ob_refcnt >= _Py_IMMORTAL_MINIMUM_REFCNT) as core::ffi::c_int
            }
        }

        #[cfg(Py_GIL_DISABLED)]
        {
            ((*op)
                .ob_ref_local
                .load(core::sync::atomic::Ordering::Relaxed)
                == _Py_IMMORTAL_REFCNT_LOCAL) as core::ffi::c_int
        }
    }
}

#[cfg(CPython)]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyDict_New(len: isize) -> *mut pyo3_ffi::PyObject {
    unsafe {
        if len > 8 {
            _PyDict_NewPresized(len)
        } else {
            pyo3_ffi::PyDict_New()
        }
    }
}

#[cfg(not(CPython))]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyDict_New(_len: isize) -> *mut pyo3_ffi::PyObject {
    unsafe { pyo3_ffi::PyDict_New() }
}

#[cfg(all(CPython, Py_3_14))]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn Py_HashBuffer(
    ptr: *const core::ffi::c_void,
    len: pyo3_ffi::Py_ssize_t,
) -> pyo3_ffi::Py_hash_t {
    unsafe { pyo3_ffi::Py_HashBuffer(ptr, len) }
}

#[cfg(all(CPython, not(Py_3_14)))]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn Py_HashBuffer(
    ptr: *const core::ffi::c_void,
    len: pyo3_ffi::Py_ssize_t,
) -> pyo3_ffi::Py_hash_t {
    unsafe { _Py_HashBytes(ptr, len) }
}

#[cfg(not(Py_3_13))]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyLong_AsByteArray(
    v: *mut pyo3_ffi::PyLongObject,
    bytes: *mut core::ffi::c_uchar,
    n: pyo3_ffi::Py_ssize_t,
    little_endian: core::ffi::c_int,
    is_signed: core::ffi::c_int,
) -> core::ffi::c_int {
    unsafe { _PyLong_AsByteArray(v, bytes, n, little_endian, is_signed) }
}

#[cfg(Py_3_13)]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyLong_AsByteArray(
    v: *mut pyo3_ffi::PyLongObject,
    bytes: *mut core::ffi::c_uchar,
    n: pyo3_ffi::Py_ssize_t,
    little_endian: core::ffi::c_int,
    is_signed: core::ffi::c_int,
) -> core::ffi::c_int {
    unsafe { _PyLong_AsByteArray(v, bytes, n, little_endian, is_signed, 0) }
}

#[cfg(CPython)]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn Py_SIZE(op: *mut pyo3_ffi::PyObject) -> pyo3_ffi::Py_ssize_t {
    unsafe { (*op.cast::<pyo3_ffi::PyVarObject>()).ob_size }
}

#[cfg(not(CPython))]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn Py_SIZE(op: *mut pyo3_ffi::PyObject) -> pyo3_ffi::Py_ssize_t {
    unsafe { pyo3_ffi::Py_SIZE(op) }
}

#[allow(unused)]
#[cfg(any(CPython, PyPy))]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn Py_SET_SIZE(op: *mut pyo3_ffi::PyVarObject, size: pyo3_ffi::Py_ssize_t) {
    unsafe { (*op).ob_size = size }
}

#[allow(unused)]
#[cfg(GraalPy)]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn Py_SET_SIZE(op: *mut pyo3_ffi::PyVarObject, size: pyo3_ffi::Py_ssize_t) {
    unsafe { (*op)._ob_size_graalpy = size }
}

#[cfg(CPython)]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyTuple_GET_ITEM(
    op: *mut pyo3_ffi::PyObject,
    i: pyo3_ffi::Py_ssize_t,
) -> *mut pyo3_ffi::PyObject {
    unsafe {
        *(*op.cast::<pyo3_ffi::PyTupleObject>())
            .ob_item
            .as_ptr()
            .offset(i)
    }
}

#[cfg(not(CPython))]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyTuple_GET_ITEM(
    op: *mut pyo3_ffi::PyObject,
    i: pyo3_ffi::Py_ssize_t,
) -> *mut pyo3_ffi::PyObject {
    unsafe { pyo3_ffi::PyTuple_GetItem(op, i) }
}

#[cfg(CPython)]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyTuple_SET_ITEM(
    op: *mut pyo3_ffi::PyObject,
    i: pyo3_ffi::Py_ssize_t,
    v: *mut pyo3_ffi::PyObject,
) {
    unsafe {
        *(*(op.cast::<pyo3_ffi::PyTupleObject>()))
            .ob_item
            .as_mut_ptr()
            .offset(i) = v;
    }
}

#[cfg(not(CPython))]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyTuple_SET_ITEM(
    op: *mut pyo3_ffi::PyObject,
    i: pyo3_ffi::Py_ssize_t,
    v: *mut pyo3_ffi::PyObject,
) {
    unsafe {
        pyo3_ffi::PyTuple_SetItem(op, i, v);
    }
}

#[cfg(not(Py_GIL_DISABLED))]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyUnstable_Unicode_GET_CACHED_HASH(
    op: *mut pyo3_ffi::PyObject,
) -> pyo3_ffi::Py_hash_t {
    unsafe {
        let hash = (*op.cast::<pyo3_ffi::PyASCIIObject>()).hash;
        debug_assert!(hash != -1);
        hash
    }
}

#[cfg(Py_GIL_DISABLED)]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyUnstable_Unicode_GET_CACHED_HASH(
    op: *mut pyo3_ffi::PyObject,
) -> pyo3_ffi::Py_hash_t {
    unsafe {
        let hash =
            core::sync::atomic::AtomicIsize::new((*op.cast::<pyo3_ffi::PyASCIIObject>()).hash)
                .load(core::sync::atomic::Ordering::Relaxed);
        debug_assert!(hash != -1);
        hash
    }
}

#[cfg(CPython)]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyObject_Type(o: *mut pyo3_ffi::PyObject) -> *mut pyo3_ffi::PyTypeObject {
    unsafe { (*o).ob_type }
}

#[cfg(not(CPython))]
#[inline(always)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyObject_Type(o: *mut pyo3_ffi::PyObject) -> *mut pyo3_ffi::PyTypeObject {
    unsafe { pyo3_ffi::Py_TYPE(o) }
}

#[cfg(all(not(Py_GIL_DISABLED), not(Py_LIMITED_ABI)))]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyType_GetFlags(type_: *mut pyo3_ffi::PyTypeObject) -> core::ffi::c_ulong {
    unsafe { (*type_).tp_flags }
}

#[cfg(Py_GIL_DISABLED)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyType_GetFlags(type_: *mut pyo3_ffi::PyTypeObject) -> core::ffi::c_ulong {
    unsafe {
        (*type_)
            .tp_flags
            .load(core::sync::atomic::Ordering::Relaxed)
    }
}

#[cfg(Py_LIMITED_ABI)]
#[allow(non_snake_case)]
pub(crate) unsafe fn PyType_GetFlags(type_: *mut pyo3_ffi::PyTypeObject) -> core::ffi::c_ulong {
    unsafe { pyo3_ffi::PyType_GetFlags(type_) }
}

unsafe extern "C" {
    #[cfg(CPython)]
    pub fn _PyBytes_Resize(
        pv: *mut *mut pyo3_ffi::PyObject,
        newsize: pyo3_ffi::Py_ssize_t,
    ) -> core::ffi::c_int;

    #[cfg(CPython)]
    pub fn _PyDict_NewPresized(minused: pyo3_ffi::Py_ssize_t) -> *mut pyo3_ffi::PyObject;

    #[cfg(CPython)]
    pub fn _PyDict_Contains_KnownHash(
        op: *mut pyo3_ffi::PyObject,
        key: *mut pyo3_ffi::PyObject,
        hash: pyo3_ffi::Py_hash_t,
    ) -> core::ffi::c_int;

    #[cfg(all(CPython, not(Py_3_13)))]
    pub(crate) fn _PyDict_SetItem_KnownHash(
        mp: *mut pyo3_ffi::PyObject,
        name: *mut pyo3_ffi::PyObject,
        value: *mut pyo3_ffi::PyObject,
        hash: pyo3_ffi::Py_hash_t,
    ) -> core::ffi::c_int;

    #[cfg(all(CPython, Py_3_13))]
    pub(crate) fn _PyDict_SetItem_KnownHash_LockHeld(
        mp: *mut pyo3_ffi::PyDictObject,
        name: *mut pyo3_ffi::PyObject,
        value: *mut pyo3_ffi::PyObject,
        hash: pyo3_ffi::Py_hash_t,
    ) -> core::ffi::c_int;

    #[cfg(Py_3_13)]
    #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")]
    pub(crate) fn _PyLong_AsByteArray(
        v: *mut pyo3_ffi::PyLongObject,
        bytes: *mut core::ffi::c_uchar,
        n: pyo3_ffi::Py_ssize_t,
        little_endian: core::ffi::c_int,
        is_signed: core::ffi::c_int,
        with_exceptions: core::ffi::c_int,
    ) -> core::ffi::c_int;

    #[cfg(not(Py_3_13))]
    #[cfg_attr(PyPy, link_name = "_PyPyLong_AsByteArrayO")]
    pub(crate) fn _PyLong_AsByteArray(
        v: *mut pyo3_ffi::PyLongObject,
        bytes: *mut core::ffi::c_uchar,
        n: pyo3_ffi::Py_ssize_t,
        little_endian: core::ffi::c_int,
        is_signed: core::ffi::c_int,
    ) -> core::ffi::c_int;

    #[cfg(CPython)]
    pub(crate) fn _PyUnicode_CheckConsistency(
        op: *mut pyo3_ffi::PyObject,
        check_context: core::ffi::c_int,
    ) -> core::ffi::c_int;

    #[cfg(not(Py_3_14))]
    pub(crate) fn _Py_HashBytes(
        ptr: *const core::ffi::c_void,
        len: pyo3_ffi::Py_ssize_t,
    ) -> pyo3_ffi::Py_hash_t;
}
