use crate::{metadata::*, FieldsCursor, FieldsStorage};
use crate::{Db, ShapedLocation, TraitObject};

use alloc::{borrow::Cow, string::String};
use core::{any::TypeId, intrinsics::transmute};
use serde::ser::Error as serError;
use serde::ser::{
    SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple, SerializeTupleStruct,
    SerializeTupleVariant,
};

impl<'db> Db<'db> {
    fn serialize_leaf<S: serde::Serializer>(
        &self,
        s: S,
        tid: TypeId,
        src: &ShapedLocation<'_, '_, '_>,
    ) -> Result<S::Ok, S::Error> {
        let vtable = *self.serialize_vtables.get(&tid).unwrap();
        let obj = unsafe {
            // SAFETY: the vtable is correct because we populated it correctly in register_leaf.
            transmute::<TraitObject, &dyn erased_serde::Serialize>(TraitObject {
                vtable,
                data: src.ptr as *mut _,
            })
        };
        erased_serde::serialize(obj, s)
    }

    fn serialize_fields<'visitor, 'data, I: IntoIterator<Item = &'db Field<'db>>, Err>(
        &'db self,
        mut f: impl FnMut(Option<&'static str>, ShapedLocation<'db, 'data, 'visitor>) -> Result<(), Err>,
        fields_of: &'visitor ShapedLocation<'db, 'data, 'visitor>,
        fields: I,
        ty_err: &impl Fn(String) -> Err,
    ) -> Result<(), Err> {
        for &Field {
            offset,
            shape: _shape,
            id,
            name,
            ..
        } in fields.into_iter()
        {
            // TODO: use the shape for free performance!
            let (ty, storage) = self.lookup_id(id).map_err(ty_err)?;
            unsafe {
                f(
                    name,
                    ShapedLocation::new(
                        &ty,
                        FieldsStorage::from_inner(Cow::Borrowed(&storage.area)),
                        fields_of.ptr.add(offset),
                    ),
                )?;
            }
        }
        Ok(())
    }
}

/// Wrapper for serializing a value from memory via reflection. You're better off using `Db::serialize`.
pub(crate) struct Serialize<'db, 'data, 'visitor> {
    db: &'db Db<'db>,
    cursor: &'visitor FieldsCursor<'db>,
    src: &'visitor ShapedLocation<'db, 'data, 'visitor>,
    name: &'static str,
}

impl<'db, 'data, 'visitor> Serialize<'db, 'data, 'visitor> {
    pub(crate) fn new(
        db: &'db Db<'db>,
        name: &'static str,
        cursor: &'visitor FieldsCursor<'db>,
        src: &'visitor ShapedLocation<'db, 'data, 'visitor>,
    ) -> Self {
        Self {
            db,
            cursor,
            src,
            name,
        }
    }

    pub(crate) fn subfield<'v2: 'visitor>(
        &self,
        field_location: &'v2 ShapedLocation<'db, 'data, 'visitor>,
    ) -> Serialize<'db, 'data, 'v2> {
        Self {
            src: field_location,
            ..*self
        }
    }
}

impl<'db, 'data, 'visitor> serde::Serialize for Serialize<'db, 'data, 'visitor> {
    fn serialize<S>(&self, dst: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let src = &self.src;
        let db = self.db;
        let cursor = self.cursor;
        let name = self.name;
        match &src.shape {
            DataShape::Leaf(tid) => db.serialize_leaf(dst, *tid, src),
            DataShape::Tuple(fields) => {
                let mut tup = dst.serialize_tuple(fields.len as usize)?;
                db.serialize_fields(
                    |_, src| tup.serialize_element(&self.subfield(&src)),
                    src,
                    unsafe { cursor.array(fields) },
                    &serError::custom,
                )?;
                tup.end()
            }

            DataShape::Struct(decl_kind, _labels_for_serde, fields) => match decl_kind {
                DeclKind::Unit => dst.serialize_unit_struct(name),
                DeclKind::Tuple => {
                    let mut tup = dst.serialize_tuple_struct(name, fields.len as usize)?;
                    db.serialize_fields(
                        |_, src| tup.serialize_field(&self.subfield(&src)),
                        src,
                        unsafe { cursor.array(fields) },
                        &serError::custom,
                    )?;
                    tup.end()
                }
                DeclKind::Struct => {
                    let mut struc = dst.serialize_struct(name, fields.len as usize)?;
                    db.serialize_fields(
                        |field_name, field_loc| {
                            struc.serialize_field(
                                field_name.expect("who unnamed the struct fields?"),
                                &self.subfield(&field_loc),
                            )
                        },
                        src,
                        unsafe { cursor.array(fields) },
                        &serError::custom,
                    )?;
                    struc.end()
                }
                DeclKind::Newtype => {
                    dst.serialize_newtype_struct(
                        name,
                        &self.subfield(
                            // SAFETY: newtype is its one field,
                            unsafe { &src.reshape(&cursor.peek_first_field(fields).shape) },
                        ),
                    )
                }
            },
            DataShape::Enum(_labels, arms) => {
                // SAFETY: we know we're looking at an enum.
                let (tag, variants) = unsafe { (src.read_discriminant(), cursor.array(arms)) };

                let arm_idx = variants.binary_search_by_key(&tag, |arm| arm.discriminant);
                let arm = match arm_idx {
                    // SAFETY: we just got ix
                    Ok(ix) => variants.get(ix).unwrap(),
                    Err(_) => {
                        return Err(serError::custom(
                            "runtime discriminant not described in reflection db",
                        ))
                    }
                };
                match &arm.decl_kind {
                    DeclKind::Unit => {
                        dst.serialize_unit_variant(name, arm.variant_index as u32, arm.label)
                    }
                    DeclKind::Struct => {
                        let mut struc = dst.serialize_struct_variant(
                            name,
                            arm.variant_index as u32,
                            arm.label,
                            arm.fields.len as usize,
                        )?;
                        db.serialize_fields(
                            |name, src| {
                                struc.serialize_field(
                                    name.expect("who unnamed the struct fields?"),
                                    &self.subfield(&src),
                                )
                            },
                            src,
                            unsafe { cursor.array(&arm.fields) },
                            &serError::custom,
                        )?;
                        struc.end()
                    }
                    DeclKind::Tuple => {
                        let mut tup = dst.serialize_tuple_variant(
                            name,
                            arm.variant_index as u32,
                            arm.label,
                            arm.fields.len as usize,
                        )?;
                        db.serialize_fields(
                            |_, src| tup.serialize_field(&self.subfield(&src)),
                            src,
                            unsafe { cursor.array(&arm.fields) },
                            &serError::custom,
                        )?;
                        tup.end()
                    }
                    DeclKind::Newtype => {
                        dst.serialize_newtype_variant(
                            name,
                            arm.variant_index as u32,
                            arm.label,
                            // SAFETY: a newtype is its one field
                            unsafe {
                                &self.subfield(
                                    &src.reshape(&cursor.peek_first_field(&arm.fields).shape),
                                )
                            },
                        )
                    }
                }
            }
            &&DataShape::FixedArray { len, stride, shape } => {
                //let (typ, storage) = db.lookup_id(type_id).map_err(serError::custom)?;
                let mut seq = dst.serialize_seq(Some(len))?;
                let shape = unsafe { cursor.shape(&shape) };
                for ix in 0..len {
                    // SAFETY: ix is inbounds, if stride is correct we're good!
                    let slot_loc = unsafe {
                        ShapedLocation::from_shape(
                            shape,
                            self.src.fields.borrow(),
                            src.ptr.add(ix * stride).cast(),
                        )
                    };
                    seq.serialize_element(&Serialize::new(db, self.name, cursor, &slot_loc))?;
                }
                seq.end()
            }
            DataShape::Slice(_) => todo!(),
            DataShape::Ref(_, _) => todo!(),
            DataShape::Builtin(builtin) => {
                macro_rules! serial {
                    ($what:ident, $($lo:ident,$ser:ident,$up:ident);+) => {
                        match $what {
                            $(RustBuiltin::$up => { dst.$ser(src.ptr.cast::<$lo>().read()) },)+
                        }
                    }
                }
                unsafe {
                    serial!(
                        builtin,
                        u8, serialize_u8, U8;
                        i8, serialize_i8, I8;
                        u16, serialize_u16, U16;
                        i16, serialize_i16, I16;
                        u32, serialize_u32, U32;
                        i32, serialize_i32, I32;
                        u64, serialize_u64, U64;
                        i64, serialize_i64, I64;
                        u128, serialize_u128, U128;
                        i128, serialize_i128, I128;
                        f32, serialize_f32, F32;
                        f64, serialize_f64, F64;
                        bool, serialize_bool, BOOLIN;
                        char, serialize_char, CHAR
                    )
                }
            }
        }
    }
}
