use std::{fmt::Debug, ptr::NonNull, rc::Rc};

use super::{
  base_impl::mvalue::impl_deserialize_for,
  extra_pools::{Entity, WorldObject},
  pool_funcs::BaseObjectPoolFuncs,
  BaseObjectContainer, BaseObjectId, BaseObjectManager, BaseObjectWrapper,
};
use crate::{col_shape::ColShapy, sdk};

macro_rules! base_objects {
  (@internal $(
    $manager_name_snake:ident
    $name_struct:ident
    $name_container:ident
    $name_ptr:ident
    $manager_name_rc:ident
    $manager_name:ident: [
      $base_type:path,
      $(
        @inherit_classes: $inherit_ptrs_struct:ty, [ $(
          $impl_trait:ty,
        )+ ],
      )?
    ],
  )+ ) => {
    paste::paste! {
    $(
      pub mod $manager_name_snake {
        use super::*;

        pub type $name_struct = sdk::alt::[<I $manager_name>];

        #[doc = "[Implementation](struct.BaseObjectWrapper.html#" [<$manager_name:lower>] "-implementation)"]
        pub type $manager_name = BaseObjectWrapper<
          $name_struct
          $(, $crate::base_objects::inherit_ptrs::$inherit_ptrs_struct )?
        >;

        // TODO: refactor this shit, this only needed for meta
        #[allow(dead_code)]
        pub(crate) type $manager_name_rc = Rc<$manager_name>;

        pub type $name_container = BaseObjectContainer<
          $name_struct
          $(, $crate::base_objects::inherit_ptrs::$inherit_ptrs_struct )?
        >;

        #[allow(dead_code)]
        pub type $name_ptr = NonNull<$name_struct>;

        impl Debug for $name_container {
          fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            write!(f, "{} {{ ... }}", stringify!($manager_name))
          }
        }

        impl BaseObjectPoolFuncs<$name_container> for $manager_name {
          fn all() -> Vec<$name_container> {
            $crate::resource::Resource::with_base_objects_ref(|v, _| v.[<$manager_name_snake>].all())
          }

          fn all_count() -> usize {
            $crate::resource::Resource::with_base_objects_ref(|v, _| v.[<$manager_name_snake>].all_count())
          }

          fn get_by_id(id: BaseObjectId) -> Option<$name_container> {
            $crate::resource::Resource::with_base_objects_ref(|v, _| {
              v.[<$manager_name_snake>].get_by_id(id)
            })
          }
        }
      $( $(
        impl $impl_trait <$crate::base_objects::inherit_ptrs::$inherit_ptrs_struct> for $manager_name {}
      )+ )?

        impl_deserialize_for!($name_container, $manager_name, [<$manager_name_snake _mvalue_deserialize_impl>]);

        #[macro_export]
        macro_rules! [<__ $manager_name_snake _remove_from_pool>] {
          ($base_object:expr) => {
            $crate::resource::Resource::with_base_objects_mut(|mut v, _| -> $crate::VoidResult {
              use $crate::base_objects::BasePtr;
              let base_ptr = $base_object.base_ptr()?;
              v.[<$manager_name_snake>].remove(base_ptr, $base_object.ptr()?)?;
              Ok(())
            })
          };
        }

        #[allow(unused_imports)]
        pub(crate) use [<__ $manager_name_snake _remove_from_pool>] as remove_from_pool;

        #[macro_export]
        macro_rules! [<__ $manager_name_snake _add_to_pool>] {
          ($ptr:expr) => {
            $crate::resource::Resource::with_base_objects_mut(|mut v, _| {
              let base_ptr = std::ptr::NonNull::new(unsafe {
                $crate::sdk::$manager_name_snake::to_base_object($ptr.as_ptr())
              }).unwrap();

              let inherit_ptrs = $crate::helpers::if_not!(
                ($(
                  unsafe { $crate::base_objects::inherit_ptrs::$inherit_ptrs_struct::new(base_ptr.as_ptr()) }
                )?) {}
              );

              let container = Self::_new($ptr, base_ptr, inherit_ptrs);

              v.[<$manager_name_snake>].add(base_ptr, $ptr, container.clone());

              container
            })
          };
        }

        #[allow(unused_imports)]
        pub(crate) use [<__ $manager_name_snake _add_to_pool>] as add_to_pool;
      }
    )+

      #[derive(Debug)]
      pub enum AnyBaseObject { $(
        $manager_name($manager_name_snake::$name_container),
      )+ }

      #[derive(Default)]
      pub struct Store {
      $(
        pub(crate) $manager_name_snake: BaseObjectManager<
          $manager_name_snake::$name_struct
          $(, $crate::base_objects::inherit_ptrs::$inherit_ptrs_struct )?
        >,
      )+
      }

      impl Store {
        pub fn on_create(
          &mut self,
          base_ptr: NonNull<sdk::alt::IBaseObject>,
          base_object_type: altv_sdk::BaseObjectType,
        ) {
          match base_object_type {
          $(
            $base_type => {
              let ptr = $crate::helpers::base_ptr_to!(base_ptr.as_ptr(), $manager_name_snake);
              if self.[<$manager_name_snake>].has(ptr) {
                logger::debug!("base object: {base_object_type:?} {ptr:?} already added");
                return;
              }

              let inherit_ptrs = $crate::helpers::if_not!(
                ($(unsafe {
                  $crate::base_objects::inherit_ptrs::$inherit_ptrs_struct::new(base_ptr.as_ptr())
                })?) {}
              );

              let container = $manager_name_snake::$manager_name::_new(
                ptr,
                base_ptr,
                inherit_ptrs
              );

              self.[<$manager_name_snake>].add(
                base_ptr,
                ptr,
                container.clone(),
              );
            }
          )+
            _ => {
              logger::error!("unknown base object type: {base_object_type:?}")
            }
          }
        }

        pub fn on_remove(
          &mut self,
          base_ptr: NonNull<sdk::alt::IBaseObject>,
          base_object_type: altv_sdk::BaseObjectType,
        ) {
          match base_object_type {
          $(
            $base_type => {
              let ptr = $crate::helpers::base_ptr_to!(base_ptr.as_ptr(), $manager_name_snake);
              self.[<$manager_name_snake>].remove_externally(base_ptr, ptr).unwrap();
            }
          )+
            _ => {
              logger::error!("unknown base object type: {base_object_type:?}")
            }
          }
        }

        pub fn get_by_ptr(
          &self,
          base_ptr: NonNull<sdk::alt::IBaseObject>,
        ) -> Option<AnyBaseObject> {
          let base_object_type = unsafe { altv_sdk::helpers::get_base_object_type(base_ptr.as_ptr()) };

          match base_object_type {
          $(
            $base_type => {
              let ptr = $crate::helpers::base_ptr_to!(base_ptr.as_ptr(), $manager_name_snake);
              let base_object = self.[<$manager_name_snake>].get_by_ptr(ptr);
              if let Some(base_object) = base_object {
                Some(AnyBaseObject::$manager_name(base_object))
              } else {
                logger::debug!("base object type: {base_object_type:?} is not in pool");
                None
              }
            }
          )+
            _ => {
              logger::error!("unknown base object type: {base_object_type:?}");
              None
            }
          }
        }

        pub fn get_by_id(
          &self,
          base_object_type: altv_sdk::BaseObjectType,
          id: BaseObjectId,
        ) -> Option<AnyBaseObject> {
          match base_object_type {
          $(
            $base_type => {
              let base_object = self.[<$manager_name_snake>].get_by_id(id);
              if let Some(base_object) = base_object {
                Some(AnyBaseObject::$manager_name(base_object))
              } else {
                None
              }
            }
          )+
            _ => {
              logger::error!("unknown base object type: {base_object_type:?}");
              None
            }
          }
        }

        pub fn all(&self) -> Vec<AnyBaseObject> {
          let capacity = self.all_count();
          let mut objects = Vec::with_capacity(capacity);
        $(
          let objects_of_type = self.[<$manager_name_snake>].all();
          objects.extend(&mut objects_of_type.into_iter().map(AnyBaseObject::$manager_name));
        )+
          objects
        }

        pub fn all_count(&self) -> usize {
          let mut count = 0;
        $(
          count += self.[<$manager_name_snake>].all_count();
        )+
          count
        }
      }
    }
  };

  ( $(
    $manager_name:ident: [
      $base_type:path,
      $(
        @inherit_classes: $inherit_ptrs_struct:ty, [ $(
          $impl_trait:ty,
        )+ ],
      )?
    ],
  )+ ) => {
    paste::paste! {
      base_objects!(@internal $(
        [<$manager_name:snake>]
        [<$manager_name Struct>]
        [<$manager_name Container>]
        [<$manager_name MutPtr>]
        [<$manager_name Rc>]
        $manager_name: [
          $base_type,
          $( @inherit_classes: $inherit_ptrs_struct, [ $(
            $impl_trait,
          )+ ], )?
        ],
      )+ );
    }
  };
}

base_objects!(
  ColShape: [
    altv_sdk::BaseObjectType::Colshape,
    @inherit_classes: WorldColShape, [
      WorldObject,
      ColShapy,
    ],
  ],
  Vehicle: [
    altv_sdk::BaseObjectType::Vehicle,
    @inherit_classes: WorldEntity, [
      WorldObject,
      Entity,
    ],
  ],
  Player: [
    altv_sdk::BaseObjectType::Player,
    @inherit_classes: WorldEntity, [
      WorldObject,
      Entity,
    ],
  ],
  Ped: [
    altv_sdk::BaseObjectType::Ped,
    @inherit_classes: WorldEntity, [
      WorldObject,
      Entity,
    ],
  ],
  Object: [
    altv_sdk::BaseObjectType::Object,
    @inherit_classes: WorldEntity, [
      WorldObject,
      Entity,
    ],
  ],
  VirtualEntity: [
    altv_sdk::BaseObjectType::VirtualEntity,
    @inherit_classes: WorldObject, [
      WorldObject,
    ],
  ],
  VirtualEntityGroup: [
    altv_sdk::BaseObjectType::VirtualEntityGroup,
  ],
  Blip: [
    altv_sdk::BaseObjectType::Blip,
    @inherit_classes: WorldObject, [
      WorldObject,
    ],
  ],
  VoiceChannel: [
    altv_sdk::BaseObjectType::VoiceChannel,
  ],
  Marker: [
    altv_sdk::BaseObjectType::Marker,
    @inherit_classes: WorldObject, [
      WorldObject,
    ],
  ],
  Checkpoint: [
    altv_sdk::BaseObjectType::Checkpoint,
    @inherit_classes: WorldColShape, [
      WorldObject,
      ColShapy,
    ],
  ],
  ConnectionInfo: [
    altv_sdk::BaseObjectType::ConnectionInfo,
  ],
);

impl Debug for Store {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "base objects Store")
  }
}

#[derive(Debug, Default)]
pub struct PendingDestroyOrCreation;
