use super::{PyStr, PyStrInterned, PyType};
use crate::{
    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
    builtins::{PyTypeRef, builtin_func::PyNativeMethod, type_},
    class::PyClassImpl,
    common::hash::PyHash,
    convert::{ToPyObject, ToPyResult},
    function::{ArgSize, FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue},
    protocol::{PyNumberBinaryFunc, PyNumberTernaryFunc, PyNumberUnaryFunc},
    types::{
        Callable, Comparable, DelFunc, DescrGetFunc, DescrSetFunc, GenericMethod, GetDescriptor,
        GetattroFunc, HashFunc, Hashable, InitFunc, IterFunc, IterNextFunc, MapAssSubscriptFunc,
        MapLenFunc, MapSubscriptFunc, PyComparisonOp, Representable, RichCompareFunc,
        SeqAssItemFunc, SeqConcatFunc, SeqContainsFunc, SeqItemFunc, SeqLenFunc, SeqRepeatFunc,
        SetattroFunc, StringifyFunc,
    },
};
use rustpython_common::lock::PyRwLock;

#[derive(Debug)]
pub struct PyDescriptor {
    pub typ: &'static Py<PyType>,
    pub name: &'static PyStrInterned,
    pub qualname: PyRwLock<Option<String>>,
}

#[derive(Debug)]
pub struct PyDescriptorOwned {
    pub typ: PyRef<PyType>,
    pub name: &'static PyStrInterned,
    pub qualname: PyRwLock<Option<String>>,
}

#[pyclass(name = "method_descriptor", module = false)]
pub struct PyMethodDescriptor {
    pub common: PyDescriptor,
    pub method: &'static PyMethodDef,
    // vectorcall: vector_call_func,
    pub objclass: &'static Py<PyType>, // TODO: move to tp_members
    /// Prevent HeapMethodDef from being freed while this descriptor references it
    pub(crate) _method_def_owner: Option<PyObjectRef>,
}

impl PyMethodDescriptor {
    pub fn new(method: &'static PyMethodDef, typ: &'static Py<PyType>, ctx: &Context) -> Self {
        Self {
            common: PyDescriptor {
                typ,
                name: ctx.intern_str(method.name),
                qualname: PyRwLock::new(None),
            },
            method,
            objclass: typ,
            _method_def_owner: None,
        }
    }
}

impl PyPayload for PyMethodDescriptor {
    fn class(ctx: &Context) -> &'static Py<PyType> {
        ctx.types.method_descriptor_type
    }
}

impl core::fmt::Debug for PyMethodDescriptor {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "method descriptor for '{}'", self.common.name)
    }
}

impl GetDescriptor for PyMethodDescriptor {
    fn descr_get(
        zelf: PyObjectRef,
        obj: Option<PyObjectRef>,
        cls: Option<PyObjectRef>,
        vm: &VirtualMachine,
    ) -> PyResult {
        let descr = Self::_as_pyref(&zelf, vm).unwrap();
        let bound = match obj {
            Some(obj) => {
                if descr.method.flags.contains(PyMethodFlags::METHOD) {
                    if cls.is_some_and(|c| c.fast_isinstance(vm.ctx.types.type_type)) {
                        obj
                    } else {
                        return Err(vm.new_type_error(format!(
                            "descriptor '{}' needs a type, not '{}', as arg 2",
                            descr.common.name.as_str(),
                            obj.class().name()
                        )));
                    }
                } else if descr.method.flags.contains(PyMethodFlags::CLASS) {
                    obj.class().to_owned().into()
                } else {
                    obj
                }
            }
            None if descr.method.flags.contains(PyMethodFlags::CLASS) => cls.unwrap(),
            None => return Ok(zelf),
        };
        Ok(descr.bind(bound, &vm.ctx).into())
    }
}

impl Callable for PyMethodDescriptor {
    type Args = FuncArgs;
    #[inline]
    fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
        (zelf.method.func)(vm, args)
    }
}

impl PyMethodDescriptor {
    pub fn bind(&self, obj: PyObjectRef, ctx: &Context) -> PyRef<PyNativeMethod> {
        self.method.build_bound_method(ctx, obj, self.common.typ)
    }
}

#[pyclass(
    with(GetDescriptor, Callable, Representable),
    flags(METHOD_DESCRIPTOR, DISALLOW_INSTANTIATION)
)]
impl PyMethodDescriptor {
    #[pygetset]
    const fn __name__(&self) -> &'static PyStrInterned {
        self.common.name
    }

    #[pygetset]
    fn __qualname__(&self) -> String {
        format!("{}.{}", self.common.typ.name(), &self.common.name)
    }

    #[pygetset]
    const fn __doc__(&self) -> Option<&'static str> {
        self.method.doc
    }

    #[pygetset]
    fn __text_signature__(&self) -> Option<String> {
        self.method.doc.and_then(|doc| {
            type_::get_text_signature_from_internal_doc(self.method.name, doc)
                .map(|signature| signature.to_string())
        })
    }

    #[pygetset]
    fn __objclass__(&self) -> PyTypeRef {
        self.objclass.to_owned()
    }

    #[pymethod]
    fn __reduce__(
        &self,
        vm: &VirtualMachine,
    ) -> (Option<PyObjectRef>, (Option<PyObjectRef>, &'static str)) {
        let builtins_getattr = vm.builtins.get_attr("getattr", vm).ok();
        let classname = vm.builtins.get_attr(&self.common.typ.__name__(vm), vm).ok();
        (builtins_getattr, (classname, self.method.name))
    }
}

impl Representable for PyMethodDescriptor {
    #[inline]
    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
        Ok(format!(
            "<method '{}' of '{}' objects>",
            &zelf.method.name,
            zelf.common.typ.name()
        ))
    }
}

#[derive(Debug)]
pub enum MemberKind {
    Object = 6,
    Bool = 14,
    ObjectEx = 16,
}

pub type MemberSetterFunc = Option<fn(&VirtualMachine, PyObjectRef, PySetterValue) -> PyResult<()>>;

pub enum MemberGetter {
    Getter(fn(&VirtualMachine, PyObjectRef) -> PyResult),
    Offset(usize),
}

pub enum MemberSetter {
    Setter(MemberSetterFunc),
    Offset(usize),
}

pub struct PyMemberDef {
    pub name: String,
    pub kind: MemberKind,
    pub getter: MemberGetter,
    pub setter: MemberSetter,
    pub doc: Option<String>,
}

impl PyMemberDef {
    fn get(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
        match self.getter {
            MemberGetter::Getter(getter) => (getter)(vm, obj),
            MemberGetter::Offset(offset) => get_slot_from_object(obj, offset, self, vm),
        }
    }

    fn set(
        &self,
        obj: PyObjectRef,
        value: PySetterValue<PyObjectRef>,
        vm: &VirtualMachine,
    ) -> PyResult<()> {
        match self.setter {
            MemberSetter::Setter(setter) => match setter {
                Some(setter) => (setter)(vm, obj, value),
                None => Err(vm.new_attribute_error("readonly attribute")),
            },
            MemberSetter::Offset(offset) => set_slot_at_object(obj, offset, self, value, vm),
        }
    }
}

impl core::fmt::Debug for PyMemberDef {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("PyMemberDef")
            .field("name", &self.name)
            .field("kind", &self.kind)
            .field("doc", &self.doc)
            .finish()
    }
}

// = PyMemberDescrObject
#[pyclass(name = "member_descriptor", module = false)]
#[derive(Debug)]
pub struct PyMemberDescriptor {
    pub common: PyDescriptorOwned,
    pub member: PyMemberDef,
}

impl PyPayload for PyMemberDescriptor {
    fn class(ctx: &Context) -> &'static Py<PyType> {
        ctx.types.member_descriptor_type
    }
}

fn calculate_qualname(descr: &PyDescriptorOwned, vm: &VirtualMachine) -> PyResult<Option<String>> {
    if let Some(qualname) = vm.get_attribute_opt(descr.typ.clone().into(), "__qualname__")? {
        let str = qualname.downcast::<PyStr>().map_err(|_| {
            vm.new_type_error("<descriptor>.__objclass__.__qualname__ is not a unicode object")
        })?;
        Ok(Some(format!("{}.{}", str, descr.name)))
    } else {
        Ok(None)
    }
}

#[pyclass(with(GetDescriptor, Representable), flags(DISALLOW_INSTANTIATION))]
impl PyMemberDescriptor {
    #[pymember]
    fn __objclass__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
        let zelf: &Py<Self> = zelf.try_to_value(vm)?;
        Ok(zelf.common.typ.clone().into())
    }

    #[pymember]
    fn __name__(vm: &VirtualMachine, zelf: PyObjectRef) -> PyResult {
        let zelf: &Py<Self> = zelf.try_to_value(vm)?;
        Ok(zelf.common.name.to_owned().into())
    }

    #[pygetset]
    fn __doc__(&self) -> Option<String> {
        self.member.doc.to_owned()
    }

    #[pygetset]
    fn __qualname__(&self, vm: &VirtualMachine) -> PyResult<Option<String>> {
        let qualname = self.common.qualname.read();
        Ok(if qualname.is_none() {
            drop(qualname);
            let calculated = calculate_qualname(&self.common, vm)?;
            calculated.clone_into(&mut self.common.qualname.write());
            calculated
        } else {
            qualname.to_owned()
        })
    }

    #[pymethod]
    fn __reduce__(&self, vm: &VirtualMachine) -> PyResult {
        let builtins_getattr = vm.builtins.get_attr("getattr", vm)?;
        Ok(vm
            .ctx
            .new_tuple(vec![
                builtins_getattr,
                vm.ctx
                    .new_tuple(vec![
                        self.common.typ.clone().into(),
                        vm.ctx.new_str(self.common.name.as_str()).into(),
                    ])
                    .into(),
            ])
            .into())
    }

    #[pyslot]
    fn descr_set(
        zelf: &PyObject,
        obj: PyObjectRef,
        value: PySetterValue<PyObjectRef>,
        vm: &VirtualMachine,
    ) -> PyResult<()> {
        let zelf = Self::_as_pyref(zelf, vm)?;

        if !obj.class().fast_issubclass(&zelf.common.typ) {
            return Err(vm.new_type_error(format!(
                "descriptor '{}' for '{}' objects doesn't apply to a '{}' object",
                zelf.common.name,
                zelf.common.typ.name(),
                obj.class().name()
            )));
        }

        zelf.member.set(obj, value, vm)
    }
}

// PyMember_GetOne
fn get_slot_from_object(
    obj: PyObjectRef,
    offset: usize,
    member: &PyMemberDef,
    vm: &VirtualMachine,
) -> PyResult {
    let slot = match member.kind {
        MemberKind::Object => obj.get_slot(offset).unwrap_or_else(|| vm.ctx.none()),
        MemberKind::Bool => obj
            .get_slot(offset)
            .unwrap_or_else(|| vm.ctx.new_bool(false).into()),
        MemberKind::ObjectEx => obj.get_slot(offset).ok_or_else(|| {
            vm.new_no_attribute_error(obj.clone(), vm.ctx.new_str(member.name.clone()))
        })?,
    };
    Ok(slot)
}

// PyMember_SetOne
fn set_slot_at_object(
    obj: PyObjectRef,
    offset: usize,
    member: &PyMemberDef,
    value: PySetterValue,
    vm: &VirtualMachine,
) -> PyResult<()> {
    match member.kind {
        MemberKind::Object => match value {
            PySetterValue::Assign(v) => {
                obj.set_slot(offset, Some(v));
            }
            PySetterValue::Delete => {
                obj.set_slot(offset, None);
            }
        },
        MemberKind::Bool => {
            match value {
                PySetterValue::Assign(v) => {
                    if !v.class().is(vm.ctx.types.bool_type) {
                        return Err(vm.new_type_error("attribute value type must be bool"));
                    }
                    obj.set_slot(offset, Some(v))
                }
                PySetterValue::Delete => {
                    return Err(vm.new_type_error("can't delete numeric/char attribute"));
                }
            };
        }
        MemberKind::ObjectEx => match value {
            PySetterValue::Assign(v) => {
                obj.set_slot(offset, Some(v));
            }
            PySetterValue::Delete => {
                if obj.get_slot(offset).is_none() {
                    return Err(vm.new_attribute_error(member.name.clone()));
                }
                obj.set_slot(offset, None);
            }
        },
    }

    Ok(())
}

impl Representable for PyMemberDescriptor {
    #[inline]
    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
        Ok(format!(
            "<member '{}' of '{}' objects>",
            zelf.common.name,
            zelf.common.typ.name(),
        ))
    }
}

impl GetDescriptor for PyMemberDescriptor {
    fn descr_get(
        zelf: PyObjectRef,
        obj: Option<PyObjectRef>,
        _cls: Option<PyObjectRef>,
        vm: &VirtualMachine,
    ) -> PyResult {
        let descr = Self::_as_pyref(&zelf, vm)?;
        match obj {
            Some(x) => {
                if !x.class().fast_issubclass(&descr.common.typ) {
                    return Err(vm.new_type_error(format!(
                        "descriptor '{}' for '{}' objects doesn't apply to a '{}' object",
                        descr.common.name,
                        descr.common.typ.name(),
                        x.class().name()
                    )));
                }
                descr.member.get(x, vm)
            }
            None => Ok(zelf),
        }
    }
}

/// Vectorcall for method_descriptor: calls native method directly
fn vectorcall_method_descriptor(
    zelf_obj: &PyObject,
    args: Vec<PyObjectRef>,
    nargs: usize,
    kwnames: Option<&[PyObjectRef]>,
    vm: &VirtualMachine,
) -> PyResult {
    let zelf: &Py<PyMethodDescriptor> = zelf_obj.downcast_ref().unwrap();
    let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
    (zelf.method.func)(vm, func_args)
}

/// Vectorcall for wrapper_descriptor: calls wrapped slot function
fn vectorcall_wrapper(
    zelf_obj: &PyObject,
    mut args: Vec<PyObjectRef>,
    nargs: usize,
    kwnames: Option<&[PyObjectRef]>,
    vm: &VirtualMachine,
) -> PyResult {
    let zelf: &Py<PyWrapper> = zelf_obj.downcast_ref().unwrap();
    // First positional arg is self
    if nargs == 0 {
        return Err(vm.new_type_error(format!(
            "descriptor '{}' of '{}' object needs an argument",
            zelf.name.as_str(),
            zelf.typ.name()
        )));
    }
    let obj = args.remove(0);
    if !obj.fast_isinstance(zelf.typ) {
        return Err(vm.new_type_error(format!(
            "descriptor '{}' requires a '{}' object but received a '{}'",
            zelf.name.as_str(),
            zelf.typ.name(),
            obj.class().name()
        )));
    }
    let rest = FuncArgs::from_vectorcall_owned(args, nargs - 1, kwnames);
    zelf.wrapped.call(obj, rest, vm)
}

pub fn init(ctx: &'static Context) {
    PyMemberDescriptor::extend_class(ctx, ctx.types.member_descriptor_type);
    PyMethodDescriptor::extend_class(ctx, ctx.types.method_descriptor_type);
    ctx.types
        .method_descriptor_type
        .slots
        .vectorcall
        .store(Some(vectorcall_method_descriptor));
    PyWrapper::extend_class(ctx, ctx.types.wrapper_descriptor_type);
    ctx.types
        .wrapper_descriptor_type
        .slots
        .vectorcall
        .store(Some(vectorcall_wrapper));
    PyMethodWrapper::extend_class(ctx, ctx.types.method_wrapper_type);
}

// PyWrapper - wrapper_descriptor

/// Each variant knows how to call the wrapped function with proper types
#[derive(Clone, Copy)]
pub enum SlotFunc {
    // Basic slots
    Init(InitFunc),
    Hash(HashFunc),
    Str(StringifyFunc),
    Repr(StringifyFunc),
    Iter(IterFunc),
    IterNext(IterNextFunc),
    Call(GenericMethod),
    Del(DelFunc),

    // Attribute access slots
    GetAttro(GetattroFunc),
    SetAttro(SetattroFunc), // __setattr__
    DelAttro(SetattroFunc), // __delattr__ (same func type, different PySetterValue)

    // Rich comparison slots (with comparison op)
    RichCompare(RichCompareFunc, PyComparisonOp),

    // Descriptor slots
    DescrGet(DescrGetFunc),
    DescrSet(DescrSetFunc), // __set__
    DescrDel(DescrSetFunc), // __delete__ (same func type, different PySetterValue)

    // Sequence sub-slots (sq_*)
    SeqLength(SeqLenFunc),
    SeqConcat(SeqConcatFunc),
    SeqRepeat(SeqRepeatFunc),
    SeqItem(SeqItemFunc),
    SeqSetItem(SeqAssItemFunc), // __setitem__ (same func type, value = Some)
    SeqDelItem(SeqAssItemFunc), // __delitem__ (same func type, value = None)
    SeqContains(SeqContainsFunc),

    // Mapping sub-slots (mp_*)
    MapLength(MapLenFunc),
    MapSubscript(MapSubscriptFunc),
    MapSetSubscript(MapAssSubscriptFunc), // __setitem__ (same func type, value = Some)
    MapDelSubscript(MapAssSubscriptFunc), // __delitem__ (same func type, value = None)

    // Number sub-slots (nb_*) - grouped by signature
    NumBoolean(PyNumberUnaryFunc<bool>),  // __bool__
    NumUnary(PyNumberUnaryFunc),          // __int__, __float__, __index__
    NumBinary(PyNumberBinaryFunc),        // __add__, __sub__, __mul__, etc.
    NumBinaryRight(PyNumberBinaryFunc),   // __radd__, __rsub__, etc. (swapped args)
    NumTernary(PyNumberTernaryFunc),      // __pow__
    NumTernaryRight(PyNumberTernaryFunc), // __rpow__ (swapped first two args)
}

impl core::fmt::Debug for SlotFunc {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            SlotFunc::Init(_) => write!(f, "SlotFunc::Init(...)"),
            SlotFunc::Hash(_) => write!(f, "SlotFunc::Hash(...)"),
            SlotFunc::Str(_) => write!(f, "SlotFunc::Str(...)"),
            SlotFunc::Repr(_) => write!(f, "SlotFunc::Repr(...)"),
            SlotFunc::Iter(_) => write!(f, "SlotFunc::Iter(...)"),
            SlotFunc::IterNext(_) => write!(f, "SlotFunc::IterNext(...)"),
            SlotFunc::Call(_) => write!(f, "SlotFunc::Call(...)"),
            SlotFunc::Del(_) => write!(f, "SlotFunc::Del(...)"),
            SlotFunc::GetAttro(_) => write!(f, "SlotFunc::GetAttro(...)"),
            SlotFunc::SetAttro(_) => write!(f, "SlotFunc::SetAttro(...)"),
            SlotFunc::DelAttro(_) => write!(f, "SlotFunc::DelAttro(...)"),
            SlotFunc::RichCompare(_, op) => write!(f, "SlotFunc::RichCompare(..., {:?})", op),
            SlotFunc::DescrGet(_) => write!(f, "SlotFunc::DescrGet(...)"),
            SlotFunc::DescrSet(_) => write!(f, "SlotFunc::DescrSet(...)"),
            SlotFunc::DescrDel(_) => write!(f, "SlotFunc::DescrDel(...)"),
            // Sequence sub-slots
            SlotFunc::SeqLength(_) => write!(f, "SlotFunc::SeqLength(...)"),
            SlotFunc::SeqConcat(_) => write!(f, "SlotFunc::SeqConcat(...)"),
            SlotFunc::SeqRepeat(_) => write!(f, "SlotFunc::SeqRepeat(...)"),
            SlotFunc::SeqItem(_) => write!(f, "SlotFunc::SeqItem(...)"),
            SlotFunc::SeqSetItem(_) => write!(f, "SlotFunc::SeqSetItem(...)"),
            SlotFunc::SeqDelItem(_) => write!(f, "SlotFunc::SeqDelItem(...)"),
            SlotFunc::SeqContains(_) => write!(f, "SlotFunc::SeqContains(...)"),
            // Mapping sub-slots
            SlotFunc::MapLength(_) => write!(f, "SlotFunc::MapLength(...)"),
            SlotFunc::MapSubscript(_) => write!(f, "SlotFunc::MapSubscript(...)"),
            SlotFunc::MapSetSubscript(_) => write!(f, "SlotFunc::MapSetSubscript(...)"),
            SlotFunc::MapDelSubscript(_) => write!(f, "SlotFunc::MapDelSubscript(...)"),
            // Number sub-slots
            SlotFunc::NumBoolean(_) => write!(f, "SlotFunc::NumBoolean(...)"),
            SlotFunc::NumUnary(_) => write!(f, "SlotFunc::NumUnary(...)"),
            SlotFunc::NumBinary(_) => write!(f, "SlotFunc::NumBinary(...)"),
            SlotFunc::NumBinaryRight(_) => write!(f, "SlotFunc::NumBinaryRight(...)"),
            SlotFunc::NumTernary(_) => write!(f, "SlotFunc::NumTernary(...)"),
            SlotFunc::NumTernaryRight(_) => write!(f, "SlotFunc::NumTernaryRight(...)"),
        }
    }
}

impl SlotFunc {
    /// Call the wrapped slot function with proper type handling
    pub fn call(&self, obj: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
        match self {
            SlotFunc::Init(func) => {
                func(obj, args, vm)?;
                Ok(vm.ctx.none())
            }
            SlotFunc::Hash(func) => {
                if !args.args.is_empty() || !args.kwargs.is_empty() {
                    return Err(vm.new_type_error("__hash__() takes no arguments (1 given)"));
                }
                let hash = func(&obj, vm)?;
                Ok(vm.ctx.new_int(hash).into())
            }
            SlotFunc::Repr(func) | SlotFunc::Str(func) => {
                if !args.args.is_empty() || !args.kwargs.is_empty() {
                    let name = match self {
                        SlotFunc::Repr(_) => "__repr__",
                        SlotFunc::Str(_) => "__str__",
                        _ => unreachable!(),
                    };
                    return Err(vm.new_type_error(format!("{name}() takes no arguments (1 given)")));
                }
                let s = func(&obj, vm)?;
                Ok(s.into())
            }
            SlotFunc::Iter(func) => {
                if !args.args.is_empty() || !args.kwargs.is_empty() {
                    return Err(vm.new_type_error("__iter__() takes no arguments (1 given)"));
                }
                func(obj, vm)
            }
            SlotFunc::IterNext(func) => {
                if !args.args.is_empty() || !args.kwargs.is_empty() {
                    return Err(vm.new_type_error("__next__() takes no arguments (1 given)"));
                }
                func(&obj, vm).to_pyresult(vm)
            }
            SlotFunc::Call(func) => func(&obj, args, vm),
            SlotFunc::Del(func) => {
                if !args.args.is_empty() || !args.kwargs.is_empty() {
                    return Err(vm.new_type_error("__del__() takes no arguments (1 given)"));
                }
                func(&obj, vm)?;
                Ok(vm.ctx.none())
            }
            SlotFunc::GetAttro(func) => {
                let (name,): (PyRef<PyStr>,) = args.bind(vm)?;
                func(&obj, &name, vm)
            }
            SlotFunc::SetAttro(func) => {
                let (name, value): (PyRef<PyStr>, PyObjectRef) = args.bind(vm)?;
                func(&obj, &name, PySetterValue::Assign(value), vm)?;
                Ok(vm.ctx.none())
            }
            SlotFunc::DelAttro(func) => {
                let (name,): (PyRef<PyStr>,) = args.bind(vm)?;
                func(&obj, &name, PySetterValue::Delete, vm)?;
                Ok(vm.ctx.none())
            }
            SlotFunc::RichCompare(func, op) => {
                let (other,): (PyObjectRef,) = args.bind(vm)?;
                func(&obj, &other, *op, vm).map(|r| match r {
                    crate::function::Either::A(obj) => obj,
                    crate::function::Either::B(cmp_val) => cmp_val.to_pyobject(vm),
                })
            }
            SlotFunc::DescrGet(func) => {
                let (instance, owner): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) =
                    args.bind(vm)?;
                let owner = owner.into_option();
                let instance_opt = if vm.is_none(&instance) {
                    None
                } else {
                    Some(instance)
                };
                func(obj, instance_opt, owner, vm)
            }
            SlotFunc::DescrSet(func) => {
                let (instance, value): (PyObjectRef, PyObjectRef) = args.bind(vm)?;
                func(&obj, instance, PySetterValue::Assign(value), vm)?;
                Ok(vm.ctx.none())
            }
            SlotFunc::DescrDel(func) => {
                let (instance,): (PyObjectRef,) = args.bind(vm)?;
                func(&obj, instance, PySetterValue::Delete, vm)?;
                Ok(vm.ctx.none())
            }
            // Sequence sub-slots
            SlotFunc::SeqLength(func) => {
                args.bind::<()>(vm)?;
                let len = func(obj.sequence_unchecked(), vm)?;
                Ok(vm.ctx.new_int(len).into())
            }
            SlotFunc::SeqConcat(func) => {
                let (other,): (PyObjectRef,) = args.bind(vm)?;
                func(obj.sequence_unchecked(), &other, vm)
            }
            SlotFunc::SeqRepeat(func) => {
                let (n,): (ArgSize,) = args.bind(vm)?;
                func(obj.sequence_unchecked(), n.into(), vm)
            }
            SlotFunc::SeqItem(func) => {
                let (index,): (isize,) = args.bind(vm)?;
                func(obj.sequence_unchecked(), index, vm)
            }
            SlotFunc::SeqSetItem(func) => {
                let (index, value): (isize, PyObjectRef) = args.bind(vm)?;
                func(obj.sequence_unchecked(), index, Some(value), vm)?;
                Ok(vm.ctx.none())
            }
            SlotFunc::SeqDelItem(func) => {
                let (index,): (isize,) = args.bind(vm)?;
                func(obj.sequence_unchecked(), index, None, vm)?;
                Ok(vm.ctx.none())
            }
            SlotFunc::SeqContains(func) => {
                let (item,): (PyObjectRef,) = args.bind(vm)?;
                let result = func(obj.sequence_unchecked(), &item, vm)?;
                Ok(vm.ctx.new_bool(result).into())
            }
            // Mapping sub-slots
            SlotFunc::MapLength(func) => {
                args.bind::<()>(vm)?;
                let len = func(obj.mapping_unchecked(), vm)?;
                Ok(vm.ctx.new_int(len).into())
            }
            SlotFunc::MapSubscript(func) => {
                let (key,): (PyObjectRef,) = args.bind(vm)?;
                func(obj.mapping_unchecked(), &key, vm)
            }
            SlotFunc::MapSetSubscript(func) => {
                let (key, value): (PyObjectRef, PyObjectRef) = args.bind(vm)?;
                func(obj.mapping_unchecked(), &key, Some(value), vm)?;
                Ok(vm.ctx.none())
            }
            SlotFunc::MapDelSubscript(func) => {
                let (key,): (PyObjectRef,) = args.bind(vm)?;
                func(obj.mapping_unchecked(), &key, None, vm)?;
                Ok(vm.ctx.none())
            }
            // Number sub-slots
            SlotFunc::NumBoolean(func) => {
                args.bind::<()>(vm)?;
                let result = func(obj.number(), vm)?;
                Ok(vm.ctx.new_bool(result).into())
            }
            SlotFunc::NumUnary(func) => {
                args.bind::<()>(vm)?;
                func(obj.number(), vm)
            }
            SlotFunc::NumBinary(func) => {
                let (other,): (PyObjectRef,) = args.bind(vm)?;
                func(&obj, &other, vm)
            }
            SlotFunc::NumBinaryRight(func) => {
                let (other,): (PyObjectRef,) = args.bind(vm)?;
                func(&other, &obj, vm) // Swapped: other op obj
            }
            SlotFunc::NumTernary(func) => {
                let (y, z): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) =
                    args.bind(vm)?;
                let z = z.unwrap_or_else(|| vm.ctx.none());
                func(&obj, &y, &z, vm)
            }
            SlotFunc::NumTernaryRight(func) => {
                let (y, z): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) =
                    args.bind(vm)?;
                let z = z.unwrap_or_else(|| vm.ctx.none());
                func(&y, &obj, &z, vm) // Swapped: y ** obj % z
            }
        }
    }
}

/// wrapper_descriptor: wraps a slot function as a Python method
// = PyWrapperDescrObject
#[pyclass(name = "wrapper_descriptor", module = false)]
#[derive(Debug)]
pub struct PyWrapper {
    pub typ: &'static Py<PyType>,
    pub name: &'static PyStrInterned,
    pub wrapped: SlotFunc,
    pub doc: Option<&'static str>,
}

impl PyPayload for PyWrapper {
    fn class(ctx: &Context) -> &'static Py<PyType> {
        ctx.types.wrapper_descriptor_type
    }
}

impl GetDescriptor for PyWrapper {
    fn descr_get(
        zelf: PyObjectRef,
        obj: Option<PyObjectRef>,
        _cls: Option<PyObjectRef>,
        vm: &VirtualMachine,
    ) -> PyResult {
        match obj {
            None => Ok(zelf),
            Some(obj) => {
                let zelf = zelf.downcast::<Self>().unwrap();
                Ok(PyMethodWrapper { wrapper: zelf, obj }.into_pyobject(vm))
            }
        }
    }
}

impl Callable for PyWrapper {
    type Args = FuncArgs;

    fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
        // list.__init__(l, [1,2,3]) form - first arg is self
        let (obj, rest): (PyObjectRef, FuncArgs) = args.bind(vm)?;

        if !obj.fast_isinstance(zelf.typ) {
            return Err(vm.new_type_error(format!(
                "descriptor '{}' requires a '{}' object but received a '{}'",
                zelf.name.as_str(),
                zelf.typ.name(),
                obj.class().name()
            )));
        }

        zelf.wrapped.call(obj, rest, vm)
    }
}

#[pyclass(
    with(GetDescriptor, Callable, Representable),
    flags(DISALLOW_INSTANTIATION)
)]
impl PyWrapper {
    #[pygetset]
    fn __name__(&self) -> &'static PyStrInterned {
        self.name
    }

    #[pygetset]
    fn __qualname__(&self) -> String {
        format!("{}.{}", self.typ.name(), self.name)
    }

    #[pygetset]
    fn __objclass__(&self) -> PyTypeRef {
        self.typ.to_owned()
    }

    #[pygetset]
    fn __doc__(&self) -> Option<&'static str> {
        self.doc
    }
}

impl Representable for PyWrapper {
    #[inline]
    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
        Ok(format!(
            "<slot wrapper '{}' of '{}' objects>",
            zelf.name.as_str(),
            zelf.typ.name()
        ))
    }
}

// PyMethodWrapper - method-wrapper

/// method-wrapper: a slot wrapper bound to an instance
/// Returned when accessing l.__init__ on an instance
#[pyclass(name = "method-wrapper", module = false, traverse)]
#[derive(Debug)]
pub struct PyMethodWrapper {
    pub wrapper: PyRef<PyWrapper>,
    #[pytraverse(skip)]
    pub obj: PyObjectRef,
}

impl PyPayload for PyMethodWrapper {
    fn class(ctx: &Context) -> &'static Py<PyType> {
        ctx.types.method_wrapper_type
    }
}

impl Callable for PyMethodWrapper {
    type Args = FuncArgs;

    fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
        // bpo-37619: Check type compatibility before calling wrapped slot
        if !zelf.obj.fast_isinstance(zelf.wrapper.typ) {
            return Err(vm.new_type_error(format!(
                "descriptor '{}' requires a '{}' object but received a '{}'",
                zelf.wrapper.name.as_str(),
                zelf.wrapper.typ.name(),
                zelf.obj.class().name()
            )));
        }
        zelf.wrapper.wrapped.call(zelf.obj.clone(), args, vm)
    }
}

#[pyclass(
    with(Callable, Representable, Hashable, Comparable),
    flags(DISALLOW_INSTANTIATION)
)]
impl PyMethodWrapper {
    #[pygetset]
    fn __self__(&self) -> PyObjectRef {
        self.obj.clone()
    }

    #[pygetset]
    fn __name__(&self) -> &'static PyStrInterned {
        self.wrapper.name
    }

    #[pygetset]
    fn __objclass__(&self) -> PyTypeRef {
        self.wrapper.typ.to_owned()
    }

    #[pymethod]
    fn __reduce__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
        let builtins_getattr = vm.builtins.get_attr("getattr", vm)?;
        Ok(vm
            .ctx
            .new_tuple(vec![
                builtins_getattr,
                vm.ctx
                    .new_tuple(vec![
                        zelf.obj.clone(),
                        vm.ctx.new_str(zelf.wrapper.name.as_str()).into(),
                    ])
                    .into(),
            ])
            .into())
    }
}

impl Representable for PyMethodWrapper {
    #[inline]
    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
        Ok(format!(
            "<method-wrapper '{}' of {} object at {:#x}>",
            zelf.wrapper.name.as_str(),
            zelf.obj.class().name(),
            zelf.obj.get_id()
        ))
    }
}

impl Hashable for PyMethodWrapper {
    fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
        let obj_hash = zelf.obj.hash(vm)?;
        let wrapper_hash = zelf.wrapper.as_object().get_id() as PyHash;
        Ok(obj_hash ^ wrapper_hash)
    }
}

impl Comparable for PyMethodWrapper {
    fn cmp(
        zelf: &Py<Self>,
        other: &PyObject,
        op: PyComparisonOp,
        vm: &VirtualMachine,
    ) -> PyResult<crate::function::PyComparisonValue> {
        op.eq_only(|| {
            let other = class_or_notimplemented!(Self, other);
            let eq = zelf.wrapper.is(&other.wrapper) && vm.bool_eq(&zelf.obj, &other.obj)?;
            Ok(eq.into())
        })
    }
}
