use bitflags::bitflags;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum RigidBodyType {
    #[default]
    Dynamic,
    Fixed,
    KinematicPositionBased,
    KinematicVelocityBased,
}

#[cfg(feature = "physics")]
impl From<RigidBodyType> for rapier3d::prelude::RigidBodyType {
    fn from(value: RigidBodyType) -> Self {
        match value {
            RigidBodyType::Dynamic => rapier3d::prelude::RigidBodyType::Dynamic,
            RigidBodyType::Fixed => rapier3d::prelude::RigidBodyType::Fixed,
            RigidBodyType::KinematicPositionBased => {
                rapier3d::prelude::RigidBodyType::KinematicPositionBased
            }
            RigidBodyType::KinematicVelocityBased => {
                rapier3d::prelude::RigidBodyType::KinematicVelocityBased
            }
        }
    }
}

#[cfg(feature = "physics")]
impl From<rapier3d::prelude::RigidBodyType> for RigidBodyType {
    fn from(value: rapier3d::prelude::RigidBodyType) -> Self {
        match value {
            rapier3d::prelude::RigidBodyType::Dynamic => RigidBodyType::Dynamic,
            rapier3d::prelude::RigidBodyType::Fixed => RigidBodyType::Fixed,
            rapier3d::prelude::RigidBodyType::KinematicPositionBased => {
                RigidBodyType::KinematicPositionBased
            }
            rapier3d::prelude::RigidBodyType::KinematicVelocityBased => {
                RigidBodyType::KinematicVelocityBased
            }
        }
    }
}

bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
    pub struct LockedAxes: u8 {
        const TRANSLATION_LOCKED_X = 0b0000_0001;
        const TRANSLATION_LOCKED_Y = 0b0000_0010;
        const TRANSLATION_LOCKED_Z = 0b0000_0100;
        const ROTATION_LOCKED_X = 0b0000_1000;
        const ROTATION_LOCKED_Y = 0b0001_0000;
        const ROTATION_LOCKED_Z = 0b0010_0000;
        const TRANSLATION_LOCKED = Self::TRANSLATION_LOCKED_X.bits() | Self::TRANSLATION_LOCKED_Y.bits() | Self::TRANSLATION_LOCKED_Z.bits();
        const ROTATION_LOCKED = Self::ROTATION_LOCKED_X.bits() | Self::ROTATION_LOCKED_Y.bits() | Self::ROTATION_LOCKED_Z.bits();
    }
}

#[cfg(feature = "physics")]
impl From<LockedAxes> for rapier3d::prelude::LockedAxes {
    fn from(value: LockedAxes) -> Self {
        rapier3d::prelude::LockedAxes::from_bits_truncate(value.bits())
    }
}

#[cfg(feature = "physics")]
impl From<rapier3d::prelude::LockedAxes> for LockedAxes {
    fn from(value: rapier3d::prelude::LockedAxes) -> Self {
        LockedAxes::from_bits_truncate(value.bits())
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct InteractionGroups {
    pub memberships: u32,
    pub filter: u32,
}

impl Default for InteractionGroups {
    fn default() -> Self {
        Self::all()
    }
}

impl InteractionGroups {
    pub fn new(memberships: u32, filter: u32) -> Self {
        Self {
            memberships,
            filter,
        }
    }

    pub fn all() -> Self {
        Self {
            memberships: u32::MAX,
            filter: u32::MAX,
        }
    }

    pub fn none() -> Self {
        Self {
            memberships: 0,
            filter: 0,
        }
    }
}

#[cfg(feature = "physics")]
impl From<InteractionGroups> for rapier3d::prelude::InteractionGroups {
    fn from(value: InteractionGroups) -> Self {
        rapier3d::prelude::InteractionGroups::new(
            rapier3d::prelude::Group::from_bits_truncate(value.memberships),
            rapier3d::prelude::Group::from_bits_truncate(value.filter),
        )
    }
}

#[cfg(feature = "physics")]
impl From<rapier3d::prelude::InteractionGroups> for InteractionGroups {
    fn from(value: rapier3d::prelude::InteractionGroups) -> Self {
        Self {
            memberships: value.memberships.bits(),
            filter: value.filter.bits(),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RigidBodyHandle(pub u64);

#[cfg(feature = "physics")]
impl From<rapier3d::prelude::RigidBodyHandle> for RigidBodyHandle {
    fn from(value: rapier3d::prelude::RigidBodyHandle) -> Self {
        let (index, generation) = value.into_raw_parts();
        Self(((generation as u64) << 32) | (index as u64))
    }
}

#[cfg(feature = "physics")]
impl From<RigidBodyHandle> for rapier3d::prelude::RigidBodyHandle {
    fn from(value: RigidBodyHandle) -> Self {
        let index = (value.0 & 0xFFFF_FFFF) as u32;
        let generation = ((value.0 >> 32) & 0xFFFF_FFFF) as u32;
        rapier3d::prelude::RigidBodyHandle::from_raw_parts(index, generation)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ColliderHandle(pub u64);

#[cfg(feature = "physics")]
impl From<rapier3d::prelude::ColliderHandle> for ColliderHandle {
    fn from(value: rapier3d::prelude::ColliderHandle) -> Self {
        let (index, generation) = value.into_raw_parts();
        Self(((generation as u64) << 32) | (index as u64))
    }
}

#[cfg(feature = "physics")]
impl From<ColliderHandle> for rapier3d::prelude::ColliderHandle {
    fn from(value: ColliderHandle) -> Self {
        let index = (value.0 & 0xFFFF_FFFF) as u32;
        let generation = ((value.0 >> 32) & 0xFFFF_FFFF) as u32;
        rapier3d::prelude::ColliderHandle::from_raw_parts(index, generation)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CharacterControllerConfig {
    pub offset: f32,
    pub max_slope_climb_angle: f32,
    pub min_slope_slide_angle: f32,
    pub autostep_max_height: Option<f32>,
    pub autostep_min_width: Option<f32>,
    pub autostep_include_dynamic_bodies: bool,
    pub snap_to_ground: Option<f32>,
}

impl Default for CharacterControllerConfig {
    fn default() -> Self {
        Self {
            offset: 0.01,
            max_slope_climb_angle: 45_f32.to_radians(),
            min_slope_slide_angle: 30_f32.to_radians(),
            autostep_max_height: Some(0.3),
            autostep_min_width: Some(0.2),
            autostep_include_dynamic_bodies: false,
            snap_to_ground: Some(0.2),
        }
    }
}

#[cfg(feature = "physics")]
impl CharacterControllerConfig {
    pub fn to_rapier(&self, scale: f32) -> rapier3d::control::KinematicCharacterController {
        rapier3d::control::KinematicCharacterController {
            offset: rapier3d::control::CharacterLength::Absolute(self.offset * scale),
            up: nalgebra::Vector3::y_axis(),
            max_slope_climb_angle: self.max_slope_climb_angle,
            min_slope_slide_angle: self.min_slope_slide_angle,
            autostep: self.autostep_max_height.map(|max_height| {
                rapier3d::control::CharacterAutostep {
                    max_height: rapier3d::control::CharacterLength::Absolute(max_height * scale),
                    min_width: rapier3d::control::CharacterLength::Absolute(
                        self.autostep_min_width.unwrap_or(0.2) * scale,
                    ),
                    include_dynamic_bodies: self.autostep_include_dynamic_bodies,
                }
            }),
            snap_to_ground: self
                .snap_to_ground
                .map(|d| rapier3d::control::CharacterLength::Absolute(d * scale)),
            ..Default::default()
        }
    }
}
