use std::ptr::NonNull;

use serde::{de, ser};

use super::{super::AnyBaseObject, base_ptr::BasePtr, BaseObjectContainer};
use crate::resource::Resource;

// see `serialize_u64` method in mvalue/src/ser_base_object.rs
impl<T, InheritPtrs: Clone> ser::Serialize for BaseObjectContainer<T, InheritPtrs> {
  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  where
    S: ser::Serializer,
  {
    // here its defaults to null ptr because i don't know how to return custom error here.
    // if ptr is null, mvalue implementation will return InvalidBaseObject error
    let raw_ptr = self.raw_base_ptr().unwrap_or(std::ptr::null_mut());

    serializer.serialize_newtype_struct(mvalue::BASE_OBJECT_MVALUE, &(raw_ptr as u64))
  }
}

struct BaseObjectVisitor;

impl<'de> de::Visitor<'de> for BaseObjectVisitor {
  type Value = AnyBaseObject;

  fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
    write!(formatter, "Base Object MValue")
  }

  fn visit_u64<E>(self, base_object_ptr: u64) -> Result<Self::Value, E>
  where
    E: de::Error,
  {
    let Some(ptr) = NonNull::new(base_object_ptr as altv_sdk::BaseObjectRawMutPtr) else {
      return Err(E::custom(
        "Base object is invalid (it has probably already been destroyed)",
      ));
    };

    let Some(base_object) = Resource::with_base_objects_ref(|v, _| v.get_by_ptr(ptr)) else {
      return Err(E::custom(
        "Base object is not found in the pool (it has probably already been destroyed)",
      ));
    };

    Ok(base_object)
  }
}

impl<'de> de::Deserialize<'de> for AnyBaseObject {
  fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  where
    D: de::Deserializer<'de>,
  {
    deserializer.deserialize_newtype_struct(mvalue::BASE_OBJECT_MVALUE, BaseObjectVisitor)
  }
}

#[macro_export]
macro_rules! __impl_mvalue_deserialize_for_base_object {
  ($container:ty, $class_name:ident, $module_name:ident) => {
    mod $module_name {
      use serde::{de};
      use std::ptr::NonNull;
      use super::*;
      use $crate::resource::Resource;

      struct BaseObjectVisitor;

      impl<'de> de::Visitor<'de> for BaseObjectVisitor {
        type Value = $container;

        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
          write!(formatter, "{} MValue", stringify!($class_name))
        }

        fn visit_u64<E>(self, base_object_ptr: u64) -> Result<Self::Value, E>
        where
          E: de::Error,
        {
          let Some(ptr) = NonNull::new(base_object_ptr as altv_sdk::BaseObjectRawMutPtr) else {
            return Err(E::custom(format!(
              "{} is invalid (it has probably already been destroyed)",
              stringify!($class_name)
            )));
          };

          let Some(base_object) = Resource::with_base_objects_ref(|v, _| v.get_by_ptr(ptr)) else {
            return Err(E::custom(format!(
              "{} is not found in the pool (it has probably already been destroyed)",
              stringify!($class_name)
            )));
          };

          match base_object {
            AnyBaseObject::$class_name(v) => Ok(v),
            _ => Err(E::custom(format!(
              "This base object is not {}",
              stringify!($class_name)
            ))),
          }
        }
      }

      impl<'de> de::Deserialize<'de> for $container {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
          D: de::Deserializer<'de>,
        {
          deserializer.deserialize_newtype_struct(mvalue::BASE_OBJECT_MVALUE, BaseObjectVisitor)
        }
      }
    }
  };
}

pub use __impl_mvalue_deserialize_for_base_object as impl_deserialize_for;
