//! The [`RigidBody`] component and its [`RigidBodyKind`].
//!
//! A rigid body is the dynamics half of a physics object: it says *how* an
//! entity moves under the simulation (or that it does not). The *shape* it
//! collides with is a separate [`Collider`](crate::Collider) component, so the
//! two concerns compose — a body with no collider is a point mass, a collider
//! with no body is static world geometry.
//!
//! Like every Oxide component these are plain serializable data with
//! `#[derive(Reflect)]`, so they are editable from the inspector and from
//! scripts with no per-type editor code. The simulation reads them when it
//! builds the rapier world; the
//! authored fields here are inputs, never live simulation state (velocities and
//! poses live on the [`Transform`](oxide_engine::math::Transform) and inside
//! rapier).

use oxide_engine::reflect::{Reflect, ReflectEnum};
use serde::{Deserialize, Serialize};

/// How a [`RigidBody`] participates in the simulation.
///
/// Mirrors rapier's body types, named for engine users:
/// - [`Dynamic`](Self::Dynamic) — fully simulated; moved by gravity, forces,
///   and contacts.
/// - [`Kinematic`](Self::Kinematic) — moved only by the game (its
///   [`Transform`](oxide_engine::math::Transform)); pushes dynamic bodies but is
///   never pushed back. The basis for the character controller (Stage 9 later
///   piece) and for moving platforms.
/// - [`Static`](Self::Static) — never moves; immovable world geometry (floors,
///   walls). The cheapest body — no integration cost.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, ReflectEnum)]
pub enum RigidBodyKind {
    /// Fully simulated — gravity, forces, and contacts move it.
    #[default]
    Dynamic,
    /// Moved only by the game; affects dynamics but is not affected by them.
    Kinematic,
    /// Immovable world geometry.
    Static,
}

impl RigidBodyKind {
    /// Every kind, for editor menus.
    pub const ALL: [RigidBodyKind; 3] = [
        RigidBodyKind::Dynamic,
        RigidBodyKind::Kinematic,
        RigidBodyKind::Static,
    ];

    /// A human-readable label.
    pub fn label(self) -> &'static str {
        match self {
            RigidBodyKind::Dynamic => "Dynamic",
            RigidBodyKind::Kinematic => "Kinematic",
            RigidBodyKind::Static => "Static",
        }
    }
}

/// Component: the dynamics of a physics object.
///
/// Attach alongside a [`Collider`](crate::Collider) to make an entity take part
/// in the simulation. The fields are authoring inputs consumed when the rapier
/// body is created; runtime velocity/pose are not stored here (the
/// [`Transform`](oxide_engine::math::Transform) is the authoritative pose, which
/// the simulation writes back each step).
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)]
pub struct RigidBody {
    /// How the body participates in the simulation.
    pub kind: RigidBodyKind,
    /// Mass in kilograms. Ignored for non-dynamic bodies. `0` means "derive the
    /// mass from the collider's [`density`](crate::Collider::density)".
    pub mass: f32,
    /// Linear velocity damping (drag), `0` = none. Slows translation over time.
    pub linear_damping: f32,
    /// Angular velocity damping, `0` = none. Slows rotation over time.
    pub angular_damping: f32,
    /// Per-body multiplier on global gravity (`1` = normal, `0` = floats,
    /// negative = anti-gravity). Lets some bodies ignore gravity individually.
    pub gravity_scale: f32,
    /// Enable continuous collision detection — prevents fast bodies tunnelling
    /// through thin geometry, at extra cost. Off by default.
    pub ccd: bool,
}

impl Default for RigidBody {
    fn default() -> Self {
        Self {
            kind: RigidBodyKind::Dynamic,
            // 0 = derive mass from collider density (rapier's default behaviour).
            mass: 0.0,
            linear_damping: 0.0,
            angular_damping: 0.0,
            gravity_scale: 1.0,
            ccd: false,
        }
    }
}

impl RigidBody {
    /// A static (immovable) body — convenience for world geometry.
    pub fn static_body() -> Self {
        Self {
            kind: RigidBodyKind::Static,
            ..Self::default()
        }
    }

    /// A kinematic body — moved by the game, not by the simulation.
    pub fn kinematic() -> Self {
        Self {
            kind: RigidBodyKind::Kinematic,
            ..Self::default()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn default_is_a_dynamic_body() {
        let b = RigidBody::default();
        assert_eq!(b.kind, RigidBodyKind::Dynamic);
        assert_eq!(b.gravity_scale, 1.0);
        assert!(!b.ccd);
    }

    #[test]
    fn constructors_set_the_kind() {
        assert_eq!(RigidBody::static_body().kind, RigidBodyKind::Static);
        assert_eq!(RigidBody::kinematic().kind, RigidBodyKind::Kinematic);
    }

    #[test]
    fn round_trips_through_ron() {
        let b = RigidBody {
            kind: RigidBodyKind::Kinematic,
            mass: 2.5,
            linear_damping: 0.1,
            angular_damping: 0.2,
            gravity_scale: 0.0,
            ccd: true,
        };
        let ron = ron::to_string(&b).unwrap();
        let back: RigidBody = ron::from_str(&ron).unwrap();
        assert_eq!(b, back);
    }

    #[test]
    fn kind_variants_are_reflected_for_a_combo() {
        // The editor lists these in a dropdown via ReflectEnum.
        assert_eq!(
            RigidBodyKind::variants(),
            &["Dynamic", "Kinematic", "Static"]
        );
    }
}
