use rkyv::{
    deserialize,
    with::{AsBox, MapNiche},
};
use std::{
    borrow::Borrow,
    hash::{Hash, Hasher},
};

use crate::{error::Error, intern::serialize_interned, schema::Etymology, serializable};

use super::{EntryRef, MediaURL};

/// Creates an `EntrySet` from a list of elements.
///
/// This macro provides a convenient way to create an `EntrySet` (which is a type alias for `IndexSet<Entry>`)
/// with pre-allocated capacity based on the number of elements provided.
///
/// # Examples
///
/// ```ignore
/// use odict::entryset;
/// use odict::schema::Entry;
///
/// let set = entryset![entry1, entry2, entry3];
/// ```
#[macro_export]
macro_rules! entryset {
    ($($value:expr,)+) => { entryset!($($value),+) };
    ($($value:expr),*) => {
        {
            const CAP: usize = <[()]>::len(&[$({ stringify!($value); }),*]);
            let mut set = $crate::schema::EntrySet::with_capacity(CAP);
            $(
                set.insert($value);
            )*
            set
        }
    };
}

serializable! {
  #[derive(Default)]
  #[serde(rename = "entry")]
  pub struct Entry {
    #[serde(rename = "@term")]
    pub term: String,

    #[serde(rename = "@see")]
    #[rkyv(with = MapNiche<AsBox>)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub see_also: Option<EntryRef>,

    #[serde(default, rename = "ety")]
    pub etymologies: Vec<Etymology>,

    #[serde(default, rename = "media")]
    pub media: Vec<MediaURL>,

    #[serde(rename = "@rank")]
    #[rkyv(with = MapNiche<AsBox>)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rank: Option<u32>,
  }
}

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

impl AsRef<Entry> for Entry {
    fn as_ref(&self) -> &Entry {
        self
    }
}

impl Borrow<str> for Entry {
    fn borrow(&self) -> &str {
        self.term.as_str()
    }
}

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

impl Borrow<str> for ArchivedEntry {
    fn borrow(&self) -> &str {
        self.term.as_str()
    }
}

impl Entry {
    pub fn serialize(&self) -> crate::Result<Vec<u8>> {
        let bytes = serialize_interned::<_, rkyv::rancor::Error>(self)?;
        Ok(bytes.to_vec())
    }
}

impl ArchivedEntry {
    pub fn deserialize(&self) -> crate::Result<Entry> {
        let entry: Entry = deserialize::<Entry, rkyv::rancor::Error>(self)
            .map_err(|e| Error::Deserialize(e.to_string()))?;

        Ok(entry)
    }
}
