use core::borrow::Borrow;
use core::cmp::Ordering;
use core::hash;

use rust_alloc::sync::Arc;

use serde::{Deserialize, Serialize};

use crate::alloc::prelude::*;
use crate::alloc::HashMap;
use crate::item::Item;
use crate::runtime::{FieldMap, TypeInfo, Value};
use crate::{Hash, ItemBuf};

/// Field accessor for a variant struct.
#[doc(hidden)]
pub struct Accessor<'a> {
    pub(crate) fields: &'a HashMap<Box<str>, usize>,
    pub(crate) data: &'a [Value],
}

impl Accessor<'_> {
    /// Get a field through the accessor.
    #[doc(hidden)]
    pub fn get<Q>(&self, key: &Q) -> Option<&Value>
    where
        Box<str>: Borrow<Q>,
        Q: hash::Hash + Eq + ?Sized,
    {
        self.data.get(*self.fields.get(key)?)
    }
}

/// The kind of value stored.
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum RttiKind {
    /// The value stored is empty.
    Empty,
    /// The value stored is a tuple.
    Tuple,
    /// The value stored is a strict.
    Struct,
}

/// Runtime information on variant.
#[derive(Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Rtti {
    /// The kind of value.
    pub(crate) kind: RttiKind,
    /// The type hash of the type.
    pub(crate) hash: Hash,
    /// If this type is a variant, designates the hash of the variant.
    pub(crate) variant_hash: Hash,
    /// The item of the type.
    pub(crate) item: ItemBuf,
    /// Mapping from field names to their corresponding indexes.
    pub(crate) fields: FieldMap<Box<str>, usize>,
}

impl Rtti {
    /// Test if this RTTI matches the given raw hashes.
    #[inline]
    pub(crate) fn is(&self, hash: Hash, variant_hash: Hash) -> bool {
        self.hash == hash && self.variant_hash == variant_hash
    }

    /// Access the item of the RTTI.
    #[inline]
    pub fn item(&self) -> &Item {
        &self.item
    }

    /// Access the type hash of the RTTI.
    #[inline]
    pub fn type_hash(&self) -> Hash {
        self.hash
    }

    /// Access the type information for the RTTI.
    #[inline]
    pub fn type_info(self: Arc<Self>) -> TypeInfo {
        TypeInfo::rtti(self)
    }
}

impl PartialEq for Rtti {
    fn eq(&self, other: &Self) -> bool {
        self.hash == other.hash && self.variant_hash == other.variant_hash
    }
}

impl Eq for Rtti {}

impl hash::Hash for Rtti {
    fn hash<H: hash::Hasher>(&self, state: &mut H) {
        self.hash.hash(state);
        self.variant_hash.hash(state);
    }
}

impl PartialOrd for Rtti {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Rtti {
    #[inline]
    fn cmp(&self, other: &Self) -> Ordering {
        self.hash
            .cmp(&other.hash)
            .then_with(|| self.variant_hash.cmp(&other.variant_hash))
    }
}
