use crate::common::lock::{
    PyMappedRwLockReadGuard, PyMappedRwLockWriteGuard, PyRwLockReadGuard, PyRwLockWriteGuard,
};
use crate::{
    AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
    builtins::{PyInt, PyStr, PyStrInterned, PyType, PyTypeRef},
    bytecode::ComparisonOperator,
    common::hash::{PyHash, fix_sentinel, hash_bigint},
    convert::ToPyObject,
    function::{Either, FromArgs, FuncArgs, PyComparisonValue, PyMethodDef, PySetterValue},
    protocol::{
        PyBuffer, PyIterReturn, PyMapping, PyMappingMethods, PyMappingSlots, PyNumber,
        PyNumberMethods, PyNumberSlots, PySequence, PySequenceMethods, PySequenceSlots,
    },
    types::slot_defs::{SlotAccessor, find_slot_defs_by_name},
    vm::Context,
};
use core::{any::Any, any::TypeId, borrow::Borrow, cmp::Ordering, ops::Deref};
use crossbeam_utils::atomic::AtomicCell;
use num_traits::{Signed, ToPrimitive};
use rustpython_common::wtf8::Wtf8Buf;

/// Type-erased storage for extension module data attached to heap types.
pub struct TypeDataSlot {
    // PyObject_GetTypeData
    type_id: TypeId,
    data: Box<dyn Any + Send + Sync>,
}

impl TypeDataSlot {
    /// Create a new type data slot with the given data.
    pub fn new<T: Any + Send + Sync + 'static>(data: T) -> Self {
        Self {
            type_id: TypeId::of::<T>(),
            data: Box::new(data),
        }
    }

    /// Get a reference to the data if the type matches.
    pub fn get<T: Any + 'static>(&self) -> Option<&T> {
        if self.type_id == TypeId::of::<T>() {
            self.data.downcast_ref()
        } else {
            None
        }
    }

    /// Get a mutable reference to the data if the type matches.
    pub fn get_mut<T: Any + 'static>(&mut self) -> Option<&mut T> {
        if self.type_id == TypeId::of::<T>() {
            self.data.downcast_mut()
        } else {
            None
        }
    }
}

/// Read guard for type data access, using mapped guard for zero-cost deref.
pub struct TypeDataRef<'a, T: 'static> {
    guard: PyMappedRwLockReadGuard<'a, T>,
}

impl<'a, T: Any + 'static> TypeDataRef<'a, T> {
    /// Try to create a TypeDataRef from a read guard.
    /// Returns None if the slot is empty or contains a different type.
    pub fn try_new(guard: PyRwLockReadGuard<'a, Option<TypeDataSlot>>) -> Option<Self> {
        PyRwLockReadGuard::try_map(guard, |opt| opt.as_ref().and_then(|slot| slot.get::<T>()))
            .ok()
            .map(|guard| Self { guard })
    }
}

impl<T: Any + 'static> core::ops::Deref for TypeDataRef<'_, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.guard
    }
}

/// Write guard for type data access, using mapped guard for zero-cost deref.
pub struct TypeDataRefMut<'a, T: 'static> {
    guard: PyMappedRwLockWriteGuard<'a, T>,
}

impl<'a, T: Any + 'static> TypeDataRefMut<'a, T> {
    /// Try to create a TypeDataRefMut from a write guard.
    /// Returns None if the slot is empty or contains a different type.
    pub fn try_new(guard: PyRwLockWriteGuard<'a, Option<TypeDataSlot>>) -> Option<Self> {
        PyRwLockWriteGuard::try_map(guard, |opt| {
            opt.as_mut().and_then(|slot| slot.get_mut::<T>())
        })
        .ok()
        .map(|guard| Self { guard })
    }
}

impl<T: Any + 'static> core::ops::Deref for TypeDataRefMut<'_, T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.guard
    }
}

impl<T: Any + 'static> core::ops::DerefMut for TypeDataRefMut<'_, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.guard
    }
}

#[macro_export]
macro_rules! atomic_func {
    ($x:expr) => {
        Some($x)
    };
}

// The corresponding field in CPython is `tp_` prefixed.
// e.g. name -> tp_name
#[derive(Default)]
#[non_exhaustive]
pub struct PyTypeSlots {
    /// # Safety
    /// For static types, always safe.
    /// For heap types, `__name__` must alive
    pub(crate) name: &'static str, // tp_name with <module>.<class> for print, not class name

    pub basicsize: usize,
    pub itemsize: usize, // tp_itemsize

    // Methods to implement standard operations

    // Method suites for standard classes
    pub as_number: PyNumberSlots,
    pub as_sequence: PySequenceSlots,
    pub as_mapping: PyMappingSlots,

    // More standard operations (here for binary compatibility)
    pub hash: AtomicCell<Option<HashFunc>>,
    pub call: AtomicCell<Option<GenericMethod>>,
    pub vectorcall: AtomicCell<Option<VectorCallFunc>>,
    pub str: AtomicCell<Option<StringifyFunc>>,
    pub repr: AtomicCell<Option<StringifyFunc>>,
    pub getattro: AtomicCell<Option<GetattroFunc>>,
    pub setattro: AtomicCell<Option<SetattroFunc>>,

    // Functions to access object as input/output buffer
    pub as_buffer: Option<AsBufferFunc>,

    // Assigned meaning in release 2.1
    // rich comparisons
    pub richcompare: AtomicCell<Option<RichCompareFunc>>,

    // Iterators
    pub iter: AtomicCell<Option<IterFunc>>,
    pub iternext: AtomicCell<Option<IterNextFunc>>,

    pub methods: &'static [PyMethodDef],

    // Flags to define presence of optional/expanded features
    pub flags: PyTypeFlags,

    // tp_doc
    pub doc: Option<&'static str>,

    // Strong reference on a heap type, borrowed reference on a static type
    // tp_base
    // tp_dict
    pub descr_get: AtomicCell<Option<DescrGetFunc>>,
    pub descr_set: AtomicCell<Option<DescrSetFunc>>,
    // tp_dictoffset
    pub init: AtomicCell<Option<InitFunc>>,
    // tp_alloc
    pub alloc: AtomicCell<Option<AllocFunc>>,
    pub new: AtomicCell<Option<NewFunc>>,
    // tp_free
    // tp_is_gc
    // tp_bases
    // tp_mro
    // tp_cache
    // tp_subclasses
    // tp_weaklist
    pub del: AtomicCell<Option<DelFunc>>,

    // The count of tp_members.
    pub member_count: usize,
}

impl PyTypeSlots {
    pub fn new(name: &'static str, flags: PyTypeFlags) -> Self {
        Self {
            name,
            flags,
            ..Default::default()
        }
    }

    pub fn heap_default() -> Self {
        Self {
            // init: AtomicCell::new(Some(init_wrapper)),
            ..Default::default()
        }
    }
}

impl core::fmt::Debug for PyTypeSlots {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str("PyTypeSlots")
    }
}

bitflags! {
    #[derive(Copy, Clone, Debug, PartialEq)]
    #[non_exhaustive]
    pub struct PyTypeFlags: u64 {
        const MANAGED_WEAKREF = 1 << 3;
        const MANAGED_DICT = 1 << 4;
        const SEQUENCE = 1 << 5;
        const MAPPING = 1 << 6;
        const DISALLOW_INSTANTIATION = 1 << 7;
        const IMMUTABLETYPE = 1 << 8;
        const HEAPTYPE = 1 << 9;
        const BASETYPE = 1 << 10;
        const METHOD_DESCRIPTOR = 1 << 17;
        // For built-in types that match the subject itself in pattern matching
        // (bool, int, float, str, bytes, bytearray, list, tuple, dict, set, frozenset)
        // This is not a stable API
        const _MATCH_SELF = 1 << 22;
        const HAS_DICT = 1 << 40;
        const HAS_WEAKREF = 1 << 41;

        #[cfg(debug_assertions)]
        const _CREATED_WITH_FLAGS = 1 << 63;
    }
}

impl PyTypeFlags {
    // Default used for both built-in and normal classes: empty, for now.
    // CPython default: Py_TPFLAGS_HAVE_STACKLESS_EXTENSION | Py_TPFLAGS_HAVE_VERSION_TAG
    pub const DEFAULT: Self = Self::empty();

    // CPython: See initialization of flags in type_new.
    /// Used for types created in Python. Subclassable and are a
    /// heaptype.
    pub const fn heap_type_flags() -> Self {
        match Self::from_bits(Self::DEFAULT.bits() | Self::HEAPTYPE.bits() | Self::BASETYPE.bits())
        {
            Some(flags) => flags,
            None => unreachable!(),
        }
    }

    pub const fn has_feature(self, flag: Self) -> bool {
        self.contains(flag)
    }

    #[cfg(debug_assertions)]
    pub const fn is_created_with_flags(self) -> bool {
        self.contains(Self::_CREATED_WITH_FLAGS)
    }
}

impl Default for PyTypeFlags {
    fn default() -> Self {
        Self::DEFAULT
    }
}

pub(crate) type GenericMethod = fn(&PyObject, FuncArgs, &VirtualMachine) -> PyResult;
/// Vectorcall function pointer (PEP 590).
/// args: owned positional args followed by kwarg values.
/// nargs: number of positional args (self prepended by caller if needed).
/// kwnames: keyword argument names (last kwnames.len() entries in args are kwarg values).
pub(crate) type VectorCallFunc = fn(
    &PyObject,              // callable
    Vec<PyObjectRef>,       // owned args (positional + kwarg values)
    usize,                  // nargs (positional count)
    Option<&[PyObjectRef]>, // kwnames (keyword argument names)
    &VirtualMachine,
) -> PyResult;
pub(crate) type HashFunc = fn(&PyObject, &VirtualMachine) -> PyResult<PyHash>;
// CallFunc = GenericMethod
pub(crate) type StringifyFunc = fn(&PyObject, &VirtualMachine) -> PyResult<PyRef<PyStr>>;
pub(crate) type GetattroFunc = fn(&PyObject, &Py<PyStr>, &VirtualMachine) -> PyResult;
pub(crate) type SetattroFunc =
    fn(&PyObject, &Py<PyStr>, PySetterValue, &VirtualMachine) -> PyResult<()>;
pub(crate) type AsBufferFunc = fn(&PyObject, &VirtualMachine) -> PyResult<PyBuffer>;
pub(crate) type RichCompareFunc = fn(
    &PyObject,
    &PyObject,
    PyComparisonOp,
    &VirtualMachine,
) -> PyResult<Either<PyObjectRef, PyComparisonValue>>;
pub(crate) type IterFunc = fn(PyObjectRef, &VirtualMachine) -> PyResult;
pub(crate) type IterNextFunc = fn(&PyObject, &VirtualMachine) -> PyResult<PyIterReturn>;
pub(crate) type DescrGetFunc =
    fn(PyObjectRef, Option<PyObjectRef>, Option<PyObjectRef>, &VirtualMachine) -> PyResult;
pub(crate) type DescrSetFunc =
    fn(&PyObject, PyObjectRef, PySetterValue, &VirtualMachine) -> PyResult<()>;
pub(crate) type AllocFunc = fn(PyTypeRef, usize, &VirtualMachine) -> PyResult;
pub(crate) type NewFunc = fn(PyTypeRef, FuncArgs, &VirtualMachine) -> PyResult;
pub(crate) type InitFunc = fn(PyObjectRef, FuncArgs, &VirtualMachine) -> PyResult<()>;
pub(crate) type DelFunc = fn(&PyObject, &VirtualMachine) -> PyResult<()>;

// Sequence sub-slot function types
pub(crate) type SeqLenFunc = fn(PySequence<'_>, &VirtualMachine) -> PyResult<usize>;
pub(crate) type SeqConcatFunc = fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult;
pub(crate) type SeqRepeatFunc = fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult;
pub(crate) type SeqItemFunc = fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult;
pub(crate) type SeqAssItemFunc =
    fn(PySequence<'_>, isize, Option<PyObjectRef>, &VirtualMachine) -> PyResult<()>;
pub(crate) type SeqContainsFunc = fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult<bool>;

// Mapping sub-slot function types
pub(crate) type MapLenFunc = fn(PyMapping<'_>, &VirtualMachine) -> PyResult<usize>;
pub(crate) type MapSubscriptFunc = fn(PyMapping<'_>, &PyObject, &VirtualMachine) -> PyResult;
pub(crate) type MapAssSubscriptFunc =
    fn(PyMapping<'_>, &PyObject, Option<PyObjectRef>, &VirtualMachine) -> PyResult<()>;

// slot_sq_length
pub(crate) fn len_wrapper(obj: &PyObject, vm: &VirtualMachine) -> PyResult<usize> {
    let ret = vm.call_special_method(obj, identifier!(vm, __len__), ())?;
    let len = ret.downcast_ref::<PyInt>().ok_or_else(|| {
        vm.new_type_error(format!(
            "'{}' object cannot be interpreted as an integer",
            ret.class()
        ))
    })?;
    let len = len.as_bigint();
    if len.is_negative() {
        return Err(vm.new_value_error("__len__() should return >= 0"));
    }
    let len = len
        .to_isize()
        .ok_or_else(|| vm.new_overflow_error("cannot fit 'int' into an index-sized integer"))?;
    Ok(len as usize)
}

pub(crate) fn contains_wrapper(
    obj: &PyObject,
    needle: &PyObject,
    vm: &VirtualMachine,
) -> PyResult<bool> {
    let ret = vm.call_special_method(obj, identifier!(vm, __contains__), (needle,))?;
    ret.try_to_bool(vm)
}

macro_rules! number_unary_op_wrapper {
    ($name:ident) => {
        |a, vm| vm.call_special_method(a.deref(), identifier!(vm, $name), ())
    };
}
macro_rules! number_binary_op_wrapper {
    ($name:ident) => {
        |a, b, vm| vm.call_special_method(a, identifier!(vm, $name), (b.to_owned(),))
    };
}
macro_rules! number_binary_right_op_wrapper {
    ($name:ident) => {
        |a, b, vm| vm.call_special_method(b, identifier!(vm, $name), (a.to_owned(),))
    };
}
macro_rules! number_ternary_op_wrapper {
    ($name:ident) => {
        |a, b, c, vm: &VirtualMachine| {
            if vm.is_none(c) {
                vm.call_special_method(a, identifier!(vm, $name), (b.to_owned(),))
            } else {
                vm.call_special_method(a, identifier!(vm, $name), (b.to_owned(), c.to_owned()))
            }
        }
    };
}
macro_rules! number_ternary_right_op_wrapper {
    ($name:ident) => {
        |a, b, c, vm: &VirtualMachine| {
            if vm.is_none(c) {
                vm.call_special_method(b, identifier!(vm, $name), (a.to_owned(),))
            } else {
                vm.call_special_method(b, identifier!(vm, $name), (a.to_owned(), c.to_owned()))
            }
        }
    };
}
fn getitem_wrapper<K: ToPyObject>(obj: &PyObject, needle: K, vm: &VirtualMachine) -> PyResult {
    vm.call_special_method(obj, identifier!(vm, __getitem__), (needle,))
}

fn setitem_wrapper<K: ToPyObject>(
    obj: &PyObject,
    needle: K,
    value: Option<PyObjectRef>,
    vm: &VirtualMachine,
) -> PyResult<()> {
    match value {
        Some(value) => vm.call_special_method(obj, identifier!(vm, __setitem__), (needle, value)),
        None => vm.call_special_method(obj, identifier!(vm, __delitem__), (needle,)),
    }
    .map(drop)
}

#[inline(never)]
fn mapping_setitem_wrapper(
    mapping: PyMapping<'_>,
    key: &PyObject,
    value: Option<PyObjectRef>,
    vm: &VirtualMachine,
) -> PyResult<()> {
    setitem_wrapper(mapping.obj, key, value, vm)
}

#[inline(never)]
fn mapping_getitem_wrapper(
    mapping: PyMapping<'_>,
    key: &PyObject,
    vm: &VirtualMachine,
) -> PyResult {
    getitem_wrapper(mapping.obj, key, vm)
}

#[inline(never)]
fn mapping_len_wrapper(mapping: PyMapping<'_>, vm: &VirtualMachine) -> PyResult<usize> {
    len_wrapper(mapping.obj, vm)
}

#[inline(never)]
fn sequence_len_wrapper(seq: PySequence<'_>, vm: &VirtualMachine) -> PyResult<usize> {
    len_wrapper(seq.obj, vm)
}

#[inline(never)]
fn sequence_getitem_wrapper(seq: PySequence<'_>, i: isize, vm: &VirtualMachine) -> PyResult {
    getitem_wrapper(seq.obj, i, vm)
}

#[inline(never)]
fn sequence_setitem_wrapper(
    seq: PySequence<'_>,
    i: isize,
    value: Option<PyObjectRef>,
    vm: &VirtualMachine,
) -> PyResult<()> {
    setitem_wrapper(seq.obj, i, value, vm)
}

#[inline(never)]
fn sequence_contains_wrapper(
    seq: PySequence<'_>,
    needle: &PyObject,
    vm: &VirtualMachine,
) -> PyResult<bool> {
    contains_wrapper(seq.obj, needle, vm)
}

#[inline(never)]
fn sequence_repeat_wrapper(seq: PySequence<'_>, n: isize, vm: &VirtualMachine) -> PyResult {
    vm.call_special_method(seq.obj, identifier!(vm, __mul__), (n,))
}

#[inline(never)]
fn sequence_inplace_repeat_wrapper(seq: PySequence<'_>, n: isize, vm: &VirtualMachine) -> PyResult {
    vm.call_special_method(seq.obj, identifier!(vm, __imul__), (n,))
}

fn repr_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
    let ret = vm.call_special_method(zelf, identifier!(vm, __repr__), ())?;
    ret.downcast::<PyStr>().map_err(|obj| {
        vm.new_type_error(format!(
            "__repr__ returned non-string (type {})",
            obj.class()
        ))
    })
}

fn str_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
    let ret = vm.call_special_method(zelf, identifier!(vm, __str__), ())?;
    ret.downcast::<PyStr>().map_err(|obj| {
        vm.new_type_error(format!(
            "__str__ returned non-string (type {})",
            obj.class()
        ))
    })
}

fn hash_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> {
    let hash_obj = vm.call_special_method(zelf, identifier!(vm, __hash__), ())?;
    let py_int = hash_obj
        .downcast_ref::<PyInt>()
        .ok_or_else(|| vm.new_type_error("__hash__ method should return an integer"))?;
    let big_int = py_int.as_bigint();
    let hash = big_int
        .to_i64()
        .map(fix_sentinel)
        .unwrap_or_else(|| hash_bigint(big_int));
    Ok(hash)
}

/// Marks a type as unhashable. Similar to PyObject_HashNotImplemented in CPython
pub fn hash_not_implemented(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> {
    Err(vm.new_type_error(format!("unhashable type: '{}'", zelf.class().name())))
}

fn call_wrapper(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
    vm.call_special_method(zelf, identifier!(vm, __call__), args)
}

fn getattro_wrapper(zelf: &PyObject, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
    let __getattribute__ = identifier!(vm, __getattribute__);
    let __getattr__ = identifier!(vm, __getattr__);
    match vm.call_special_method(zelf, __getattribute__, (name.to_owned(),)) {
        Ok(r) => Ok(r),
        Err(e)
            if e.fast_isinstance(vm.ctx.exceptions.attribute_error)
                && zelf.class().has_attr(__getattr__) =>
        {
            vm.call_special_method(zelf, __getattr__, (name.to_owned(),))
        }
        Err(e) => Err(e),
    }
}

fn setattro_wrapper(
    zelf: &PyObject,
    name: &Py<PyStr>,
    value: PySetterValue,
    vm: &VirtualMachine,
) -> PyResult<()> {
    let name = name.to_owned();
    match value {
        PySetterValue::Assign(value) => {
            vm.call_special_method(zelf, identifier!(vm, __setattr__), (name, value))?;
        }
        PySetterValue::Delete => {
            vm.call_special_method(zelf, identifier!(vm, __delattr__), (name,))?;
        }
    };
    Ok(())
}

pub(crate) fn richcompare_wrapper(
    zelf: &PyObject,
    other: &PyObject,
    op: PyComparisonOp,
    vm: &VirtualMachine,
) -> PyResult<Either<PyObjectRef, PyComparisonValue>> {
    vm.call_special_method(zelf, op.method_name(&vm.ctx), (other.to_owned(),))
        .map(Either::A)
}

fn iter_wrapper(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
    // slot_tp_iter: if __iter__ is None, the type is explicitly not iterable
    let cls = zelf.class();
    let iter_attr = cls.get_attr(identifier!(vm, __iter__));
    match iter_attr {
        Some(attr) if vm.is_none(&attr) => {
            Err(vm.new_type_error(format!("'{}' object is not iterable", cls.name())))
        }
        _ => vm.call_special_method(&zelf, identifier!(vm, __iter__), ()),
    }
}

fn bool_wrapper(num: PyNumber<'_>, vm: &VirtualMachine) -> PyResult<bool> {
    let result = vm.call_special_method(num.obj, identifier!(vm, __bool__), ())?;
    // __bool__ must return exactly bool, not int subclass
    if !result.class().is(vm.ctx.types.bool_type) {
        return Err(vm.new_type_error(format!(
            "__bool__ should return bool, returned {}",
            result.class().name()
        )));
    }
    Ok(crate::builtins::bool_::get_value(&result))
}

// PyObject_SelfIter in CPython
const fn self_iter(zelf: PyObjectRef, _vm: &VirtualMachine) -> PyResult {
    Ok(zelf)
}

fn iternext_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
    PyIterReturn::from_pyresult(
        vm.call_special_method(zelf, identifier!(vm, __next__), ()),
        vm,
    )
}

fn descr_get_wrapper(
    zelf: PyObjectRef,
    obj: Option<PyObjectRef>,
    cls: Option<PyObjectRef>,
    vm: &VirtualMachine,
) -> PyResult {
    vm.call_special_method(&zelf, identifier!(vm, __get__), (obj, cls))
}

fn descr_set_wrapper(
    zelf: &PyObject,
    obj: PyObjectRef,
    value: PySetterValue,
    vm: &VirtualMachine,
) -> PyResult<()> {
    match value {
        PySetterValue::Assign(val) => {
            vm.call_special_method(zelf, identifier!(vm, __set__), (obj, val))
        }
        PySetterValue::Delete => vm.call_special_method(zelf, identifier!(vm, __delete__), (obj,)),
    }
    .map(drop)
}

fn init_wrapper(obj: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
    let res = vm.call_special_method(&obj, identifier!(vm, __init__), args)?;
    if !vm.is_none(&res) {
        return Err(vm.new_type_error(format!(
            "__init__() should return None, not '{:.200}'",
            res.class().name()
        )));
    }
    Ok(())
}

pub(crate) fn new_wrapper(cls: PyTypeRef, mut args: FuncArgs, vm: &VirtualMachine) -> PyResult {
    let new = cls.get_attr(identifier!(vm, __new__)).unwrap();
    args.prepend_arg(cls.into());
    new.call(args, vm)
}

fn del_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
    vm.call_special_method(zelf, identifier!(vm, __del__), ())?;
    Ok(())
}

/// Result of looking up a slot function in MRO.
enum SlotLookupResult<T> {
    /// Found a native slot function from a wrapper_descriptor.
    NativeSlot(T),
    /// Found a Python-level method (not a native slot).
    /// The caller should use the wrapper function.
    PythonMethod,
    /// No method with this name found in MRO at all.
    /// The caller should inherit the slot from MRO.
    NotFound,
}

impl PyType {
    /// Update slots based on dunder method changes
    ///
    /// Iterates SLOT_DEFS to find all slots matching the given name and updates them.
    /// Also recursively updates subclasses that don't have their own definition.
    pub(crate) fn update_slot<const ADD: bool>(&self, name: &'static PyStrInterned, ctx: &Context) {
        debug_assert!(name.as_str().starts_with("__"));
        debug_assert!(name.as_str().ends_with("__"));

        // Find all slot_defs matching this name and update each
        // NOTE: Collect into Vec first to avoid issues during iteration
        let defs: Vec<_> = find_slot_defs_by_name(name.as_str()).collect();
        for def in defs {
            self.update_one_slot::<ADD>(&def.accessor, name, ctx);
        }

        // Recursively update subclasses that don't have their own definition
        self.update_subclasses::<ADD>(name, ctx);
    }

    /// Recursively update subclasses' slots
    /// recurse_down_subclasses
    fn update_subclasses<const ADD: bool>(&self, name: &'static PyStrInterned, ctx: &Context) {
        let subclasses = self.subclasses.read();
        for weak_ref in subclasses.iter() {
            let Some(subclass) = weak_ref.upgrade() else {
                continue;
            };
            let Some(subclass) = subclass.downcast_ref::<PyType>() else {
                continue;
            };

            // Skip if subclass has its own definition for this attribute
            if subclass.attributes.read().contains_key(name) {
                continue;
            }

            // Update subclass's slots
            for def in find_slot_defs_by_name(name.as_str()) {
                subclass.update_one_slot::<ADD>(&def.accessor, name, ctx);
            }

            // Recurse into subclass's subclasses
            subclass.update_subclasses::<ADD>(name, ctx);
        }
    }

    /// Update a single slot
    fn update_one_slot<const ADD: bool>(
        &self,
        accessor: &SlotAccessor,
        name: &'static PyStrInterned,
        ctx: &Context,
    ) {
        use crate::builtins::descriptor::SlotFunc;

        // Helper macro for main slots
        macro_rules! update_main_slot {
            ($slot:ident, $wrapper:expr, $variant:ident) => {{
                if ADD {
                    match self.lookup_slot_in_mro(name, ctx, |sf| {
                        if let SlotFunc::$variant(f) = sf {
                            Some(*f)
                        } else {
                            None
                        }
                    }) {
                        SlotLookupResult::NativeSlot(func) => {
                            self.slots.$slot.store(Some(func));
                        }
                        SlotLookupResult::PythonMethod => {
                            self.slots.$slot.store(Some($wrapper));
                        }
                        SlotLookupResult::NotFound => {
                            accessor.inherit_from_mro(self);
                        }
                    }
                } else {
                    accessor.inherit_from_mro(self);
                }
            }};
        }

        // Helper macro for number/sequence/mapping sub-slots
        macro_rules! update_sub_slot {
            ($group:ident, $slot:ident, $wrapper:expr, $variant:ident) => {{
                if ADD {
                    // Check if this type defines any method that maps to this slot.
                    // Some slots like SqAssItem/MpAssSubscript are shared by multiple
                    // methods (__setitem__ and __delitem__). If any of those methods
                    // is defined, we must use the wrapper to ensure Python method calls.
                    let has_own = {
                        let guard = self.attributes.read();
                        // Check the current method name
                        let mut result = guard.contains_key(name);
                        // For ass_item/ass_subscript slots, also check the paired method
                        // (__setitem__ and __delitem__ share the same slot)
                        if !result
                            && (stringify!($slot) == "ass_item"
                                || stringify!($slot) == "ass_subscript")
                        {
                            let setitem = ctx.intern_str("__setitem__");
                            let delitem = ctx.intern_str("__delitem__");
                            result = guard.contains_key(setitem) || guard.contains_key(delitem);
                        }
                        result
                    };
                    if has_own {
                        self.slots.$group.$slot.store(Some($wrapper));
                    } else {
                        match self.lookup_slot_in_mro(name, ctx, |sf| {
                            if let SlotFunc::$variant(f) = sf {
                                Some(*f)
                            } else {
                                None
                            }
                        }) {
                            SlotLookupResult::NativeSlot(func) => {
                                self.slots.$group.$slot.store(Some(func));
                            }
                            SlotLookupResult::PythonMethod => {
                                self.slots.$group.$slot.store(Some($wrapper));
                            }
                            SlotLookupResult::NotFound => {
                                accessor.inherit_from_mro(self);
                            }
                        }
                    }
                } else {
                    accessor.inherit_from_mro(self);
                }
            }};
        }

        match accessor {
            // === Main slots ===
            SlotAccessor::TpRepr => update_main_slot!(repr, repr_wrapper, Repr),
            SlotAccessor::TpStr => update_main_slot!(str, str_wrapper, Str),
            SlotAccessor::TpHash => {
                // Special handling for __hash__ = None
                if ADD {
                    let method = self.attributes.read().get(name).cloned().or_else(|| {
                        self.mro
                            .read()
                            .iter()
                            .find_map(|cls| cls.attributes.read().get(name).cloned())
                    });

                    if method.as_ref().is_some_and(|m| m.is(&ctx.none)) {
                        self.slots.hash.store(Some(hash_not_implemented));
                    } else {
                        match self.lookup_slot_in_mro(name, ctx, |sf| {
                            if let SlotFunc::Hash(f) = sf {
                                Some(*f)
                            } else {
                                None
                            }
                        }) {
                            SlotLookupResult::NativeSlot(func) => {
                                self.slots.hash.store(Some(func));
                            }
                            SlotLookupResult::PythonMethod => {
                                self.slots.hash.store(Some(hash_wrapper));
                            }
                            SlotLookupResult::NotFound => {
                                accessor.inherit_from_mro(self);
                            }
                        }
                    }
                } else {
                    accessor.inherit_from_mro(self);
                }
            }
            SlotAccessor::TpCall => {
                update_main_slot!(call, call_wrapper, Call);
                // When __call__ is overridden in Python, clear vectorcall
                // so the slow path through call_wrapper is used.
                if ADD {
                    self.slots.vectorcall.store(None);
                }
            }
            SlotAccessor::TpIter => update_main_slot!(iter, iter_wrapper, Iter),
            SlotAccessor::TpIternext => update_main_slot!(iternext, iternext_wrapper, IterNext),
            SlotAccessor::TpInit => {
                update_main_slot!(init, init_wrapper, Init);
                if ADD {
                    self.slots.vectorcall.store(None);
                }
            }
            SlotAccessor::TpNew => {
                // __new__ is not wrapped via PyWrapper
                if ADD {
                    self.slots.new.store(Some(new_wrapper));
                    self.slots.vectorcall.store(None);
                } else {
                    accessor.inherit_from_mro(self);
                }
            }
            SlotAccessor::TpDel => update_main_slot!(del, del_wrapper, Del),
            SlotAccessor::TpGetattro => {
                // __getattribute__ and __getattr__ both map to TpGetattro.
                // If __getattr__ is defined anywhere in MRO, we must use the wrapper
                // because the native slot won't call __getattr__.
                let __getattr__ = identifier!(ctx, __getattr__);
                let has_getattr = {
                    let attrs = self.attributes.read();
                    let in_self = attrs.contains_key(__getattr__);
                    drop(attrs);
                    // mro[0] is self, so skip it
                    in_self
                        || self
                            .mro
                            .read()
                            .iter()
                            .skip(1)
                            .any(|cls| cls.attributes.read().contains_key(__getattr__))
                };

                if has_getattr {
                    // Must use wrapper to handle __getattr__
                    self.slots.getattro.store(Some(getattro_wrapper));
                } else if ADD {
                    match self.lookup_slot_in_mro(name, ctx, |sf| {
                        if let SlotFunc::GetAttro(f) = sf {
                            Some(*f)
                        } else {
                            None
                        }
                    }) {
                        SlotLookupResult::NativeSlot(func) => {
                            self.slots.getattro.store(Some(func));
                        }
                        SlotLookupResult::PythonMethod => {
                            self.slots.getattro.store(Some(getattro_wrapper));
                        }
                        SlotLookupResult::NotFound => {
                            accessor.inherit_from_mro(self);
                        }
                    }
                } else {
                    accessor.inherit_from_mro(self);
                }
            }
            SlotAccessor::TpSetattro => {
                // __setattr__ and __delattr__ share the same slot
                if ADD {
                    match self.lookup_slot_in_mro(name, ctx, |sf| match sf {
                        SlotFunc::SetAttro(f) | SlotFunc::DelAttro(f) => Some(*f),
                        _ => None,
                    }) {
                        SlotLookupResult::NativeSlot(func) => {
                            self.slots.setattro.store(Some(func));
                        }
                        SlotLookupResult::PythonMethod => {
                            self.slots.setattro.store(Some(setattro_wrapper));
                        }
                        SlotLookupResult::NotFound => {
                            accessor.inherit_from_mro(self);
                        }
                    }
                } else {
                    accessor.inherit_from_mro(self);
                }
            }
            SlotAccessor::TpDescrGet => update_main_slot!(descr_get, descr_get_wrapper, DescrGet),
            SlotAccessor::TpDescrSet => {
                // __set__ and __delete__ share the same slot
                if ADD {
                    match self.lookup_slot_in_mro(name, ctx, |sf| match sf {
                        SlotFunc::DescrSet(f) | SlotFunc::DescrDel(f) => Some(*f),
                        _ => None,
                    }) {
                        SlotLookupResult::NativeSlot(func) => {
                            self.slots.descr_set.store(Some(func));
                        }
                        SlotLookupResult::PythonMethod => {
                            self.slots.descr_set.store(Some(descr_set_wrapper));
                        }
                        SlotLookupResult::NotFound => {
                            accessor.inherit_from_mro(self);
                        }
                    }
                } else {
                    accessor.inherit_from_mro(self);
                }
            }

            // === Rich compare (__lt__, __le__, __eq__, __ne__, __gt__, __ge__) ===
            SlotAccessor::TpRichcompare => {
                if ADD {
                    // Check if self or any class in MRO has a Python-defined comparison method
                    // All comparison ops share the same slot, so if any is overridden anywhere
                    // in the hierarchy with a Python function, we need to use the wrapper
                    let cmp_names = [
                        identifier!(ctx, __eq__),
                        identifier!(ctx, __ne__),
                        identifier!(ctx, __lt__),
                        identifier!(ctx, __le__),
                        identifier!(ctx, __gt__),
                        identifier!(ctx, __ge__),
                    ];

                    let has_python_cmp = {
                        // Check self first
                        let attrs = self.attributes.read();
                        let in_self = cmp_names.iter().any(|n| attrs.contains_key(*n));
                        drop(attrs);

                        // mro[0] is self, so skip it since we already checked self above
                        in_self
                            || self.mro.read()[1..].iter().any(|cls| {
                                let attrs = cls.attributes.read();
                                cmp_names.iter().any(|n| {
                                    if let Some(attr) = attrs.get(*n) {
                                        // Check if it's a Python function (not a native descriptor)
                                        !attr.class().is(ctx.types.wrapper_descriptor_type)
                                            && !attr.class().is(ctx.types.method_descriptor_type)
                                    } else {
                                        false
                                    }
                                })
                            })
                    };

                    if has_python_cmp {
                        // Use wrapper to call the Python method
                        self.slots.richcompare.store(Some(richcompare_wrapper));
                    } else {
                        match self.lookup_slot_in_mro(name, ctx, |sf| {
                            if let SlotFunc::RichCompare(f, _) = sf {
                                Some(*f)
                            } else {
                                None
                            }
                        }) {
                            SlotLookupResult::NativeSlot(func) => {
                                self.slots.richcompare.store(Some(func));
                            }
                            SlotLookupResult::PythonMethod => {
                                self.slots.richcompare.store(Some(richcompare_wrapper));
                            }
                            SlotLookupResult::NotFound => {
                                accessor.inherit_from_mro(self);
                            }
                        }
                    }
                } else {
                    accessor.inherit_from_mro(self);
                }
            }

            // === Number binary operations ===
            SlotAccessor::NbAdd => {
                if name.as_str() == "__radd__" {
                    update_sub_slot!(
                        as_number,
                        right_add,
                        number_binary_right_op_wrapper!(__radd__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        add,
                        number_binary_op_wrapper!(__add__),
                        NumBinary
                    )
                }
            }
            SlotAccessor::NbInplaceAdd => {
                update_sub_slot!(
                    as_number,
                    inplace_add,
                    number_binary_op_wrapper!(__iadd__),
                    NumBinary
                )
            }
            SlotAccessor::NbSubtract => {
                if name.as_str() == "__rsub__" {
                    update_sub_slot!(
                        as_number,
                        right_subtract,
                        number_binary_right_op_wrapper!(__rsub__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        subtract,
                        number_binary_op_wrapper!(__sub__),
                        NumBinary
                    )
                }
            }
            SlotAccessor::NbInplaceSubtract => {
                update_sub_slot!(
                    as_number,
                    inplace_subtract,
                    number_binary_op_wrapper!(__isub__),
                    NumBinary
                )
            }
            SlotAccessor::NbMultiply => {
                if name.as_str() == "__rmul__" {
                    update_sub_slot!(
                        as_number,
                        right_multiply,
                        number_binary_right_op_wrapper!(__rmul__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        multiply,
                        number_binary_op_wrapper!(__mul__),
                        NumBinary
                    )
                }
            }
            SlotAccessor::NbInplaceMultiply => {
                update_sub_slot!(
                    as_number,
                    inplace_multiply,
                    number_binary_op_wrapper!(__imul__),
                    NumBinary
                )
            }
            SlotAccessor::NbRemainder => {
                if name.as_str() == "__rmod__" {
                    update_sub_slot!(
                        as_number,
                        right_remainder,
                        number_binary_right_op_wrapper!(__rmod__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        remainder,
                        number_binary_op_wrapper!(__mod__),
                        NumBinary
                    )
                }
            }
            SlotAccessor::NbInplaceRemainder => {
                update_sub_slot!(
                    as_number,
                    inplace_remainder,
                    number_binary_op_wrapper!(__imod__),
                    NumBinary
                )
            }
            SlotAccessor::NbDivmod => {
                if name.as_str() == "__rdivmod__" {
                    update_sub_slot!(
                        as_number,
                        right_divmod,
                        number_binary_right_op_wrapper!(__rdivmod__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        divmod,
                        number_binary_op_wrapper!(__divmod__),
                        NumBinary
                    )
                }
            }
            SlotAccessor::NbPower => {
                if name.as_str() == "__rpow__" {
                    update_sub_slot!(
                        as_number,
                        right_power,
                        number_ternary_right_op_wrapper!(__rpow__),
                        NumTernary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        power,
                        number_ternary_op_wrapper!(__pow__),
                        NumTernary
                    )
                }
            }
            SlotAccessor::NbInplacePower => {
                update_sub_slot!(
                    as_number,
                    inplace_power,
                    number_ternary_op_wrapper!(__ipow__),
                    NumTernary
                )
            }
            SlotAccessor::NbFloorDivide => {
                if name.as_str() == "__rfloordiv__" {
                    update_sub_slot!(
                        as_number,
                        right_floor_divide,
                        number_binary_right_op_wrapper!(__rfloordiv__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        floor_divide,
                        number_binary_op_wrapper!(__floordiv__),
                        NumBinary
                    )
                }
            }
            SlotAccessor::NbInplaceFloorDivide => {
                update_sub_slot!(
                    as_number,
                    inplace_floor_divide,
                    number_binary_op_wrapper!(__ifloordiv__),
                    NumBinary
                )
            }
            SlotAccessor::NbTrueDivide => {
                if name.as_str() == "__rtruediv__" {
                    update_sub_slot!(
                        as_number,
                        right_true_divide,
                        number_binary_right_op_wrapper!(__rtruediv__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        true_divide,
                        number_binary_op_wrapper!(__truediv__),
                        NumBinary
                    )
                }
            }
            SlotAccessor::NbInplaceTrueDivide => {
                update_sub_slot!(
                    as_number,
                    inplace_true_divide,
                    number_binary_op_wrapper!(__itruediv__),
                    NumBinary
                )
            }
            SlotAccessor::NbMatrixMultiply => {
                if name.as_str() == "__rmatmul__" {
                    update_sub_slot!(
                        as_number,
                        right_matrix_multiply,
                        number_binary_right_op_wrapper!(__rmatmul__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        matrix_multiply,
                        number_binary_op_wrapper!(__matmul__),
                        NumBinary
                    )
                }
            }
            SlotAccessor::NbInplaceMatrixMultiply => {
                update_sub_slot!(
                    as_number,
                    inplace_matrix_multiply,
                    number_binary_op_wrapper!(__imatmul__),
                    NumBinary
                )
            }

            // === Number bitwise operations ===
            SlotAccessor::NbLshift => {
                if name.as_str() == "__rlshift__" {
                    update_sub_slot!(
                        as_number,
                        right_lshift,
                        number_binary_right_op_wrapper!(__rlshift__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        lshift,
                        number_binary_op_wrapper!(__lshift__),
                        NumBinary
                    )
                }
            }
            SlotAccessor::NbInplaceLshift => {
                update_sub_slot!(
                    as_number,
                    inplace_lshift,
                    number_binary_op_wrapper!(__ilshift__),
                    NumBinary
                )
            }
            SlotAccessor::NbRshift => {
                if name.as_str() == "__rrshift__" {
                    update_sub_slot!(
                        as_number,
                        right_rshift,
                        number_binary_right_op_wrapper!(__rrshift__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        rshift,
                        number_binary_op_wrapper!(__rshift__),
                        NumBinary
                    )
                }
            }
            SlotAccessor::NbInplaceRshift => {
                update_sub_slot!(
                    as_number,
                    inplace_rshift,
                    number_binary_op_wrapper!(__irshift__),
                    NumBinary
                )
            }
            SlotAccessor::NbAnd => {
                if name.as_str() == "__rand__" {
                    update_sub_slot!(
                        as_number,
                        right_and,
                        number_binary_right_op_wrapper!(__rand__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        and,
                        number_binary_op_wrapper!(__and__),
                        NumBinary
                    )
                }
            }
            SlotAccessor::NbInplaceAnd => {
                update_sub_slot!(
                    as_number,
                    inplace_and,
                    number_binary_op_wrapper!(__iand__),
                    NumBinary
                )
            }
            SlotAccessor::NbXor => {
                if name.as_str() == "__rxor__" {
                    update_sub_slot!(
                        as_number,
                        right_xor,
                        number_binary_right_op_wrapper!(__rxor__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(
                        as_number,
                        xor,
                        number_binary_op_wrapper!(__xor__),
                        NumBinary
                    )
                }
            }
            SlotAccessor::NbInplaceXor => {
                update_sub_slot!(
                    as_number,
                    inplace_xor,
                    number_binary_op_wrapper!(__ixor__),
                    NumBinary
                )
            }
            SlotAccessor::NbOr => {
                if name.as_str() == "__ror__" {
                    update_sub_slot!(
                        as_number,
                        right_or,
                        number_binary_right_op_wrapper!(__ror__),
                        NumBinary
                    )
                } else {
                    update_sub_slot!(as_number, or, number_binary_op_wrapper!(__or__), NumBinary)
                }
            }
            SlotAccessor::NbInplaceOr => {
                update_sub_slot!(
                    as_number,
                    inplace_or,
                    number_binary_op_wrapper!(__ior__),
                    NumBinary
                )
            }

            // === Number unary operations ===
            SlotAccessor::NbNegative => {
                update_sub_slot!(
                    as_number,
                    negative,
                    number_unary_op_wrapper!(__neg__),
                    NumUnary
                )
            }
            SlotAccessor::NbPositive => {
                update_sub_slot!(
                    as_number,
                    positive,
                    number_unary_op_wrapper!(__pos__),
                    NumUnary
                )
            }
            SlotAccessor::NbAbsolute => {
                update_sub_slot!(
                    as_number,
                    absolute,
                    number_unary_op_wrapper!(__abs__),
                    NumUnary
                )
            }
            SlotAccessor::NbInvert => {
                update_sub_slot!(
                    as_number,
                    invert,
                    number_unary_op_wrapper!(__invert__),
                    NumUnary
                )
            }
            SlotAccessor::NbBool => {
                update_sub_slot!(as_number, boolean, bool_wrapper, NumBoolean)
            }
            SlotAccessor::NbInt => {
                update_sub_slot!(as_number, int, number_unary_op_wrapper!(__int__), NumUnary)
            }
            SlotAccessor::NbFloat => {
                update_sub_slot!(
                    as_number,
                    float,
                    number_unary_op_wrapper!(__float__),
                    NumUnary
                )
            }
            SlotAccessor::NbIndex => {
                update_sub_slot!(
                    as_number,
                    index,
                    number_unary_op_wrapper!(__index__),
                    NumUnary
                )
            }

            // === Sequence slots ===
            SlotAccessor::SqLength => {
                update_sub_slot!(as_sequence, length, sequence_len_wrapper, SeqLength)
            }
            SlotAccessor::SqConcat | SlotAccessor::SqInplaceConcat => {
                // Sequence concat uses sq_concat slot - no generic wrapper needed
                // (handled by number protocol fallback)
                if !ADD {
                    accessor.inherit_from_mro(self);
                }
            }
            SlotAccessor::SqRepeat => {
                update_sub_slot!(as_sequence, repeat, sequence_repeat_wrapper, SeqRepeat)
            }
            SlotAccessor::SqInplaceRepeat => {
                update_sub_slot!(
                    as_sequence,
                    inplace_repeat,
                    sequence_inplace_repeat_wrapper,
                    SeqRepeat
                )
            }
            SlotAccessor::SqItem => {
                update_sub_slot!(as_sequence, item, sequence_getitem_wrapper, SeqItem)
            }
            SlotAccessor::SqAssItem => {
                // SqAssItem is shared by __setitem__ (SeqSetItem) and __delitem__ (SeqDelItem)
                if ADD {
                    let has_own = {
                        let guard = self.attributes.read();
                        let setitem = ctx.intern_str("__setitem__");
                        let delitem = ctx.intern_str("__delitem__");
                        guard.contains_key(setitem) || guard.contains_key(delitem)
                    };
                    if has_own {
                        self.slots
                            .as_sequence
                            .ass_item
                            .store(Some(sequence_setitem_wrapper));
                    } else {
                        match self.lookup_slot_in_mro(name, ctx, |sf| match sf {
                            SlotFunc::SeqSetItem(f) | SlotFunc::SeqDelItem(f) => Some(*f),
                            _ => None,
                        }) {
                            SlotLookupResult::NativeSlot(func) => {
                                self.slots.as_sequence.ass_item.store(Some(func));
                            }
                            SlotLookupResult::PythonMethod => {
                                self.slots
                                    .as_sequence
                                    .ass_item
                                    .store(Some(sequence_setitem_wrapper));
                            }
                            SlotLookupResult::NotFound => {
                                accessor.inherit_from_mro(self);
                            }
                        }
                    }
                } else {
                    accessor.inherit_from_mro(self);
                }
            }
            SlotAccessor::SqContains => {
                update_sub_slot!(
                    as_sequence,
                    contains,
                    sequence_contains_wrapper,
                    SeqContains
                )
            }

            // === Mapping slots ===
            SlotAccessor::MpLength => {
                update_sub_slot!(as_mapping, length, mapping_len_wrapper, MapLength)
            }
            SlotAccessor::MpSubscript => {
                update_sub_slot!(as_mapping, subscript, mapping_getitem_wrapper, MapSubscript)
            }
            SlotAccessor::MpAssSubscript => {
                // MpAssSubscript is shared by __setitem__ (MapSetSubscript) and __delitem__ (MapDelSubscript)
                if ADD {
                    let has_own = {
                        let guard = self.attributes.read();
                        let setitem = ctx.intern_str("__setitem__");
                        let delitem = ctx.intern_str("__delitem__");
                        guard.contains_key(setitem) || guard.contains_key(delitem)
                    };
                    if has_own {
                        self.slots
                            .as_mapping
                            .ass_subscript
                            .store(Some(mapping_setitem_wrapper));
                    } else {
                        match self.lookup_slot_in_mro(name, ctx, |sf| match sf {
                            SlotFunc::MapSetSubscript(f) | SlotFunc::MapDelSubscript(f) => Some(*f),
                            _ => None,
                        }) {
                            SlotLookupResult::NativeSlot(func) => {
                                self.slots.as_mapping.ass_subscript.store(Some(func));
                            }
                            SlotLookupResult::PythonMethod => {
                                self.slots
                                    .as_mapping
                                    .ass_subscript
                                    .store(Some(mapping_setitem_wrapper));
                            }
                            SlotLookupResult::NotFound => {
                                accessor.inherit_from_mro(self);
                            }
                        }
                    }
                } else {
                    accessor.inherit_from_mro(self);
                }
            }

            // Reserved slots - no-op
            _ => {}
        }
    }

    /// Look up a method in MRO and extract the slot function if it's a slot wrapper.
    fn lookup_slot_in_mro<T: Copy>(
        &self,
        name: &'static PyStrInterned,
        ctx: &Context,
        extract: impl Fn(&crate::builtins::descriptor::SlotFunc) -> Option<T>,
    ) -> SlotLookupResult<T> {
        use crate::builtins::descriptor::PyWrapper;

        // Helper to check if a class is a subclass of another by checking MRO
        let is_subclass_of = |subclass_mro: &[PyRef<PyType>], superclass: &Py<PyType>| -> bool {
            subclass_mro.iter().any(|c| c.is(superclass))
        };

        // Helper to extract slot from an attribute if it's a wrapper descriptor
        // and the wrapper's type is compatible with the given class.
        // bpo-37619: wrapper descriptor from wrong class should not be used directly.
        let try_extract = |attr: &PyObjectRef, for_class_mro: &[PyRef<PyType>]| -> Option<T> {
            if attr.class().is(ctx.types.wrapper_descriptor_type) {
                attr.downcast_ref::<PyWrapper>().and_then(|wrapper| {
                    // Only extract slot if for_class is a subclass of wrapper.typ
                    if is_subclass_of(for_class_mro, wrapper.typ) {
                        extract(&wrapper.wrapped)
                    } else {
                        None
                    }
                })
            } else {
                None
            }
        };

        let mro = self.mro.read();

        // Look up in self's dict first
        if let Some(attr) = self.attributes.read().get(name).cloned() {
            if let Some(func) = try_extract(&attr, &mro) {
                return SlotLookupResult::NativeSlot(func);
            }
            return SlotLookupResult::PythonMethod;
        }

        // Look up in MRO (mro[0] is self, so skip it)
        for (i, cls) in mro[1..].iter().enumerate() {
            if let Some(attr) = cls.attributes.read().get(name).cloned() {
                // Use the slice starting from this class in MRO
                if let Some(func) = try_extract(&attr, &mro[i + 1..]) {
                    return SlotLookupResult::NativeSlot(func);
                }
                return SlotLookupResult::PythonMethod;
            }
        }
        // No method found in MRO
        SlotLookupResult::NotFound
    }
}

/// Trait for types that can be constructed via Python's `__new__` method.
///
/// `slot_new` corresponds to the `__new__` type slot.
///
/// In most cases, `__new__` simply initializes the payload and assigns a type,
/// so you only need to override `py_new`. The default `slot_new` implementation
/// will call `py_new` and then wrap the result with `into_ref_with_type`.
///
/// However, if a subtype requires more than just payload initialization
/// (e.g., returning an existing object for optimization, setting attributes
/// after creation, or special handling of the class type), you should override
/// `slot_new` directly instead of `py_new`.
///
/// # When to use `py_new` only (most common case):
/// - Simple payload initialization that just creates `Self`
/// - The type doesn't need special handling for subtypes
///
/// # When to override `slot_new`:
/// - Returning existing objects (e.g., `PyInt`, `PyStr`, `PyBool` for optimization)
/// - Setting attributes or dict entries after object creation
/// - Special class type handling (e.g., `PyType` and its metaclasses)
/// - Post-creation mutations that require `PyRef`
#[pyclass]
pub trait Constructor: PyPayload + core::fmt::Debug {
    type Args: FromArgs;

    /// The type slot for `__new__`. Override this only when you need special
    /// behavior beyond simple payload creation.
    #[inline]
    #[pyslot]
    fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
        let args: Self::Args = args.bind(vm)?;
        let payload = Self::py_new(&cls, args, vm)?;
        payload.into_ref_with_type(vm, cls).map(Into::into)
    }

    /// Creates the payload for this type. In most cases, just implement this method
    /// and let the default `slot_new` handle wrapping with the correct type.
    fn py_new(cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self>;
}

pub trait DefaultConstructor: PyPayload + Default + core::fmt::Debug {
    fn construct_and_init(args: Self::Args, vm: &VirtualMachine) -> PyResult<PyRef<Self>>
    where
        Self: Initializer,
    {
        let this = Self::default().into_ref(&vm.ctx);
        Self::init(this.clone(), args, vm)?;
        Ok(this)
    }
}

impl<T> Constructor for T
where
    T: DefaultConstructor,
{
    type Args = FuncArgs;

    fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
        Self::default().into_ref_with_type(vm, cls).map(Into::into)
    }

    fn py_new(cls: &Py<PyType>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
        Err(vm.new_type_error(format!("cannot create {} instances", cls.slot_name())))
    }
}

#[pyclass]
pub trait Initializer: PyPayload {
    type Args: FromArgs;

    #[inline]
    #[pyslot]
    fn slot_init(zelf: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
        #[cfg(debug_assertions)]
        let class_name_for_debug = zelf.class().name().to_string();

        let zelf = match zelf.try_into_value(vm) {
            Ok(zelf) => zelf,
            Err(err) => {
                #[cfg(debug_assertions)]
                {
                    if let Ok(msg) = err.as_object().repr(vm) {
                        let double_appearance = msg
                            .to_string_lossy()
                            .matches(&class_name_for_debug as &str)
                            .count()
                            == 2;
                        if double_appearance {
                            panic!(
                                "This type `{}` doesn't seem to support `init`. Override `slot_init` instead: {}",
                                class_name_for_debug, msg
                            );
                        }
                    }
                }
                return Err(err);
            }
        };
        let args: Self::Args = args.bind(vm)?;
        Self::init(zelf, args, vm)
    }

    fn init(zelf: PyRef<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()>;
}

#[pyclass]
pub trait Destructor: PyPayload {
    #[inline] // for __del__
    #[pyslot]
    fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
        let zelf = zelf
            .downcast_ref()
            .ok_or_else(|| vm.new_type_error("unexpected payload for __del__"))?;
        Self::del(zelf, vm)
    }

    fn del(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<()>;
}

#[pyclass]
pub trait Callable: PyPayload {
    type Args: FromArgs;

    #[inline]
    #[pyslot]
    fn slot_call(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
        let zelf = zelf.downcast_ref().ok_or_else(|| {
            let repr = zelf.repr(vm);
            let help: Wtf8Buf = if let Ok(repr) = repr.as_ref() {
                repr.as_wtf8().to_owned()
            } else {
                zelf.class().name().to_owned().into()
            };
            let mut msg = Wtf8Buf::from("unexpected payload for __call__ of ");
            msg.push_wtf8(&help);
            vm.new_type_error(msg)
        })?;
        let args = args.bind(vm)?;
        Self::call(zelf, args, vm)
    }

    fn call(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult;
}

#[pyclass]
pub trait GetDescriptor: PyPayload {
    #[pyslot]
    fn descr_get(
        zelf: PyObjectRef,
        obj: Option<PyObjectRef>,
        cls: Option<PyObjectRef>,
        vm: &VirtualMachine,
    ) -> PyResult;

    #[inline]
    fn _as_pyref<'a>(zelf: &'a PyObject, vm: &VirtualMachine) -> PyResult<&'a Py<Self>> {
        zelf.try_to_value(vm)
    }

    #[inline]
    fn _unwrap<'a>(
        zelf: &'a PyObject,
        obj: Option<PyObjectRef>,
        vm: &VirtualMachine,
    ) -> PyResult<(&'a Py<Self>, PyObjectRef)> {
        let zelf = Self::_as_pyref(zelf, vm)?;
        let obj = vm.unwrap_or_none(obj);
        Ok((zelf, obj))
    }

    #[inline]
    fn _check<'a>(
        zelf: &'a PyObject,
        obj: Option<PyObjectRef>,
        vm: &VirtualMachine,
    ) -> Option<(&'a Py<Self>, PyObjectRef)> {
        // CPython descr_check
        let obj = obj?;
        // if (!PyObject_TypeCheck(obj, descr->d_type)) {
        //     PyErr_Format(PyExc_TypeError,
        //                  "descriptor '%V' for '%.100s' objects "
        //                  "doesn't apply to a '%.100s' object",
        //                  descr_name((PyDescrObject *)descr), "?",
        //                  descr->d_type->slot_name,
        //                  obj->ob_type->slot_name);
        //     *pres = NULL;
        //     return 1;
        // } else {
        Some((Self::_as_pyref(zelf, vm).unwrap(), obj))
    }

    #[inline]
    fn _cls_is(cls: &Option<PyObjectRef>, other: &impl Borrow<PyObject>) -> bool {
        cls.as_ref().is_some_and(|cls| other.borrow().is(cls))
    }
}

#[pyclass]
pub trait Hashable: PyPayload {
    #[inline]
    #[pyslot]
    fn slot_hash(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> {
        let zelf = zelf
            .downcast_ref()
            .ok_or_else(|| vm.new_type_error("unexpected payload for __hash__"))?;
        Self::hash(zelf, vm)
    }

    fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash>;
}

#[pyclass]
pub trait Representable: PyPayload {
    #[inline]
    #[pyslot]
    fn slot_repr(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
        let zelf = zelf
            .downcast_ref()
            .ok_or_else(|| vm.new_type_error("unexpected payload for __repr__"))?;
        Self::repr(zelf, vm)
    }

    #[inline]
    fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
        let repr = Self::repr_wtf8(zelf, vm)?;
        Ok(vm.ctx.new_str(repr))
    }

    fn repr_wtf8(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
        Self::repr_str(zelf, vm).map(|utf8| utf8.into())
    }
    fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
        unreachable!("Representable requires overriding either repr_str or repr_wtf8")
    }
}

#[pyclass]
pub trait Comparable: PyPayload {
    #[inline]
    #[pyslot]
    fn slot_richcompare(
        zelf: &PyObject,
        other: &PyObject,
        op: PyComparisonOp,
        vm: &VirtualMachine,
    ) -> PyResult<Either<PyObjectRef, PyComparisonValue>> {
        let zelf = zelf.downcast_ref().ok_or_else(|| {
            vm.new_type_error(format!(
                "unexpected payload for {}",
                op.method_name(&vm.ctx).as_str()
            ))
        })?;
        Self::cmp(zelf, other, op, vm).map(Either::B)
    }

    fn cmp(
        zelf: &Py<Self>,
        other: &PyObject,
        op: PyComparisonOp,
        vm: &VirtualMachine,
    ) -> PyResult<PyComparisonValue>;
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(transparent)]
pub struct PyComparisonOp(ComparisonOperator);

impl From<ComparisonOperator> for PyComparisonOp {
    fn from(op: ComparisonOperator) -> Self {
        Self(op)
    }
}

#[allow(non_upper_case_globals)]
impl PyComparisonOp {
    pub const Lt: Self = Self(ComparisonOperator::Less);
    pub const Gt: Self = Self(ComparisonOperator::Greater);
    pub const Ne: Self = Self(ComparisonOperator::NotEqual);
    pub const Eq: Self = Self(ComparisonOperator::Equal);
    pub const Le: Self = Self(ComparisonOperator::LessOrEqual);
    pub const Ge: Self = Self(ComparisonOperator::GreaterOrEqual);
}

impl PyComparisonOp {
    pub fn eq_only(
        self,
        f: impl FnOnce() -> PyResult<PyComparisonValue>,
    ) -> PyResult<PyComparisonValue> {
        match self {
            Self::Eq => f(),
            Self::Ne => f().map(|x| x.map(|eq| !eq)),
            _ => Ok(PyComparisonValue::NotImplemented),
        }
    }

    pub fn eval_ord(self, ord: Ordering) -> bool {
        let bit = match ord {
            Ordering::Less => Self::Lt,
            Ordering::Equal => Self::Eq,
            Ordering::Greater => Self::Gt,
        };
        u8::from(self.0) & u8::from(bit.0) != 0
    }

    pub const fn swapped(self) -> Self {
        match self {
            Self::Lt => Self::Gt,
            Self::Le => Self::Ge,
            Self::Eq => Self::Eq,
            Self::Ne => Self::Ne,
            Self::Ge => Self::Le,
            Self::Gt => Self::Lt,
        }
    }

    pub fn method_name(self, ctx: &Context) -> &'static PyStrInterned {
        match self {
            Self::Lt => identifier!(ctx, __lt__),
            Self::Le => identifier!(ctx, __le__),
            Self::Eq => identifier!(ctx, __eq__),
            Self::Ne => identifier!(ctx, __ne__),
            Self::Ge => identifier!(ctx, __ge__),
            Self::Gt => identifier!(ctx, __gt__),
        }
    }

    pub const fn operator_token(self) -> &'static str {
        match self {
            Self::Lt => "<",
            Self::Le => "<=",
            Self::Eq => "==",
            Self::Ne => "!=",
            Self::Ge => ">=",
            Self::Gt => ">",
        }
    }

    /// Returns an appropriate return value for the comparison when a and b are the same object, if an
    /// appropriate return value exists.
    #[inline]
    pub fn identical_optimization(
        self,
        a: &impl Borrow<PyObject>,
        b: &impl Borrow<PyObject>,
    ) -> Option<bool> {
        self.map_eq(|| a.borrow().is(b.borrow()))
    }

    /// Returns `Some(true)` when self is `Eq` and `f()` returns true. Returns `Some(false)` when self
    /// is `Ne` and `f()` returns true. Otherwise returns `None`.
    #[inline]
    pub fn map_eq(self, f: impl FnOnce() -> bool) -> Option<bool> {
        let eq = match self {
            Self::Eq => true,
            Self::Ne => false,
            _ => return None,
        };
        f().then_some(eq)
    }
}

#[pyclass]
pub trait GetAttr: PyPayload {
    #[pyslot]
    fn slot_getattro(obj: &PyObject, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
        let zelf = obj
            .downcast_ref()
            .ok_or_else(|| vm.new_type_error("unexpected payload for __getattribute__"))?;
        Self::getattro(zelf, name, vm)
    }

    fn getattro(zelf: &Py<Self>, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult;
}

#[pyclass]
pub trait SetAttr: PyPayload {
    #[pyslot]
    #[inline]
    fn slot_setattro(
        obj: &PyObject,
        name: &Py<PyStr>,
        value: PySetterValue,
        vm: &VirtualMachine,
    ) -> PyResult<()> {
        let zelf = obj
            .downcast_ref::<Self>()
            .ok_or_else(|| vm.new_type_error("unexpected payload for __setattr__"))?;
        Self::setattro(zelf, name, value, vm)
    }

    fn setattro(
        zelf: &Py<Self>,
        name: &Py<PyStr>,
        value: PySetterValue,
        vm: &VirtualMachine,
    ) -> PyResult<()>;
}

#[pyclass]
pub trait AsBuffer: PyPayload {
    // TODO: `flags` parameter
    #[inline]
    #[pyslot]
    fn slot_as_buffer(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyBuffer> {
        let zelf = zelf
            .downcast_ref()
            .ok_or_else(|| vm.new_type_error("unexpected payload for as_buffer"))?;
        Self::as_buffer(zelf, vm)
    }

    fn as_buffer(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyBuffer>;
}

#[pyclass]
pub trait AsMapping: PyPayload {
    fn as_mapping() -> &'static PyMappingMethods;

    #[inline]
    fn mapping_downcast(mapping: PyMapping<'_>) -> &Py<Self> {
        unsafe { mapping.obj.downcast_unchecked_ref() }
    }

    fn extend_slots(slots: &mut PyTypeSlots) {
        slots.as_mapping.copy_from(Self::as_mapping());
    }
}

#[pyclass]
pub trait AsSequence: PyPayload {
    fn as_sequence() -> &'static PySequenceMethods;

    #[inline]
    fn sequence_downcast(seq: PySequence<'_>) -> &Py<Self> {
        unsafe { seq.obj.downcast_unchecked_ref() }
    }

    fn extend_slots(slots: &mut PyTypeSlots) {
        slots.as_sequence.copy_from(Self::as_sequence());
    }
}

#[pyclass]
pub trait AsNumber: PyPayload {
    #[pyslot]
    fn as_number() -> &'static PyNumberMethods;

    fn extend_slots(slots: &mut PyTypeSlots) {
        slots.as_number.copy_from(Self::as_number());
    }

    fn clone_exact(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyRef<Self> {
        // not all AsNumber requires this implementation.
        unimplemented!()
    }

    #[inline]
    fn number_downcast(num: PyNumber<'_>) -> &Py<Self> {
        unsafe { num.obj.downcast_unchecked_ref() }
    }

    #[inline]
    fn number_downcast_exact(num: PyNumber<'_>, vm: &VirtualMachine) -> PyRef<Self> {
        if let Some(zelf) = num.downcast_ref_if_exact::<Self>(vm) {
            zelf.to_owned()
        } else {
            Self::clone_exact(Self::number_downcast(num), vm)
        }
    }
}

#[pyclass]
pub trait Iterable: PyPayload {
    #[pyslot]
    fn slot_iter(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
        let zelf = zelf
            .downcast()
            .map_err(|_| vm.new_type_error("unexpected payload for __iter__"))?;
        Self::iter(zelf, vm)
    }

    fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult;

    fn extend_slots(_slots: &mut PyTypeSlots) {}
}

// `Iterator` fits better, but to avoid confusion with rust core::iter::Iterator
#[pyclass(with(Iterable))]
pub trait IterNext: PyPayload + Iterable {
    #[pyslot]
    fn slot_iternext(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
        let zelf = zelf
            .downcast_ref()
            .ok_or_else(|| vm.new_type_error("unexpected payload for __next__"))?;
        Self::next(zelf, vm)
    }

    fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn>;
}

pub trait SelfIter: PyPayload {}

impl<T> Iterable for T
where
    T: SelfIter,
{
    #[cold]
    fn slot_iter(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
        let repr = zelf.repr(vm)?;
        unreachable!("slot must be overridden for {}", repr.as_wtf8());
    }

    #[cold]
    fn iter(_zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyResult {
        unreachable!("slot_iter is implemented");
    }

    fn extend_slots(slots: &mut PyTypeSlots) {
        let prev = slots.iter.swap(Some(self_iter));
        debug_assert!(prev.is_some()); // slot_iter would be set
    }
}
