use super::{
    JsPrototype, ObjectStorage, PropertyDescriptor, PropertyKey,
    shape::{
        ChangeTransitionAction, RootShape, Shape, UniqueShape,
        property_table::PropertyTableInner,
        shared_shape::TransitionKey,
        slot::{Slot, SlotAttributes},
    },
};
use crate::value::JsVariant;
use crate::{JsValue, property::PropertyDescriptorBuilder};
use boa_gc::{Finalize, Trace, custom_trace};
use indexmap::IndexMap;
use rustc_hash::{FxHashMap, FxHasher};
use std::{collections::hash_map, hash::BuildHasherDefault, iter::FusedIterator};
use thin_vec::ThinVec;

/// Wrapper around `indexmap::IndexMap` for usage in `PropertyMap`.
#[derive(Debug, Finalize)]
#[allow(unused)] // TODO: OrderedHashmap is unused, candidate from removal?
struct OrderedHashMap<K: Trace>(IndexMap<K, PropertyDescriptor, BuildHasherDefault<FxHasher>>);

impl<K: Trace> Default for OrderedHashMap<K> {
    fn default() -> Self {
        Self(IndexMap::with_hasher(BuildHasherDefault::default()))
    }
}

unsafe impl<K: Trace> Trace for OrderedHashMap<K> {
    custom_trace!(this, mark, {
        for (k, v) in &this.0 {
            mark(k);
            mark(v);
        }
    });
}

/// This represents all the indexed properties.
///
/// The index properties can be stored in two storage methods:
///
/// ## Dense Storage
///
/// Dense storage holds a contiguous array of properties where the index in the array is the key of the property.
/// These are known to be data descriptors with a value field, writable field set to `true`, configurable field set to `true`, enumerable field set to `true`.
///
/// Since we know the properties of the property descriptors (and they are all the same) we can omit it and just store only
/// the value field and construct the data property descriptor on demand.
///
/// ## Sparse Storage
///
/// This storage is used as a backup if the element keys are not continuous or the property descriptors
/// are not data descriptors with with a value field, writable field set to `true`, configurable field set to `true`, enumerable field set to `true`.
///
/// This method uses more space, since we also have to store the property descriptors, not just the value.
/// It is also slower because we need to do a hash lookup.
#[derive(Debug, Trace, Finalize)]
pub enum IndexedProperties {
    /// Dense [`i32`] storage.
    DenseI32(ThinVec<i32>),

    /// Dense [`f64`] storage.
    DenseF64(ThinVec<f64>),

    /// Dense [`JsValue`] storage.
    DenseElement(ThinVec<JsValue>),

    /// Sparse [`JsValue`] storage.
    Sparse(Box<FxHashMap<u32, PropertyDescriptor>>),
}

impl Default for IndexedProperties {
    #[inline]
    fn default() -> Self {
        Self::DenseI32(ThinVec::new())
    }
}

impl IndexedProperties {
    pub(crate) fn from_dense_js_value(elements: ThinVec<JsValue>) -> Self {
        if elements.is_empty() {
            return Self::default();
        }
        Self::DenseElement(elements)
    }

    /// Get a property descriptor if it exists.
    fn get(&self, key: u32) -> Option<PropertyDescriptor> {
        let value = match self {
            Self::DenseI32(vec) => vec.get(key as usize).copied()?.into(),
            Self::DenseF64(vec) => vec.get(key as usize).copied()?.into(),
            Self::DenseElement(vec) => vec.get(key as usize)?.clone(),
            Self::Sparse(map) => return map.get(&key).cloned(),
        };

        Some(
            PropertyDescriptorBuilder::new()
                .writable(true)
                .enumerable(true)
                .configurable(true)
                .value(value)
                .build(),
        )
    }

    /// Helper function for converting from a dense storage type to sparse storage type.
    fn convert_dense_to_sparse<T>(vec: &mut ThinVec<T>) -> FxHashMap<u32, PropertyDescriptor>
    where
        T: Into<JsValue>,
    {
        let data = std::mem::take(vec);

        data.into_iter()
            .enumerate()
            .map(|(index, value)| {
                (
                    index as u32,
                    PropertyDescriptorBuilder::new()
                        .writable(true)
                        .enumerable(true)
                        .configurable(true)
                        .value(value.into())
                        .build(),
                )
            })
            .collect()
    }

    fn convert_to_sparse_and_insert(&mut self, key: u32, property: PropertyDescriptor) -> bool {
        let mut map = match self {
            Self::DenseI32(vec) => Self::convert_dense_to_sparse(vec),
            Self::DenseF64(vec) => Self::convert_dense_to_sparse(vec),
            Self::DenseElement(vec) => Self::convert_dense_to_sparse(vec),
            Self::Sparse(map) => {
                return map.insert(key, property).is_some();
            }
        };

        map.insert(key, property);
        *self = Self::Sparse(Box::new(map));
        false
    }

    /// Inserts a property descriptor with the specified key.
    fn insert(&mut self, key: u32, property: PropertyDescriptor) -> bool {
        if !property.writable().unwrap_or(false)
            || !property.enumerable().unwrap_or(false)
            || !property.configurable().unwrap_or(false)
        {
            return self.convert_to_sparse_and_insert(key, property);
        }
        let Some(value) = property.value().cloned() else {
            return self.convert_to_sparse_and_insert(key, property);
        };

        match self {
            // Fast Path: continues array access.
            Self::DenseI32(vec) if key <= vec.len() as u32 => {
                let len = vec.len() as u32;

                // If it can fit in a i32 and the truncated version is
                // equal to the original then it is an integer.
                let is_rational_integer = |n: f64| n.to_bits() == f64::from(n as i32).to_bits();

                let value = match value.variant() {
                    JsVariant::Integer32(n) => n,
                    JsVariant::Float64(n) if is_rational_integer(n) => n as i32,
                    JsVariant::Float64(value) => {
                        let mut vec = vec.iter().copied().map(f64::from).collect::<ThinVec<_>>();

                        // If the key is pointing one past the last element, we push it!
                        //
                        // Since the previous key is the current key - 1. Meaning that the elements are continuos.
                        if key == len {
                            vec.push(value);
                            *self = Self::DenseF64(vec);
                            return false;
                        }

                        // If it the key points in at a already taken index, set it.
                        vec[key as usize] = value;
                        *self = Self::DenseF64(vec);
                        return true;
                    }
                    _ => {
                        let mut vec = vec
                            .iter()
                            .copied()
                            .map(JsValue::from)
                            .collect::<ThinVec<JsValue>>();

                        // If the key is pointing one past the last element, we push it!
                        //
                        // Since the previous key is the current key - 1. Meaning that the elements are continuos.
                        if key == len {
                            vec.push(value);
                            *self = Self::DenseElement(vec);
                            return false;
                        }

                        // If it the key points in at a already taken index, set it.
                        vec[key as usize] = value;
                        *self = Self::DenseElement(vec);
                        return true;
                    }
                };

                // If the key is pointing one past the last element, we push it!
                //
                // Since the previous key is the current key - 1. Meaning that the elements are continuos.
                if key == len {
                    vec.push(value);
                    return false;
                }

                // If it the key points in at a already taken index, swap and return it.
                vec[key as usize] = value;
                true
            }
            Self::DenseF64(vec) if key <= vec.len() as u32 => {
                let len = vec.len() as u32;

                if let Some(value) = value.as_number() {
                    // If the key is pointing one past the last element, we push it!
                    //
                    // Since the previous key is the current key - 1. Meaning that the elements are continuos.
                    if key == len {
                        vec.push(value);
                        return false;
                    }

                    // If it the key points in at a already taken index, swap and return it.
                    vec[key as usize] = value;
                    return true;
                }

                let mut vec = vec
                    .iter()
                    .copied()
                    .map(JsValue::from)
                    .collect::<ThinVec<JsValue>>();

                // If the key is pointing one past the last element, we push it!
                //
                // Since the previous key is the current key - 1. Meaning that the elements are continuos.
                if key == len {
                    vec.push(value);
                    *self = Self::DenseElement(vec);
                    return false;
                }

                // If it the key points in at a already taken index, set it.
                vec[key as usize] = value;
                *self = Self::DenseElement(vec);
                true
            }
            Self::DenseElement(vec) if key <= vec.len() as u32 => {
                let len = vec.len() as u32;

                // If the key is pointing one past the last element, we push it!
                //
                // Since the previous key is the current key - 1. Meaning that the elements are continuos.
                if key == len {
                    vec.push(value);
                    return false;
                }

                // If it the key points in at a already taken index, set it.
                vec[key as usize] = value;
                true
            }
            Self::Sparse(map) => map.insert(key, property).is_some(),
            _ => self.convert_to_sparse_and_insert(key, property),
        }
    }

    fn convert_to_sparse_and_remove(&mut self, key: u32) -> bool {
        let mut map = match self {
            IndexedProperties::DenseI32(vec) => Self::convert_dense_to_sparse(vec),
            IndexedProperties::DenseF64(vec) => Self::convert_dense_to_sparse(vec),
            IndexedProperties::DenseElement(vec) => Self::convert_dense_to_sparse(vec),
            IndexedProperties::Sparse(map) => return map.remove(&key).is_some(),
        };

        let removed = map.remove(&key).is_some();
        *self = Self::Sparse(Box::new(map));
        removed
    }

    /// Removes a property descriptor with the specified key.
    fn remove(&mut self, key: u32) -> bool {
        match self {
            // Fast Paths: contiguous storage.
            //
            // If the key is pointing at the last element, then we pop it.
            Self::DenseI32(vec) if (key + 1) == vec.len() as u32 => {
                vec.pop();
                true
            }
            // If the key is out of range then don't do anything.
            Self::DenseI32(vec) if key >= vec.len() as u32 => false,
            Self::DenseF64(vec) if (key + 1) == vec.len() as u32 => {
                vec.pop();
                true
            }
            Self::DenseF64(vec) if key >= vec.len() as u32 => false,
            Self::DenseElement(vec) if (key + 1) == vec.len() as u32 => {
                vec.pop();
                true
            }
            Self::DenseElement(vec) if key >= vec.len() as u32 => false,
            // Slow Paths: non-contiguous storage.
            Self::Sparse(map) => map.remove(&key).is_some(),
            _ => self.convert_to_sparse_and_remove(key),
        }
    }

    /// Check if we contain the key to a property descriptor.
    fn contains_key(&self, key: u32) -> bool {
        match self {
            Self::DenseI32(vec) => (0..vec.len() as u32).contains(&key),
            Self::DenseF64(vec) => (0..vec.len() as u32).contains(&key),
            Self::DenseElement(vec) => (0..vec.len() as u32).contains(&key),
            Self::Sparse(map) => map.contains_key(&key),
        }
    }

    fn iter(&self) -> IndexProperties<'_> {
        match self {
            Self::DenseI32(vec) => IndexProperties::DenseI32(vec.iter().enumerate()),
            Self::DenseF64(vec) => IndexProperties::DenseF64(vec.iter().enumerate()),
            Self::DenseElement(vec) => IndexProperties::DenseElement(vec.iter().enumerate()),
            Self::Sparse(map) => IndexProperties::Sparse(map.iter()),
        }
    }

    fn keys(&self) -> IndexPropertyKeys<'_> {
        match self {
            Self::DenseI32(vec) => IndexPropertyKeys::Dense(0..vec.len() as u32),
            Self::DenseF64(vec) => IndexPropertyKeys::Dense(0..vec.len() as u32),
            Self::DenseElement(vec) => IndexPropertyKeys::Dense(0..vec.len() as u32),
            Self::Sparse(map) => IndexPropertyKeys::Sparse(map.keys()),
        }
    }

    fn values(&self) -> IndexPropertyValues<'_> {
        match self {
            Self::DenseI32(vec) => IndexPropertyValues::DenseI32(vec.iter()),
            Self::DenseF64(vec) => IndexPropertyValues::DenseF64(vec.iter()),
            Self::DenseElement(vec) => IndexPropertyValues::DenseElement(vec.iter()),
            Self::Sparse(map) => IndexPropertyValues::Sparse(map.values()),
        }
    }
}

impl<'a> IntoIterator for &'a IndexedProperties {
    type IntoIter = IndexProperties<'a>;
    type Item = (u32, PropertyDescriptor);
    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

/// A [`PropertyMap`] contains all the properties of an object.
///
/// The property values are stored in different data structures based on keys.
#[derive(Default, Debug, Trace, Finalize)]
pub struct PropertyMap {
    /// Properties stored with integers as keys.
    pub(crate) indexed_properties: IndexedProperties,

    pub(crate) shape: Shape,
    pub(crate) storage: ObjectStorage,
}

impl PropertyMap {
    /// Create a new [`PropertyMap`].
    #[must_use]
    #[inline]
    pub fn new(shape: Shape, indexed_properties: IndexedProperties) -> Self {
        Self {
            indexed_properties,
            shape,
            storage: Vec::default(),
        }
    }

    /// Construct a [`PropertyMap`] from with the given prototype with an unique [`Shape`].
    #[must_use]
    #[inline]
    pub fn from_prototype_unique_shape(prototype: JsPrototype) -> Self {
        Self {
            indexed_properties: IndexedProperties::default(),
            shape: UniqueShape::new(prototype, PropertyTableInner::default()).into(),
            storage: Vec::default(),
        }
    }

    /// Construct a [`PropertyMap`] from with the given prototype with a shared shape [`Shape`].
    #[must_use]
    #[inline]
    pub fn from_prototype_with_shared_shape(
        root_shape: &RootShape,
        prototype: JsPrototype,
    ) -> Self {
        let shape = root_shape.shape().change_prototype_transition(prototype);
        Self {
            indexed_properties: IndexedProperties::default(),
            shape: shape.into(),
            storage: Vec::default(),
        }
    }

    /// Get the property with the given key from the [`PropertyMap`].
    #[must_use]
    pub fn get(&self, key: &PropertyKey) -> Option<PropertyDescriptor> {
        if let PropertyKey::Index(index) = key {
            return self.indexed_properties.get(index.get());
        }
        if let Some(slot) = self.shape.lookup(key) {
            return Some(self.get_storage(slot));
        }

        None
    }

    /// Get the property with the given key from the [`PropertyMap`].
    #[must_use]
    pub(crate) fn get_with_slot(
        &self,
        key: &PropertyKey,
        out_slot: &mut Slot,
    ) -> Option<PropertyDescriptor> {
        if let PropertyKey::Index(index) = key {
            return self.indexed_properties.get(index.get());
        }
        if let Some(slot) = self.shape.lookup(key) {
            out_slot.index = slot.index;

            // Remove all descriptor attributes, but keep inline caching bits.
            out_slot.attributes = (out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS)
                | slot.attributes
                | SlotAttributes::FOUND;
            return Some(self.get_storage(slot));
        }

        None
    }

    /// Get the property with the given key from the [`PropertyMap`].
    #[must_use]
    pub(crate) fn get_storage(&self, Slot { index, attributes }: Slot) -> PropertyDescriptor {
        let index = index as usize;
        let mut builder = PropertyDescriptor::builder()
            .configurable(attributes.contains(SlotAttributes::CONFIGURABLE))
            .enumerable(attributes.contains(SlotAttributes::ENUMERABLE));
        if attributes.is_accessor_descriptor() {
            if attributes.has_get() {
                builder = builder.get(self.storage[index].clone());
            }
            if attributes.has_set() {
                builder = builder.set(self.storage[index + 1].clone());
            }
        } else {
            builder = builder.writable(attributes.contains(SlotAttributes::WRITABLE));
            builder = builder.value(self.storage[index].clone());
        }
        builder.build()
    }

    /// Insert the given property descriptor with the given key [`PropertyMap`].
    pub fn insert(&mut self, key: &PropertyKey, property: PropertyDescriptor) -> bool {
        let mut dummy_slot = Slot::new();
        self.insert_with_slot(key, property, &mut dummy_slot)
    }

    /// Insert the given property descriptor with the given key [`PropertyMap`].
    pub(crate) fn insert_with_slot(
        &mut self,
        key: &PropertyKey,
        property: PropertyDescriptor,
        out_slot: &mut Slot,
    ) -> bool {
        if let PropertyKey::Index(index) = key {
            return self.indexed_properties.insert(index.get(), property);
        }

        let attributes = property.to_slot_attributes();

        if let Some(slot) = self.shape.lookup(key) {
            let index = slot.index as usize;

            if slot.attributes != attributes {
                let key = TransitionKey {
                    property_key: key.clone(),
                    attributes,
                };
                let transition = self.shape.change_attributes_transition(key);
                self.shape = transition.shape;
                match transition.action {
                    ChangeTransitionAction::Nothing => {}
                    ChangeTransitionAction::Remove => {
                        self.storage.remove(slot.index as usize + 1);
                    }
                    ChangeTransitionAction::Insert => {
                        // insert after index which is (index + 1).
                        self.storage.insert(index, JsValue::undefined());
                    }
                }
            }

            if attributes.is_accessor_descriptor() {
                if attributes.has_get() {
                    self.storage[index] = property
                        .get()
                        .cloned()
                        .map(JsValue::new)
                        .unwrap_or_default();
                }
                if attributes.has_set() {
                    self.storage[index + 1] = property
                        .set()
                        .cloned()
                        .map(JsValue::new)
                        .unwrap_or_default();
                }
            } else {
                self.storage[index] = property.expect_value().clone();
            }
            out_slot.index = slot.index;
            out_slot.attributes =
                (out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | attributes;
            return true;
        }

        let transition_key = TransitionKey {
            property_key: key.clone(),
            attributes,
        };
        self.shape = self.shape.insert_property_transition(transition_key);

        // Make Sure that if we are inserting, it has the correct slot index.
        debug_assert_eq!(
            self.shape.lookup(key),
            Some(Slot {
                index: self.storage.len() as u32,
                attributes
            })
        );

        out_slot.index = self.storage.len() as u32;
        out_slot.attributes =
            (out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | attributes;

        if attributes.is_accessor_descriptor() {
            self.storage.push(
                property
                    .get()
                    .cloned()
                    .map(JsValue::new)
                    .unwrap_or_default(),
            );
            self.storage.push(
                property
                    .set()
                    .cloned()
                    .map(JsValue::new)
                    .unwrap_or_default(),
            );
        } else {
            self.storage
                .push(property.value().cloned().unwrap_or_default());
        }

        false
    }

    /// Remove the property with the given key from the [`PropertyMap`].
    pub fn remove(&mut self, key: &PropertyKey) -> bool {
        if let PropertyKey::Index(index) = key {
            return self.indexed_properties.remove(index.get());
        }
        if let Some(slot) = self.shape.lookup(key) {
            // shift all elements when removing.
            if slot.attributes.is_accessor_descriptor() {
                self.storage.remove(slot.index as usize + 1);
            }
            self.storage.remove(slot.index as usize);

            self.shape = self.shape.remove_property_transition(key);
            return true;
        }

        false
    }

    /// Overrides all the indexed properties, setting it to dense storage.
    pub(crate) fn override_indexed_properties(&mut self, properties: ThinVec<JsValue>) {
        self.indexed_properties = IndexedProperties::DenseElement(properties);
    }

    pub(crate) fn get_dense_property(&self, index: u32) -> Option<JsValue> {
        let index = index as usize;
        match &self.indexed_properties {
            IndexedProperties::DenseI32(properties) => {
                properties.get(index).copied().map(JsValue::from)
            }
            IndexedProperties::DenseF64(properties) => {
                properties.get(index).copied().map(JsValue::from)
            }
            IndexedProperties::DenseElement(properties) => properties.get(index).cloned(),
            IndexedProperties::Sparse(_) => None,
        }
    }

    pub(crate) fn set_dense_property(&mut self, index: u32, value: &JsValue) -> bool {
        let index = index as usize;

        match &mut self.indexed_properties {
            IndexedProperties::DenseI32(properties) => {
                let Some(element) = properties.get_mut(index) else {
                    return false;
                };

                // If it can fit in a i32 and the truncated version is
                // equal to the original then it is an integer.
                let is_rational_integer = |n: f64| n.to_bits() == f64::from(n as i32).to_bits();

                let value = match value.variant() {
                    JsVariant::Integer32(n) => n,
                    JsVariant::Float64(n) if is_rational_integer(n) => n as i32,
                    JsVariant::Float64(value) => {
                        let mut properties = properties
                            .iter()
                            .copied()
                            .map(f64::from)
                            .collect::<ThinVec<_>>();
                        properties[index] = value;
                        self.indexed_properties = IndexedProperties::DenseF64(properties);

                        return true;
                    }
                    _ => {
                        let mut properties = properties
                            .iter()
                            .copied()
                            .map(JsValue::from)
                            .collect::<ThinVec<_>>();
                        properties[index] = value.clone();
                        self.indexed_properties = IndexedProperties::DenseElement(properties);

                        return true;
                    }
                };

                *element = value;
                true
            }
            IndexedProperties::DenseF64(properties) => {
                let Some(element) = properties.get_mut(index) else {
                    return false;
                };

                let Some(value) = value.as_number() else {
                    let mut properties = properties
                        .iter()
                        .copied()
                        .map(JsValue::from)
                        .collect::<ThinVec<_>>();
                    properties[index] = value.clone();
                    self.indexed_properties = IndexedProperties::DenseElement(properties);
                    return true;
                };

                *element = value;
                true
            }
            IndexedProperties::DenseElement(properties) => {
                let Some(element) = properties.get_mut(index) else {
                    return false;
                };
                *element = value.clone();
                true
            }
            IndexedProperties::Sparse(_) => false,
        }
    }

    /// Returns the vec of dense indexed properties if they exist.
    pub(crate) fn to_dense_indexed_properties(&self) -> Option<ThinVec<JsValue>> {
        match &self.indexed_properties {
            IndexedProperties::DenseI32(properties) => {
                Some(properties.iter().copied().map(JsValue::from).collect())
            }
            IndexedProperties::DenseF64(properties) => {
                Some(properties.iter().copied().map(JsValue::from).collect())
            }
            IndexedProperties::DenseElement(properties) => Some(properties.clone()),
            IndexedProperties::Sparse(_) => None,
        }
    }

    /// Returns the vec of dense indexed properties if they exist.
    pub(crate) fn dense_indexed_properties_mut(&mut self) -> Option<&mut ThinVec<JsValue>> {
        if let IndexedProperties::DenseElement(properties) = &mut self.indexed_properties {
            Some(properties)
        } else {
            None
        }
    }

    /// An iterator visiting all indexed key-value pairs in arbitrary order. The iterator element type is `(&'a u32, &'a Property)`.
    ///
    /// This iterator does not recurse down the prototype chain.
    #[inline]
    #[must_use]
    pub fn index_properties(&self) -> IndexProperties<'_> {
        self.indexed_properties.iter()
    }

    /// An iterator visiting all index keys in arbitrary order. The iterator element type is `&'a u32`.
    ///
    /// This iterator does not recurse down the prototype chain.
    #[inline]
    #[must_use]
    pub fn index_property_keys(&self) -> IndexPropertyKeys<'_> {
        self.indexed_properties.keys()
    }

    /// An iterator visiting all index values in arbitrary order. The iterator element type is `&'a Property`.
    ///
    /// This iterator does not recurse down the prototype chain.
    #[inline]
    #[must_use]
    pub fn index_property_values(&self) -> IndexPropertyValues<'_> {
        self.indexed_properties.values()
    }

    /// Returns `true` if the given key is contained in the [`PropertyMap`].
    #[inline]
    #[must_use]
    pub fn contains_key(&self, key: &PropertyKey) -> bool {
        if let PropertyKey::Index(index) = key {
            return self.indexed_properties.contains_key(index.get());
        }
        if self.shape.lookup(key).is_some() {
            return true;
        }

        false
    }
}

/// An iterator over the indexed property entries of an `Object`.
#[derive(Debug, Clone)]
pub enum IndexProperties<'a> {
    /// An iterator over dense i32, Vec backed indexed property entries of an `Object`.
    DenseI32(std::iter::Enumerate<std::slice::Iter<'a, i32>>),

    /// An iterator over dense f64, Vec backed indexed property entries of an `Object`.
    DenseF64(std::iter::Enumerate<std::slice::Iter<'a, f64>>),

    /// An iterator over dense, Vec backed indexed property entries of an `Object`.
    DenseElement(std::iter::Enumerate<std::slice::Iter<'a, JsValue>>),

    /// An iterator over sparse, `HashMap` backed indexed property entries of an `Object`.
    Sparse(hash_map::Iter<'a, u32, PropertyDescriptor>),
}

impl Iterator for IndexProperties<'_> {
    type Item = (u32, PropertyDescriptor);

    fn next(&mut self) -> Option<Self::Item> {
        let (index, value) = match self {
            Self::DenseI32(vec) => vec
                .next()
                .map(|(index, value)| (index, JsValue::from(*value)))?,
            Self::DenseF64(vec) => vec
                .next()
                .map(|(index, value)| (index, JsValue::from(*value)))?,
            Self::DenseElement(vec) => vec.next().map(|(index, value)| (index, value.clone()))?,
            Self::Sparse(map) => return map.next().map(|(index, value)| (*index, value.clone())),
        };

        Some((
            index as u32,
            PropertyDescriptorBuilder::new()
                .writable(true)
                .configurable(true)
                .enumerable(true)
                .value(value.clone())
                .build(),
        ))
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        match self {
            Self::DenseI32(vec) => vec.size_hint(),
            Self::DenseF64(vec) => vec.size_hint(),
            Self::DenseElement(vec) => vec.size_hint(),
            Self::Sparse(map) => map.size_hint(),
        }
    }
}

impl ExactSizeIterator for IndexProperties<'_> {
    #[inline]
    fn len(&self) -> usize {
        match self {
            Self::DenseI32(vec) => vec.len(),
            Self::DenseF64(vec) => vec.len(),
            Self::DenseElement(vec) => vec.len(),
            Self::Sparse(map) => map.len(),
        }
    }
}

impl FusedIterator for IndexProperties<'_> {}

/// An iterator over the index keys (`u32`) of an `Object`.
#[derive(Debug, Clone)]
pub enum IndexPropertyKeys<'a> {
    /// An iterator over dense, Vec backed indexed property entries of an `Object`.
    Dense(std::ops::Range<u32>),

    /// An iterator over sparse, `HashMap` backed indexed property entries of an `Object`.
    Sparse(hash_map::Keys<'a, u32, PropertyDescriptor>),
}

impl Iterator for IndexPropertyKeys<'_> {
    type Item = u32;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        match self {
            Self::Dense(vec) => vec.next(),
            Self::Sparse(map) => map.next().copied(),
        }
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        match self {
            Self::Dense(vec) => vec.size_hint(),
            Self::Sparse(map) => map.size_hint(),
        }
    }
}

impl ExactSizeIterator for IndexPropertyKeys<'_> {
    #[inline]
    fn len(&self) -> usize {
        match self {
            Self::Dense(vec) => vec.len(),
            Self::Sparse(map) => map.len(),
        }
    }
}

impl FusedIterator for IndexPropertyKeys<'_> {}

/// An iterator over the index values (`Property`) of an `Object`.
#[derive(Debug, Clone)]
#[allow(variant_size_differences)]
pub enum IndexPropertyValues<'a> {
    /// An iterator over dense, Vec backed indexed property entries of an `Object`.
    DenseI32(std::slice::Iter<'a, i32>),

    /// An iterator over dense, Vec backed indexed property entries of an `Object`.
    DenseF64(std::slice::Iter<'a, f64>),

    /// An iterator over dense, Vec backed indexed property entries of an `Object`.
    DenseElement(std::slice::Iter<'a, JsValue>),

    /// An iterator over sparse, `HashMap` backed indexed property entries of an `Object`.
    Sparse(hash_map::Values<'a, u32, PropertyDescriptor>),
}

impl Iterator for IndexPropertyValues<'_> {
    type Item = PropertyDescriptor;

    fn next(&mut self) -> Option<Self::Item> {
        let value = match self {
            Self::DenseI32(vec) => vec.next().copied()?.into(),
            Self::DenseF64(vec) => vec.next().copied()?.into(),
            Self::DenseElement(vec) => vec.next().cloned()?,
            Self::Sparse(map) => return map.next().cloned(),
        };

        Some(
            PropertyDescriptorBuilder::new()
                .writable(true)
                .configurable(true)
                .enumerable(true)
                .value(value)
                .build(),
        )
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        match self {
            Self::DenseI32(vec) => vec.size_hint(),
            Self::DenseF64(vec) => vec.size_hint(),
            Self::DenseElement(vec) => vec.size_hint(),
            Self::Sparse(map) => map.size_hint(),
        }
    }
}

impl ExactSizeIterator for IndexPropertyValues<'_> {
    #[inline]
    fn len(&self) -> usize {
        match self {
            Self::DenseI32(vec) => vec.len(),
            Self::DenseF64(vec) => vec.len(),
            Self::DenseElement(vec) => vec.len(),
            Self::Sparse(map) => map.len(),
        }
    }
}
