//! `character_capsule` — a runnable tour of the Stage 9 character controller.
//!
//! Run with:
//! ```sh
//! cargo run -p oxide-examples --bin character_capsule
//! ```
//!
//! This example has no window or GPU dependency. It builds a floor with a low
//! step and a wall, then drives a kinematic capsule character through a scripted
//! routine — walk forward, climb the step, jump, and push into the wall — and
//! prints its position and grounded state, so move-and-slide, auto-step,
//! grounding, and jumping can be reviewed by eye.

use oxide_engine::prelude::*;
use oxide_physics::{CharacterController, Collider, PhysicsModule, PhysicsWorld, RigidBody};

/// Gravity acceleration (m/s²) applied to the character's vertical velocity.
const GRAVITY: f32 = 9.81;
/// Upward speed (m/s) imparted by a jump.
const JUMP_SPEED: f32 = 4.5;

fn main() {
    env_logger::init();
    println!("Oxide physics demo — Stage 9 capsule character controller\n");

    let mut app = App::new();
    app.add_modules(DefaultModules);
    app.add_module(PhysicsModule);

    // Floor (top at y = 0.5).
    spawn_static_box(&mut app, "floor", Vec3::ZERO, Vec3::new(20.0, 0.5, 20.0));
    // A 0.2 m step (top at y = 0.7) in front of the start position.
    spawn_static_box(
        &mut app,
        "step",
        Vec3::new(3.0, 0.35, 0.0),
        Vec3::new(1.5, 0.35, 5.0),
    );
    // A wall further along (near face at x = 7.5).
    spawn_static_box(
        &mut app,
        "wall",
        Vec3::new(8.0, 2.0, 0.0),
        Vec3::new(0.5, 2.0, 5.0),
    );

    // The character: a capsule starting on the floor (rest center ≈ y 1.4).
    let player = app.scene.spawn(
        "player",
        Transform::from_translation(Vec3::new(0.0, 1.4, 0.0)),
    );
    app.scene
        .world_mut()
        .insert_one(player, CharacterController::default())
        .unwrap();

    // Register the static world once so the query pipeline is populated.
    app.update(1.0 / 60.0);

    println!("Driving the character (walk → climb step → jump → into wall):\n");
    println!(
        "  {:>5}  {:>7} {:>7}  {:>8}  note",
        "step", "x", "y", "grounded"
    );

    let dt = 1.0 / 60.0;
    let mut vy = 0.0f32; // vertical velocity carried between frames
    for step in 0..300 {
        // Always push forward (+X); request a jump once, mid-run, while grounded.
        let want_jump = step == 150;

        // Read the grounded state from the previous resolved move to decide
        // jumping and gravity reset.
        let grounded = drive(&mut app, player, &mut vy, 2.0, want_jump, dt);

        if step % 25 == 0 || step == 150 {
            let p = app.scene.world_transform(player).unwrap().translation;
            let note = match step {
                0 => "start: walking forward",
                150 => "JUMP!",
                _ if !grounded => "airborne",
                _ if p.x > 6.5 => "blocked by the wall",
                _ if p.y > 1.5 => "up on the step",
                _ => "",
            };
            println!(
                "  {:>5}  {:>7.3} {:>7.3}  {:>8}  {}",
                step, p.x, p.y, grounded, note
            );
        }
    }

    let end = app.scene.world_transform(player).unwrap().translation;
    println!(
        "\nFinal position: x = {:.2}, y = {:.2}. The capsule walked forward, stepped",
        end.x, end.y
    );
    println!("up onto the ledge, jumped, and was stopped by the wall (x never passes ~7.2).");
}

/// One control tick: integrate gravity into `vy` (jumping if asked & grounded),
/// move the character, apply the resolved translation, and return whether it is
/// grounded afterwards.
fn drive(
    app: &mut App,
    player: Entity,
    vy: &mut f32,
    forward_speed: f32,
    want_jump: bool,
    dt: f32,
) -> bool {
    // Integrate gravity into the vertical velocity; the controller clamps the
    // resulting downward motion against the floor and reports grounded.
    *vy -= GRAVITY * dt;
    let desired = Vec3::new(forward_speed * dt, *vy * dt, 0.0);

    let movement = {
        let world = app.get_resource::<PhysicsWorld>().unwrap();
        world
            .move_character(&app.scene, player, desired, dt)
            .unwrap()
    };

    // Apply the collision-corrected translation.
    let mut t = app.scene.local_transform(player).unwrap();
    t.translation += movement.translation;
    app.scene.set_local_transform(player, t);

    // On the ground: cancel downward velocity (and allow a jump this frame).
    if movement.grounded && *vy < 0.0 {
        *vy = 0.0;
    }
    if want_jump && movement.grounded {
        *vy = JUMP_SPEED;
    }
    movement.grounded
}

/// Spawns a static box collider (world geometry) at `pos` with `half_extents`.
fn spawn_static_box(app: &mut App, name: &str, pos: Vec3, half_extents: Vec3) -> Entity {
    let e = app.scene.spawn(name, Transform::from_translation(pos));
    app.scene
        .world_mut()
        .insert_one(e, RigidBody::static_body())
        .unwrap();
    app.scene
        .world_mut()
        .insert_one(e, Collider::cuboid(half_extents))
        .unwrap();
    e
}
