// TODO https://github.com/PyO3/pyo3/issues/5487
#![allow(clippy::undocumented_unsafe_blocks)]

use crate::exceptions::PyStopAsyncIteration;
use crate::impl_::callback::IntoPyCallbackOutput;
use crate::impl_::panic::PanicTrap;
use crate::impl_::pycell::PyClassObjectBaseLayout;
use crate::impl_::pyclass::PyClassDict as _;
use crate::internal::get_slot::{get_slot, TP_BASE, TP_CLEAR, TP_TRAVERSE};
use crate::internal::pyclass_init::PyClassInit;
use crate::internal::state::ForbidAttaching;
use crate::pycell::impl_::{PyClassBorrowChecker as _, PyClassObjectLayout};
use crate::types::PyType;
use crate::{
    ffi, Borrowed, Bound, Py, PyAny, PyClass, PyErr, PyResult, PyTraverseError, PyVisit, Python,
};
use core::ffi::CStr;
use core::ffi::{c_int, c_void};
use core::fmt;
use core::marker::PhantomData;
use core::panic::AssertUnwindSafe;
use core::ptr::{null_mut, NonNull};
use std::panic::catch_unwind;

use super::pyclass::PyClassImpl;
use super::trampoline;
use crate::internal_tricks::{clear_eq, traverse_eq};

/// `PyMethodDefType` represents different types of Python callable objects.
/// It is used by the `#[pymethods]` attribute.
#[derive(Copy, Clone)]
pub enum PyMethodDefType {
    /// Represents a class method (might be `classmethod` or `staticmethod`, depends on `ml_flags`)
    Method(PyMethodDef),
    /// Represents class attribute, used by `#[attribute]`
    ClassAttribute(PyClassAttributeDef),
    /// Represents getter descriptor, used by `#[getter]`
    Getter(PyGetterDef),
    /// Represents setter descriptor, used by `#[setter]` and `#[deleter]`
    Setter(PySetterDef),
    /// Represents deleter descriptor, used by `#[deleter]`
    Deleter(PyDeleterDef),
    /// Represents a struct member
    StructMember(ffi::PyMemberDef),
}

#[derive(Copy, Clone, Debug)]
pub enum PyMethodType {
    PyCFunction(ffi::PyCFunction),
    PyCFunctionWithKeywords(ffi::PyCFunctionWithKeywords),
    #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
    PyCFunctionFastWithKeywords(ffi::PyCFunctionFastWithKeywords),
}

pub type PyClassAttributeFactory = for<'p> fn(Python<'p>) -> PyResult<Py<PyAny>>;

#[derive(Copy, Clone, Debug)]
pub struct PyMethodDef {
    pub(crate) ml_name: &'static CStr,
    pub(crate) ml_meth: PyMethodType,
    pub(crate) ml_flags: c_int,
    pub(crate) ml_doc: &'static CStr,
}

#[derive(Copy, Clone)]
pub struct PyClassAttributeDef {
    pub(crate) name: &'static CStr,
    pub(crate) meth: PyClassAttributeFactory,
}

#[derive(Copy, Clone)]
pub struct PyGetterDef {
    pub(crate) name: &'static CStr,
    pub(crate) meth: Getter,
    pub(crate) doc: Option<&'static CStr>,
}

#[derive(Copy, Clone)]
pub struct PySetterDef {
    pub(crate) name: &'static CStr,
    pub(crate) meth: Setter,
    pub(crate) doc: Option<&'static CStr>,
}

#[derive(Copy, Clone)]
pub struct PyDeleterDef {
    pub(crate) name: &'static CStr,
    pub(crate) meth: Deleter,
    pub(crate) doc: Option<&'static CStr>,
}

/// Abstraction around fastcall calling convention, which is only available in Python 3.10 and up,
/// can inline this directly into the proc macro when Python 3.10 support dropped
#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
#[macro_export]
#[doc(hidden)]
macro_rules! maybe_define_fastcall_function_with_keywords {
    ($name:ident, $py:ident, $slf:ident, $args:ident, $nargs:ident, $kwargs:ident, $body:block) => {
        #[allow(non_snake_case)]
        unsafe fn $name<'py>(
            $py: $crate::Python<'py>,
            $slf: *mut $crate::ffi::PyObject,
            $args: *const *mut $crate::ffi::PyObject,
            $nargs: $crate::ffi::Py_ssize_t,
            $kwargs: *mut $crate::ffi::PyObject
        ) -> $crate::PyResult<*mut $crate::ffi::PyObject> $body
    };
}

/// On older abi3 versions, required to use varargs calling convention
#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
#[macro_export]
#[doc(hidden)]
macro_rules! maybe_define_fastcall_function_with_keywords {
    ($name:ident, $py:ident, $slf:ident, $args:ident, $nargs:ident, $kwargs:ident, $body:block) => {
        #[allow(non_snake_case)]
        unsafe fn $name<'py>(
            $py: $crate::Python<'py>,
            $slf: *mut $crate::ffi::PyObject,
            $args: *mut $crate::ffi::PyObject,
            $kwargs: *mut $crate::ffi::PyObject
        ) -> $crate::PyResult<*mut $crate::ffi::PyObject> $body
    };
}

pub use crate::maybe_define_fastcall_function_with_keywords;

#[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
#[macro_export]
#[doc(hidden)]
macro_rules! maybe_extract_arguments_fastcall {
    ($description:ident, $py:ident, $args:ident, $nargs:ident, $kwargs:ident, $args_array:ident, $args_handler:ty, $kwargs_handler:ty) => {
        // SAFETY: guaranteed by the proc macro that all args to extract_arguments_fastcall are valid
        unsafe {
            $description.extract_arguments_fastcall::<$args_handler, $kwargs_handler>(
                $py,
                $args,
                $nargs,
                $kwargs,
                &mut $args_array,
            )
        }
    };
}

#[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
#[macro_export]
#[doc(hidden)]
macro_rules! maybe_extract_arguments_fastcall {
    ($description:ident, $py:ident, $args:ident, $nargs:ident, $kwargs:ident, $args_array:ident, $args_handler:ty, $kwargs_handler:ty) => {
        // SAFETY: guaranteed by the proc macro that all args to extract_arguments_tuple_dict are valid
        unsafe {
            $description.extract_arguments_tuple_dict::<$args_handler, $kwargs_handler>(
                $py,
                $args,
                $kwargs,
                &mut $args_array,
            )
        }
    };
}

pub use crate::maybe_extract_arguments_fastcall;

impl PyMethodDef {
    /// Define a function that takes no arguments.
    pub const fn noargs(
        ml_name: &'static CStr,
        cfunction: ffi::PyCFunction,
        ml_doc: &'static CStr,
    ) -> Self {
        Self {
            ml_name,
            ml_meth: PyMethodType::PyCFunction(cfunction),
            ml_flags: ffi::METH_NOARGS,
            ml_doc,
        }
    }

    /// Define a function that takes arbitrary arguments as a tuple and dict.
    pub const fn cfunction_with_keywords(
        ml_name: &'static CStr,
        cfunction: ffi::PyCFunctionWithKeywords,
        ml_doc: &'static CStr,
    ) -> Self {
        Self {
            ml_name,
            ml_meth: PyMethodType::PyCFunctionWithKeywords(cfunction),
            ml_flags: ffi::METH_VARARGS | ffi::METH_KEYWORDS,
            ml_doc,
        }
    }

    /// Define a function that takes arbitrary arguments as a C-style array and tuple of keyword arguments.
    #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
    pub const fn fastcall_cfunction_with_keywords(
        ml_name: &'static CStr,
        cfunction: ffi::PyCFunctionFastWithKeywords,
        ml_doc: &'static CStr,
    ) -> Self {
        Self {
            ml_name,
            ml_meth: PyMethodType::PyCFunctionFastWithKeywords(cfunction),
            ml_flags: ffi::METH_FASTCALL | ffi::METH_KEYWORDS,
            ml_doc,
        }
    }

    /// Abstraction over fastcall to fall back to varargs on older Python versions.
    pub const fn maybe_fastcall_cfunction_with_keywords(
        ml_name: &'static CStr,
        #[cfg(any(Py_3_10, not(Py_LIMITED_API)))] cfunction: ffi::PyCFunctionFastWithKeywords,
        // on older abi3 versions, Fastcall not supported
        #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))] cfunction: ffi::PyCFunctionWithKeywords,
        ml_doc: &'static CStr,
    ) -> Self {
        #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
        {
            Self::fastcall_cfunction_with_keywords(ml_name, cfunction, ml_doc)
        }

        #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))]
        {
            Self::cfunction_with_keywords(ml_name, cfunction, ml_doc)
        }
    }

    pub const fn flags(mut self, flags: c_int) -> Self {
        self.ml_flags |= flags;
        self
    }

    pub const fn into_raw(self) -> ffi::PyMethodDef {
        let meth = match self.ml_meth {
            PyMethodType::PyCFunction(meth) => ffi::PyMethodDefPointer { PyCFunction: meth },
            PyMethodType::PyCFunctionWithKeywords(meth) => ffi::PyMethodDefPointer {
                PyCFunctionWithKeywords: meth,
            },
            #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
            PyMethodType::PyCFunctionFastWithKeywords(meth) => ffi::PyMethodDefPointer {
                PyCFunctionFastWithKeywords: meth,
            },
        };

        ffi::PyMethodDef {
            ml_name: self.ml_name.as_ptr(),
            ml_meth: meth,
            ml_flags: self.ml_flags,
            ml_doc: self.ml_doc.as_ptr(),
        }
    }
}

impl PyClassAttributeDef {
    /// Define a class attribute.
    pub const fn new(name: &'static CStr, meth: PyClassAttributeFactory) -> Self {
        Self { name, meth }
    }
}

// Manual implementation because `Python<'_>` does not implement `Debug` and
// trait bounds on `fn` compiler-generated derive impls are too restrictive.
impl fmt::Debug for PyClassAttributeDef {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("PyClassAttributeDef")
            .field("name", &self.name)
            .finish()
    }
}

/// Class getter / setters
pub(crate) type Getter =
    for<'py> unsafe fn(Python<'py>, NonNull<ffi::PyObject>) -> PyResult<*mut ffi::PyObject>;
pub(crate) type Setter = for<'py> unsafe fn(
    Python<'py>,
    NonNull<ffi::PyObject>,
    NonNull<ffi::PyObject>,
) -> PyResult<c_int>;
pub(crate) type Deleter =
    for<'py> unsafe fn(Python<'py>, NonNull<ffi::PyObject>) -> PyResult<c_int>;

impl PyGetterDef {
    /// Define a getter.
    pub const fn new(name: &'static CStr, getter: Getter, doc: Option<&'static CStr>) -> Self {
        Self {
            name,
            meth: getter,
            doc,
        }
    }
}

impl PySetterDef {
    /// Define a setter.
    pub const fn new(name: &'static CStr, setter: Setter, doc: Option<&'static CStr>) -> Self {
        Self {
            name,
            meth: setter,
            doc,
        }
    }
}

impl PyDeleterDef {
    /// Define a deleter.
    pub const fn new(name: &'static CStr, deleter: Deleter, doc: Option<&'static CStr>) -> Self {
        Self {
            name,
            meth: deleter,
            doc,
        }
    }
}

/// Calls an implementation of __traverse__ for tp_traverse
///
/// NB cannot accept `'static` visitor, this is a sanity check below:
///
/// ```rust,compile_fail
/// use pyo3::prelude::*;
/// use pyo3::pyclass::{PyTraverseError, PyVisit};
///
/// #[pyclass]
/// struct Foo;
///
/// #[pymethods]
/// impl Foo {
///     fn __traverse__(&self, _visit: PyVisit<'static>) -> Result<(), PyTraverseError> {
///         Ok(())
///     }
/// }
/// ```
///
/// Elided lifetime should compile ok:
///
/// ```rust,no_run
/// use pyo3::prelude::*;
/// use pyo3::pyclass::{PyTraverseError, PyVisit};
///
/// #[pyclass]
/// struct Foo;
///
/// #[pymethods]
/// impl Foo {
///     fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> {
///         Ok(())
///     }
/// }
/// ```
#[doc(hidden)]
pub unsafe fn _call_traverse<T>(
    slf: *mut ffi::PyObject,
    impl_: fn(&T, PyVisit<'_>) -> Result<(), PyTraverseError>,
    visit: ffi::visitproc,
    arg: *mut c_void,
    current_traverse: ffi::traverseproc,
) -> c_int
where
    T: PyClass,
{
    // It is important the implementation of `__traverse__` cannot safely access the interpreter,
    // c.f. https://github.com/PyO3/pyo3/issues/3165, and hence we do not expose our Python
    // token to the user code and forbid safe methods for attaching.
    // (This includes enforcing the `&self` method receiver as e.g. `PyRef<Self>` could
    // reconstruct a Python token via `PyRef::py`.)
    //
    // The traversal lives in `traverse_impl` so that it can return early: `trap` is armed until
    // `disarm` below, and dropping it armed panics, which aborts out of `tp_traverse`.
    let trap = PanicTrap::new("uncaught panic inside __traverse__ handler");
    let lock = ForbidAttaching::during_traverse();

    let retval = unsafe { traverse_impl(slf, impl_, visit, arg, current_traverse) };

    // Drop lock before trap just in case dropping lock panics
    drop(lock);
    trap.disarm();
    retval
}

/// Visits the base type, the instance `__dict__` and the pyclass's own data, stopping as soon as
/// one of them returns non-zero.
///
/// # Safety
/// - `slf` must be a valid pointer to an instance of `T`.
/// - Must only be called from `_call_traverse`, which holds the `PanicTrap` and `ForbidAttaching`
///   lock this relies on.
unsafe fn traverse_impl<T>(
    slf: *mut ffi::PyObject,
    impl_: fn(&T, PyVisit<'_>) -> Result<(), PyTraverseError>,
    visit: ffi::visitproc,
    arg: *mut c_void,
    current_traverse: ffi::traverseproc,
) -> c_int
where
    T: PyClass,
{
    // A non-zero return means a `visitproc` has asked us to stop the traversal.
    let super_retval = unsafe { call_super_traverse(slf, visit, arg, current_traverse) };
    if super_retval != 0 {
        return super_retval;
    }

    // SAFETY: `slf` is a valid Python object pointer to a class object of type T, and
    // traversal is running so no mutations can occur.
    let class_object: &<T as PyClassImpl>::Layout = unsafe { &*slf.cast() };

    // The `__dict__` is not Rust data, so it is visited without the thread and borrow checks
    // below: it must stay reachable to the GC even when the pyclass data cannot be traversed.
    let dict_retval = unsafe { class_object.contents().dict.traverse_dict(visit, arg) };
    if dict_retval != 0 {
        return dict_retval;
    }

    // `#[pyclass(unsendable)]` types can only be deallocated by their own thread, so do not
    // traverse them if not on their owning thread :(
    // ... and we cannot traverse a type which might be being mutated by a Rust thread.
    if class_object.check_threadsafe().is_err()
        || class_object.borrow_checker().try_borrow().is_err()
    {
        return 0;
    }

    struct TraverseGuard<'a, T: PyClassImpl>(&'a T::Layout);
    impl<T: PyClassImpl> Drop for TraverseGuard<'_, T> {
        fn drop(&mut self) {
            self.0.borrow_checker().release_borrow()
        }
    }

    // `.try_borrow()` above created a borrow, we need to release it when we're done
    // traversing the object. This allows us to read `instance` safely.
    let _guard = TraverseGuard::<T>(class_object);
    let instance = unsafe { &*class_object.contents().value.get() };

    let visit = PyVisit {
        visit,
        arg,
        _guard: PhantomData,
    };

    match catch_unwind(AssertUnwindSafe(move || impl_(instance, visit))) {
        Ok(Ok(())) => 0,
        Ok(Err(traverse_error)) => traverse_error.into_inner(),
        Err(_err) => -1,
    }
}

/// Call super-type traverse method, if necessary.
///
/// Adapted from <https://github.com/cython/cython/blob/7acfb375fb54a033f021b0982a3cd40c34fb22ac/Cython/Utility/ExtensionTypes.c#L386>
///
/// TODO: There are possible optimizations over looking up the base type in this way
/// - if the base type is known in this module, can potentially look it up directly in module state
///   (when we have it)
/// - if the base type is a Python builtin, can jut call the C function directly
/// - if the base type is a PyO3 type defined in the same module, can potentially do similar to
///   tp_alloc where we solve this at compile time
unsafe fn call_super_traverse(
    obj: *mut ffi::PyObject,
    visit: ffi::visitproc,
    arg: *mut c_void,
    current_traverse: ffi::traverseproc,
) -> c_int {
    // SAFETY: in this function here it's ok to work with raw type objects `ffi::Py_TYPE`
    // because the GC is running and so
    // - (a) we cannot do refcounting and
    // - (b) the type of the object cannot change.
    let mut ty = unsafe { ffi::Py_TYPE(obj) };
    let mut traverse: Option<ffi::traverseproc>;

    // First find the current type by the current_traverse function
    loop {
        traverse = unsafe { get_slot(ty, TP_TRAVERSE) };
        if traverse_eq(traverse, current_traverse) {
            break;
        }
        ty = unsafe { get_slot(ty, TP_BASE) };
        if ty.is_null() {
            // FIXME: return an error if current type not in the MRO? Should be impossible.
            return 0;
        }
    }

    // Get first base which has a different traverse function
    while traverse_eq(traverse, current_traverse) {
        ty = unsafe { get_slot(ty, TP_BASE) };
        if ty.is_null() {
            break;
        }
        traverse = unsafe { get_slot(ty, TP_TRAVERSE) };
    }

    // If we found a type with a different traverse function, call it
    if let Some(traverse) = traverse {
        return unsafe { traverse(obj, visit, arg) };
    }

    // FIXME same question as cython: what if the current type is not in the MRO?
    0
}

/// Calls an implementation of __clear__ for tp_clear
pub unsafe fn _call_clear<T>(
    slf: *mut ffi::PyObject,
    impl_: for<'py> unsafe fn(Python<'py>, *mut ffi::PyObject) -> PyResult<()>,
    current_clear: ffi::inquiry,
) -> c_int
where
    T: PyClass,
{
    unsafe {
        trampoline::trampoline(move |py| {
            let super_retval = call_super_clear(py, slf, current_clear);
            if super_retval != 0 {
                return Err(PyErr::fetch(py));
            }
            impl_(py, slf)?;

            // Clear the `__dict__`, breaking any reference cycle through the instance's
            // attributes.
            //
            // SAFETY: `slf` is a valid instance of `T`. A shared reference suffices: clearing
            // the `__dict__` never touches the pyclass data, so needs no borrow check.
            let class_object: &<T as PyClassImpl>::Layout = &*slf.cast();
            class_object.contents().dict.clear_dict(py);

            Ok(0)
        })
    }
}

/// `tp_traverse` for a `#[pyclass]` which defines no `__traverse__` of its own: visits the
/// base type and the instance `__dict__` (if it is a `#[pyclass(dict)]`).
pub unsafe extern "C" fn synthesized_traverse<T>(
    slf: *mut ffi::PyObject,
    visit: ffi::visitproc,
    arg: *mut c_void,
) -> c_int
where
    T: PyClass,
{
    let super_retval = unsafe { call_super_traverse(slf, visit, arg, synthesized_traverse::<T>) };
    if super_retval != 0 {
        return super_retval;
    }

    // SAFETY: `slf` is a valid pointer to an instance of `T`, and traversal is running so no
    // mutations can occur. The `__dict__` is not Rust data, so needs no thread or borrow check.
    let class_object: &<T as PyClassImpl>::Layout = unsafe { &*slf.cast() };
    unsafe { class_object.contents().dict.traverse_dict(visit, arg) }
}

/// `tp_clear` for a `#[pyclass]` which defines no `__clear__` of its own: calls the base type
/// and clears the instance `__dict__` (if it is a `#[pyclass(dict)]`).
pub unsafe extern "C" fn synthesized_clear<T>(slf: *mut ffi::PyObject) -> c_int
where
    T: PyClass,
{
    unsafe { _call_clear::<T>(slf, |_, _| Ok(()), synthesized_clear::<T>) }
}

/// Call super-type traverse method, if necessary.
///
/// Adapted from <https://github.com/cython/cython/blob/7acfb375fb54a033f021b0982a3cd40c34fb22ac/Cython/Utility/ExtensionTypes.c#L386>
///
/// TODO: There are possible optimizations over looking up the base type in this way
/// - if the base type is known in this module, can potentially look it up directly in module state
///   (when we have it)
/// - if the base type is a Python builtin, can jut call the C function directly
/// - if the base type is a PyO3 type defined in the same module, can potentially do similar to
///   tp_alloc where we solve this at compile time
unsafe fn call_super_clear(
    py: Python<'_>,
    obj: *mut ffi::PyObject,
    current_clear: ffi::inquiry,
) -> c_int {
    let mut ty = unsafe { PyType::from_borrowed_type_ptr(py, ffi::Py_TYPE(obj)) };
    let mut clear: Option<ffi::inquiry>;

    // First find the current type by the current_clear function
    loop {
        clear = ty.get_slot(TP_CLEAR);
        if clear_eq(clear, current_clear) {
            break;
        }
        let base = ty.get_slot(TP_BASE);
        if base.is_null() {
            // FIXME: return an error if current type not in the MRO? Should be impossible.
            return 0;
        }
        ty = unsafe { PyType::from_borrowed_type_ptr(py, base) };
    }

    // Get first base which has a different clear function
    while clear_eq(clear, current_clear) {
        let base = ty.get_slot(TP_BASE);
        if base.is_null() {
            break;
        }
        ty = unsafe { PyType::from_borrowed_type_ptr(py, base) };
        clear = ty.get_slot(TP_CLEAR);
    }

    // If we found a type with a different clear function, call it
    if let Some(clear) = clear {
        return unsafe { clear(obj) };
    }

    // FIXME same question as cython: what if the current type is not in the MRO?
    0
}

// Autoref-based specialization for handling `__next__` returning `Option`

pub struct IterBaseTag;

impl IterBaseTag {
    #[inline]
    pub fn convert<'py, Value, Target>(self, py: Python<'py>, value: Value) -> PyResult<Target>
    where
        Value: IntoPyCallbackOutput<'py, Target>,
    {
        value.convert(py)
    }
}

pub trait IterBaseKind {
    #[inline]
    fn iter_tag(&self) -> IterBaseTag {
        IterBaseTag
    }
}

impl<Value> IterBaseKind for &Value {}

pub struct IterOptionTag;

impl IterOptionTag {
    #[inline]
    pub fn convert<'py, Value>(
        self,
        py: Python<'py>,
        value: Option<Value>,
    ) -> PyResult<*mut ffi::PyObject>
    where
        Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
    {
        match value {
            Some(value) => value.convert(py),
            None => Ok(null_mut()),
        }
    }
}

pub trait IterOptionKind {
    #[inline]
    fn iter_tag(&self) -> IterOptionTag {
        IterOptionTag
    }
}

impl<Value> IterOptionKind for Option<Value> {}

pub struct IterResultOptionTag;

impl IterResultOptionTag {
    #[inline]
    pub fn convert<'py, Value, Error>(
        self,
        py: Python<'py>,
        value: Result<Option<Value>, Error>,
    ) -> PyResult<*mut ffi::PyObject>
    where
        Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
        Error: Into<PyErr>,
    {
        match value {
            Ok(Some(value)) => value.convert(py),
            Ok(None) => Ok(null_mut()),
            Err(err) => Err(err.into()),
        }
    }
}

pub trait IterResultOptionKind {
    #[inline]
    fn iter_tag(&self) -> IterResultOptionTag {
        IterResultOptionTag
    }
}

impl<Value, Error> IterResultOptionKind for Result<Option<Value>, Error> {}

// Autoref-based specialization for handling `__anext__` returning `Option`

pub struct AsyncIterBaseTag;

impl AsyncIterBaseTag {
    #[inline]
    pub fn convert<'py, Value, Target>(self, py: Python<'py>, value: Value) -> PyResult<Target>
    where
        Value: IntoPyCallbackOutput<'py, Target>,
    {
        value.convert(py)
    }
}

pub trait AsyncIterBaseKind {
    #[inline]
    fn async_iter_tag(&self) -> AsyncIterBaseTag {
        AsyncIterBaseTag
    }
}

impl<Value> AsyncIterBaseKind for &Value {}

pub struct AsyncIterOptionTag;

impl AsyncIterOptionTag {
    #[inline]
    pub fn convert<'py, Value>(
        self,
        py: Python<'py>,
        value: Option<Value>,
    ) -> PyResult<*mut ffi::PyObject>
    where
        Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
    {
        match value {
            Some(value) => value.convert(py),
            None => Err(PyStopAsyncIteration::new_err(())),
        }
    }
}

pub trait AsyncIterOptionKind {
    #[inline]
    fn async_iter_tag(&self) -> AsyncIterOptionTag {
        AsyncIterOptionTag
    }
}

impl<Value> AsyncIterOptionKind for Option<Value> {}

pub struct AsyncIterResultOptionTag;

impl AsyncIterResultOptionTag {
    #[inline]
    pub fn convert<'py, Value, Error>(
        self,
        py: Python<'py>,
        value: Result<Option<Value>, Error>,
    ) -> PyResult<*mut ffi::PyObject>
    where
        Value: IntoPyCallbackOutput<'py, *mut ffi::PyObject>,
        Error: Into<PyErr>,
    {
        match value {
            Ok(Some(value)) => value.convert(py),
            Ok(None) => Err(PyStopAsyncIteration::new_err(())),
            Err(err) => Err(err.into()),
        }
    }
}

pub trait AsyncIterResultOptionKind {
    #[inline]
    fn async_iter_tag(&self) -> AsyncIterResultOptionTag {
        AsyncIterResultOptionTag
    }
}

impl<Value, Error> AsyncIterResultOptionKind for Result<Option<Value>, Error> {}

/// Re-exported so that `#[new]` generated code can resolve the type tag for `tp_new_impl`
pub use crate::internal::pyclass_init::tp_new_resolver;

#[expect(
    private_bounds,
    reason = "`PyClassInit` is not a public trait, bound exist for diagnostics"
)]
/// # SAFETY
/// - `cls` must be the type object for `ClassT` (or a subclass)
pub unsafe fn tp_new_impl<'py, InitializerT, ClassT>(
    py: Python<'py>,
    initializer: InitializerT,
    cls: *mut ffi::PyTypeObject,
) -> PyResult<*mut ffi::PyObject>
where
    InitializerT: PyClassInit<'py, ClassT>,
{
    // SAFETY: caller has guaranteed `cls` is the correct object
    unsafe { initializer.init(Borrowed::from_ptr_unchecked(py, cls.cast()).cast_unchecked()) }
        .map(Bound::into_ptr)
}

#[cfg(test)]
mod tests {
    #[allow(unused_imports, reason = "conditionally used")]
    use crate::platform::prelude::*;

    #[test]
    #[cfg(any(Py_3_10, not(Py_LIMITED_API)))]
    fn test_fastcall_function_with_keywords() {
        use super::PyMethodDef;
        use crate::impl_::pyfunction::PyFunctionDef;
        use crate::types::PyAnyMethods;
        use crate::{ffi, Python};

        Python::attach(|py| {
            let def =
                PyFunctionDef::from_method_def(PyMethodDef::fastcall_cfunction_with_keywords(
                    c"test",
                    accepts_no_arguments,
                    c"doc",
                ));
            // leak to make it 'static
            // deliberately done at runtime to have coverage of `PyFunctionDef::from_method_def`
            let def = Box::leak(Box::new(def));

            unsafe extern "C" fn accepts_no_arguments(
                _slf: *mut ffi::PyObject,
                _args: *const *mut ffi::PyObject,
                nargs: ffi::Py_ssize_t,
                kwargs: *mut ffi::PyObject,
            ) -> *mut ffi::PyObject {
                assert_eq!(nargs, 0);
                assert!(kwargs.is_null());
                unsafe { Python::assume_attached().None().into_ptr() }
            }

            let f = def.create_py_c_function(py, None).unwrap();

            f.call0().unwrap();
        });
    }
}
