use crate::HasKey;
use alloc::borrow::Cow;
use alloc::vec::Vec;
use core::{
    any::TypeId,
    cell::Cell,
    mem::{align_of, size_of},
};

/// A named or unnamed field, offset from the base of its containing type.
///
/// TODO(docs): examples of fields in some types, showing how the shape
/// transparency works.
#[derive(Clone, Copy, Debug)]
pub struct Field<'db> {
    /// offset relative to the containing type
    pub offset: usize,
    pub name: Option<&'static str>, // this could be an index into the parent list...
    #[cfg(feature = "attrs")]
    pub attrs: &'db [Attr<'db>],
    pub id: TypeId,
    pub shape: DataShape<'db>,
}

/// An `#[attribute]`.
///
/// The structure is lightly recursive, such that an attribute is also the things
/// inside a list-like attribute eg `#[derive(Serialize, Deserialize)]` both `Serialize`
// and `Deserialize` are `Attr::Name`.
#[derive(Clone, Copy, Debug)]
pub enum Attr<'db> {
    // #[foo]
    Name(&'db str),
    // #[foo(...)]
    List(&'db str, &'db [&'db Attr<'db>]),
    // #[bar = ...]
    NameValue(&'db str, &'db RustPrimitive<'db>),
}

/// A primitive Rust value that can occur in an attribute.
#[derive(Clone, Copy, Debug)]
pub enum RustPrimitive<'db> {
    Str(&'db str),
    ByteStr(&'db [u8]),
    Byte(u8),
    Char(char),
    Int(i128),
    Float(f64),
    Bool(bool),
}

/// A Rust type, of some kind, with a unique `TypeId`.
#[derive(Clone, Debug)]
pub struct ReflectedType<'db> {
    pub id: TypeId,
    pub name: &'static str,
    pub layout: core::alloc::Layout,
    #[cfg(feature = "attrs")]
    pub attrs: Cow<'db, [Attr<'db>]>,
    pub shape: DataShape<'db>,
    // TODO: why isn't the fieldstorage here?
}

/// An arm of an enum declaration, describing a variant.
#[derive(Clone, Copy, Debug)]
pub struct EnumArm<'db> {
    /// Name of the variant in the source code
    pub label: &'static str,
    /// Index into the list of variants, in source order.
    pub variant_index: u16,
    /// The discriminant value for variants like this.
    pub discriminant: u16,
    #[cfg(feature = "attrs")]
    pub attrs: &'db Attr<'db>,
    pub decl_kind: DeclKind,
    pub fields: ExpectArr<Field<'db>>,
}

#[derive(Clone, Copy, Debug)]
#[repr(u8)]
/// We don't care much how the source code is written, but serde does.
pub enum DeclKind {
    Unit,
    Tuple,
    Struct,
    Newtype,
}

#[derive(Clone, Copy, Debug)]
/// Token indicating what type to expect in the field metadata.
pub struct Expect<T>(core::marker::PhantomData<T>);
#[derive(Clone, Copy, Debug)]
/// Token indicating length and type of an array to expect in the field metadata.
pub struct ExpectArr<T> {
    // keeps datashape smaller at least
    pub len: u16,
    pub _marker: core::marker::PhantomData<[T]>,
}

/// The sorts of things in a FieldsCursor that can be expected,
/// but owned.
///
/// This is unused in the API, but someday you will be able
/// to pass Expectables to some method to encode a new
/// [FieldsStorage.
pub enum Expectables<'db> {
    Field(Field<'db>),
    Shape(DataShape<'db>),
    Shapes(Vec<DataShape<'db>>),
    EnumArms(Vec<EnumArm<'db>>),
}

/// The world of rust type representation, according to reflectopus.
///
/// Unions are not supported. Intentionally compatible with the serde data model.
// TODO: shrink this enum,
#[derive(Clone, Copy, Debug)]
pub enum DataShape<'db> {
    /// The reflection system will go no further with this type.
    ///
    /// For serde integration, leafs types are sought after
    /// in the reflection database when they are encountered.
    Leaf(TypeId),
    /// A small builtin rust type like i128 or char.
    Builtin(RustBuiltin),
    /// `[T; n]`
    FixedArray {
        shape: Expect<DataShape<'db>>,
        len: usize,
        stride: usize,
    },
    /// `&[T]`
    Slice(Expect<DataShape<'db>>),
    // TODO: if it is a fat pointer, what do i want to do about it?
    /// `&T`. ⚠️⚠️ might be a fat pointer! ⚠️⚠️
    Ref(Expect<DataShape<'db>>, RefKind),
    Tuple(ExpectArr<Field<'db>>),
    Enum(&'static [&'static str], ExpectArr<EnumArm<'db>>),
    Struct(DeclKind, &'static [&'static str], ExpectArr<Field<'db>>),
}

#[derive(Clone, Copy, Debug)]
/// Whether a reference is a fat or thin pointer.
pub enum RefKind {
    Fat,
    Thin,
}

#[derive(Clone, Copy, Debug)]
/// Builtin scalar rust types (u8, char, bool, etc)
pub enum RustBuiltin {
    U8,
    I8,
    U16,
    I16,
    U32,
    I32,
    U64,
    I64,
    U128,
    I128,
    F32,
    F64,
    BOOLIN,
    CHAR,
}

/// A cursor into the in-memory densely packed field metadata
/// for a particular type.
///
/// Using this directly is not advised, it is very easy to misuse.
/// The reflect generates code that drives this cursor for writing
/// the reflection db during startup, and there are readers of
/// this metadata in the serde implementations.
///
/// # Safety
///
/// This can both read and write, and does no bounds checking
/// in release mode. It essentially transmutes every time it
/// is touched. Be cautious!
///
/// During debug builds, the type IDs will be recorded and verified
/// to try and ensure runtime type safety but this is mostly for the
/// the tests. If the state of the field visitor and the datashape
/// ever start disagreeing, lots of corruption will be had.
#[derive(Clone)]
pub struct FieldsCursor<'db> {
    pub(crate) cursor: Cell<*mut u8>,
    pub(crate) _marker: core::marker::PhantomData<&'db ()>,
}

#[allow(missing_docs)]
#[allow(clippy::clippy::missing_safety_doc)]
impl<'db> FieldsCursor<'db> {
    unsafe fn advance<T>(&self) {
        self.cursor
            .set(self.cursor.get().wrapping_add(size_of::<T>()));
    }

    unsafe fn align<T>(&self) {
        self.cursor.set(
            self.cursor
                .get()
                .wrapping_add(self.cursor.get().align_offset(align_of::<T>())),
        );
    }

    unsafe fn verify_type<T: 'static>(&self) {
        #[cfg(debug_assertions)]
        {
            let actual_type = self.cursor.get().cast::<TypeId>().read_unaligned();
            let expected_type = TypeId::of::<T>();
            debug_assert_eq!(actual_type, expected_type);
            self.advance::<TypeId>();
        }
    }

    unsafe fn write_type<T: 'static>(&self) {
        self.cursor
            .get()
            .cast::<TypeId>()
            .write_unaligned(TypeId::of::<T>());
        self.advance::<TypeId>();
    }

    unsafe fn write_any<T: HasKey>(&self, val: T) {
        self.write_type::<T::Key>();
        self.align::<T>();

        self.cursor.get().cast::<T>().write(val);
    }

    pub unsafe fn array<T: HasKey>(&self, len: &ExpectArr<T>) -> &'db [T] {
        self.verify_type::<T::Key>();
        self.align::<T>();

        let slice = core::slice::from_raw_parts(self.cursor.get().cast(), len.len as usize);

        // SAFETY: i _think_ this is what the reference says the layout must be?
        // otherwise it needs to be aligned?
        let total_advancement = size_of::<T>() * len.len as usize;

        self.cursor
            .set(self.cursor.get().wrapping_add(total_advancement));

        slice
    }

    unsafe fn scan_any<T: HasKey>(&self, advance: bool) -> &'db T {
        let c = self.cursor.get();

        self.verify_type::<T::Key>();
        self.align::<T>();

        let v = &*(self.cursor.get() as *const T);

        if !advance {
            self.cursor.set(c);
        }

        v
    }

    /// get a reference to the T stored here, resetting the cursor back
    /// to its original state such that the next yield will return the same
    /// reference.
    unsafe fn read_any<T: HasKey>(&self) -> &'db T {
        let v = self.scan_any(true);

        self.cursor
            .set(self.cursor.get().wrapping_add(size_of::<T>()));

        v
    }

    pub unsafe fn write_enum_arm(&self, arm: EnumArm<'db>) {
        self.write_any(arm)
    }

    pub unsafe fn write_field(&self, field: Field<'db>) {
        self.write_any(field);
    }

    pub unsafe fn write_shape(&self, shape: DataShape<'db>) {
        self.write_any(shape);
    }

    pub unsafe fn shape(&self, _: &Expect<DataShape<'db>>) -> &'db DataShape<'db> {
        self.read_any()
    }

    pub unsafe fn field(&self, _: &Expect<Field<'db>>) -> &'db Field<'db> {
        self.read_any()
    }

    pub unsafe fn peek_first_field(&self, _: &ExpectArr<Field<'db>>) -> &'db Field<'db> {
        self.scan_any(false)
    }
}
