//! The [`CharacterController`] component — a kinematic capsule character.
//!
//! A character controller is *not* a rigid body: it never reacts to forces.
//! Instead the game asks it to move by some desired translation each frame and
//! the controller resolves that against the world's colliders — sliding along
//! walls, stepping up small ledges, sticking to the ground on slopes, and
//! refusing to climb anything too steep — then reports whether the character
//! ended up grounded. It is the basis for player/NPC movement and is reused by
//! the Stage-15 prototyping kit.
//!
//! The character is a **capsule** (radius + cylindrical half-height along `+Y`).
//! An entity with this component is moved through
//! [`PhysicsWorld::move_character`](crate::PhysicsWorld::move_character); it does
//! **not** carry a [`RigidBody`](crate::RigidBody)/[`Collider`](crate::Collider)
//! (it isn't simulated), so it never appears in the body set and never
//! self-collides.

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

/// Component: a kinematic capsule character moved by move-and-slide.
///
/// Tune the capsule shape (`radius`/`half_height`) and the movement rules
/// (`max_slope_degrees`, `step_offset`, `snap_to_ground`, `skin_width`). Pass an
/// entity carrying this to
/// [`PhysicsWorld::move_character`](crate::PhysicsWorld::move_character).
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)]
pub struct CharacterController {
    /// Capsule radius.
    pub radius: f32,
    /// Capsule cylindrical half-height (the straight segment between the two
    /// hemispherical caps), along local `+Y`.
    pub half_height: f32,
    /// Steepest ground slope (degrees from horizontal) the character will walk
    /// up; steeper surfaces are treated as walls.
    pub max_slope_degrees: f32,
    /// Tallest step/ledge the character will automatically climb. `0` disables
    /// auto-stepping.
    pub step_offset: f32,
    /// Distance below the feet within which the character snaps down to stay
    /// grounded (prevents bouncing down stairs/slopes). `0` disables snapping.
    pub snap_to_ground: f32,
    /// A small collision-detection margin kept around the capsule.
    pub skin_width: f32,
}

impl Default for CharacterController {
    fn default() -> Self {
        Self {
            radius: 0.3,
            half_height: 0.6,
            max_slope_degrees: 45.0,
            step_offset: 0.3,
            snap_to_ground: 0.2,
            skin_width: 0.01,
        }
    }
}

impl CharacterController {
    /// The total half-height of the capsule (cylinder half-height + one radius),
    /// i.e. the distance from the capsule center to either tip.
    pub fn total_half_height(&self) -> f32 {
        self.half_height + self.radius
    }

    /// The local-space offset from the capsule center down to the feet (the
    /// bottom tip), useful for placing/grounding the character.
    pub fn foot_offset(&self) -> Vec3 {
        Vec3::new(0.0, -self.total_half_height(), 0.0)
    }
}

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

    #[test]
    fn default_is_a_reasonable_humanoid_capsule() {
        let c = CharacterController::default();
        assert_eq!(c.radius, 0.3);
        assert!((c.total_half_height() - 0.9).abs() < 1e-6);
        assert!((c.foot_offset() - Vec3::new(0.0, -0.9, 0.0)).length() < 1e-6);
    }

    #[test]
    fn round_trips_through_ron() {
        let c = CharacterController {
            radius: 0.4,
            half_height: 1.0,
            max_slope_degrees: 50.0,
            step_offset: 0.0,
            snap_to_ground: 0.0,
            skin_width: 0.02,
        };
        let ron = ron::to_string(&c).unwrap();
        let back: CharacterController = ron::from_str(&ron).unwrap();
        assert_eq!(c, back);
    }
}
