use flecs_ecs::prelude::ComponentId;
use flecs_ecs::prelude::*;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};

pub mod codec;
use codec::json::JsonValueCodec;

#[cfg(feature = "arrow_rs")]
use codec::arrow::ArrowSnapshotFactory;

pub type CompIdFn = fn(&World) -> Option<u64>;
pub type CompRegFn = fn(&World) -> u64;

pub fn short_type_name<T>() -> &'static str {
    std::any::type_name::<T>()
        .rsplit("::")
        .next()
        .unwrap_or("unknown")
}

#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)]
pub enum SnapshotMode {
    #[default]
    Full,
    Placeholder,
    PlaceholderEmplaceIfNotExists,
}

#[derive(Clone, Debug)]
pub struct SnapshotFactory {
    pub js_value: JsonValueCodec,
    #[cfg(feature = "arrow_rs")]
    pub arrow: Option<ArrowSnapshotFactory>,
    pub comp_id: CompIdFn,
    pub register: CompRegFn,
    pub mode: SnapshotMode,
}

#[cfg(feature = "arrow_rs")]
macro_rules! arrow_ext {
    ($text:ty) => {
        $text
    };
}

#[cfg(not(feature = "arrow_rs"))]
macro_rules! arrow_ext {
    ($text:ty) => {
        ()
    };
}

macro_rules! feature_expr {
    ($feature:literal, $expr:expr) => {{
        #[cfg(feature = $feature)]
        {
            $expr
        }
        #[cfg(not(feature = $feature))]
        {
            ()
        }
    }};
}
type SnapshotTuple = (JsonValueCodec, arrow_ext!(Option<ArrowSnapshotFactory>));
impl SnapshotFactory {
    #[inline]
    #[allow(unused_variables)]
    fn from_mode_tuple(
        mode: SnapshotMode,
        comp_id: CompIdFn,
        register: CompRegFn,
        parts: SnapshotTuple,
    ) -> Self {
        let (js_value, arrow) = parts;
        SnapshotFactory {
            js_value,
            #[cfg(feature = "arrow_rs")]
            arrow,
            mode,
            comp_id,
            register,
        }
    }
}

macro_rules! build_common {
    ($t:ty ) => {
        (SnapshotFactory::component_id::<$t>, |world| {
            <$t>::get_id(world)
        })
    };
}

impl SnapshotFactory {
    #[inline]
    fn component_id<T: ComponentId>(world: &World) -> Option<u64> {
        Some(T::get_id(world))
    }
    pub fn new<T>(mode: SnapshotMode) -> Self
    where
        T: Serialize + DeserializeOwned + ComponentId + DataComponent + 'static,
    {
        let (comp_id, register): (CompIdFn, CompRegFn) = build_common!(T);
        let js = JsonValueCodec::new::<T>();
        let arrow = feature_expr!("arrow_rs", Some(ArrowSnapshotFactory::new::<T>()));
        SnapshotFactory::from_mode_tuple(mode, comp_id, register, (js, arrow))
    }
    pub fn new_with_wrapper<T, T1>(mode: SnapshotMode) -> Self
    where
        T: ComponentId + From<T1> + DataComponent,
        T1: Serialize + DeserializeOwned + for<'a> From<&'a T>,
    {
        let (comp_id, register): (CompIdFn, CompRegFn) = build_common!(T);

        let js = JsonValueCodec::new_with::<T, T1>();
        let arrow = feature_expr!("arrow_rs", Some(ArrowSnapshotFactory::new_with::<T, T1>()));
        return SnapshotFactory::from_mode_tuple(mode, comp_id, register, (js, arrow));
    }
}
