use derive_more::Deref;
use rapira::{Rapira, RapiraError};
use serde::Serialize;

use crate::{Cid, Record};

#[derive(Clone, Debug, Serialize, Deref)]
pub struct Entry<Val: Record> {
    #[serde(flatten)]
    pub key: Val::SelfId,
    #[deref]
    #[serde(flatten)]
    pub val: Val,
}

impl<Val: Record> Entry<Val> {
    pub fn new(key: Val::SelfId, val: Val) -> Self {
        Self { key, val }
    }

    pub fn get_id(self) -> Val::SelfId {
        self.key
    }
    pub fn get_val(&self) -> &Val {
        &self.val
    }
    pub fn split(self) -> (Val::SelfId, Val) {
        let Self { key, val } = self;
        (key, val)
    }

    pub fn split_ref(&self) -> (&Val::SelfId, &Val) {
        let Self { key, val } = self;
        (key, val)
    }

    pub fn split_mut(&mut self) -> (&mut Val::SelfId, &mut Val) {
        let Self { key, val } = self;
        (key, val)
    }
}

impl<Val: Record> From<(Val::SelfId, Val)> for Entry<Val> {
    fn from(tuple: (Val::SelfId, Val)) -> Self {
        Self::new(tuple.0, tuple.1)
    }
}

const fn a_b_size(a: Option<usize>, b: Option<usize>) -> Option<usize> {
    match a {
        Some(s) => match b {
            Some(ss) => Some(s + ss),
            None => None,
        },
        None => None,
    }
}

impl<Val> Rapira for Entry<Val>
where
    Val: Record + Rapira,
    <Val::SelfId as Cid>::B: Rapira,
{
    const STATIC_SIZE: Option<usize> =
        a_b_size(Val::STATIC_SIZE, <Val::SelfId as Cid>::B::STATIC_SIZE);
    const MIN_SIZE: usize = {
        let a = Val::MIN_SIZE;
        let b = <Val::SelfId as Cid>::B::MIN_SIZE;
        a + b
    };

    fn size(&self) -> usize {
        match Self::STATIC_SIZE {
            Some(s) => s,
            // None => self.key.bytes().as_ref().len(),
            None => self.key.encode().size() + self.val.size(),
        }
    }

    fn check_bytes(slice: &mut &[u8]) -> rapira::Result<()> {
        <Val::SelfId as Cid>::B::check_bytes(slice)?;
        Val::check_bytes(slice)?;
        Ok(())
    }

    unsafe fn from_slice_unchecked(slice: &mut &[u8]) -> rapira::Result<Self>
    where
        Self: Sized,
    {
        unsafe {
            let key = <Val::SelfId as Cid>::B::from_slice_unchecked(slice)?;
            let key = <Val::SelfId>::from_bytes(key.as_ref())
                .map_err(|_| RapiraError::Other("Cid::from_bytes error"))?;
            let val = Val::from_slice_unchecked(slice)?;
            Ok(Entry::new(key, val))
        }
    }

    fn from_slice(slice: &mut &[u8]) -> rapira::Result<Self>
    where
        Self: Sized,
    {
        let key = <Val::SelfId as Cid>::B::from_slice(slice)?;
        let key = <Val::SelfId>::from_bytes(key.as_ref())
            .map_err(|_| RapiraError::Other("Entry Cid error"))?;
        let val = Val::from_slice(slice)?;
        Ok(Self::new(key, val))
    }

    unsafe fn from_slice_unsafe(slice: &mut &[u8]) -> rapira::Result<Self>
    where
        Self: Sized,
    {
        unsafe {
            let key = <Val::SelfId as Cid>::B::from_slice_unsafe(slice)?;
            let key = <Val::SelfId>::from_bytes_unsafe(key.as_ref());
            let val = Val::from_slice_unsafe(slice)?;
            Ok(Self::new(key, val))
        }
    }

    fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
        self.key.encode().convert_to_bytes(slice, cursor);
        self.val.convert_to_bytes(slice, cursor);
    }

    fn try_convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) -> rapira::Result<()> {
        self.key.encode().try_convert_to_bytes(slice, cursor)?;
        self.val.try_convert_to_bytes(slice, cursor)?;
        Ok(())
    }
}

#[cfg(feature = "ts")]
impl<Val> TypeDef for Entry<Val>
where
    Self: 'static,
    Val: Record + TypeDef,
    Val::SelfId: TypeDef,
{
    const INFO: TypeInfo = TypeInfo::Defined(DefinedTypeInfo {
        def: TypeDefinition {
            docs: None,
            path: &[],
            name: Ident("Entry"),
            generic_vars: &[Ident("T"), Ident("Id")],
            def: TypeExpr::Intersection(TypeIntersection {
                docs: None,
                members: &[TypeExpr::Ref(&Val::SelfId::INFO), TypeExpr::Ref(&Val::INFO)],
            }),
        },
        generic_args: &[],
    });
}

#[cfg(feature = "ts-rs")]
mod fake_id {
    use rapira::Rapira;

    use crate::GetType;

    #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash, ts_rs::TS, Rapira)]
    pub struct Fake {
        data: (),
    }

    impl GetType for Fake {
        const TYPE: crate::Typ = crate::Typ::Void;
    }

    crate::rapira_record!(Fake, crate::Fuid<()>, crate::InlineArray, "fake");
}

// export type Entry<T, ID> = T & ID;
#[cfg(feature = "ts-rs")]
impl<Val: Record> ts_rs::TS for Entry<Val>
where
    Val: ts_rs::TS,
    Val::SelfId: ts_rs::TS,
{
    type OptionInnerType = Self;
    type WithoutGenerics = Entry<fake_id::Fake>;
    fn ident() -> String {
        "Entry".to_owned()
    }
    fn name() -> String {
        {
            let val_name = <Val as ts_rs::TS>::name();
            let id_name = <Val::SelfId as ts_rs::TS>::name();
            format!("Entry<{val_name}, {id_name}>")
        }
    }
    fn decl_concrete() -> String {
        Self::decl()
    }
    fn decl() -> String {
        // let entry = <Entry<Val> as ts_rs::TS>::inline();
        // let val = <Val as ts_rs::TS>::inline();
        // let key = <Val::SelfId as ts_rs::TS>::inline();
        // format!("type Entry<Val, Key> = Val & Key;")
        "type Entry<Val, Key> = Val & Key;".to_string()
    }
    fn inline() -> String {
        <[String]>::join(
            &[
                <Val::SelfId as ts_rs::TS>::inline_flattened(),
                <Val as ts_rs::TS>::inline_flattened(),
            ],
            " & ",
        )
        .replace(" } & { ", " ")
    }
    fn inline_flattened() -> String {
        Self::inline()
    }
    fn visit_generics(v: &mut impl ts_rs::TypeVisitor)
    where
        Self: 'static,
    {
        v.visit::<Val>();
        v.visit::<Val::SelfId>();
        <Val as ts_rs::TS>::visit_generics(v);
        <Val::SelfId as ts_rs::TS>::visit_generics(v);
    }
    fn output_path() -> Option<std::path::PathBuf> {
        Some(std::path::PathBuf::from("entry.ts"))
    }
    fn visit_dependencies(v: &mut impl ts_rs::TypeVisitor)
    where
        Self: 'static,
    {
        <Val as ts_rs::TS>::visit_dependencies(v);
        <Val::SelfId as ts_rs::TS>::visit_dependencies(v);
    }

    fn dependencies() -> Vec<ts_rs::Dependency>
    where
        Self: 'static,
    {
        let mut deps: Vec<ts_rs::Dependency> = vec![];
        struct Visit<'a>(&'a mut Vec<ts_rs::Dependency>);
        impl<'a> ts_rs::TypeVisitor for Visit<'a> {
            fn visit<T: ts_rs::TS + 'static + ?Sized>(&mut self) {
                if let Some(dep) = ts_rs::Dependency::from_ty::<T>() {
                    self.0.push(dep);
                }
            }
        }
        let mut v = Visit(&mut deps);
        <Val as ts_rs::TS>::visit_dependencies(&mut v);
        <Val::SelfId as ts_rs::TS>::visit_dependencies(&mut v);

        vec![]
    }
}
