// Avatar — unified character representation combining skeleton, locomotion,
// spatial events, culling profile, and physics configuration.
//
// Bridges the engine's FBX import data with the GPU's skeletal animation
// and dreamlet rendering systems.

use crate::dream_animations::{LocomotionController, Skeleton, SkeletonInstance};
use crate::observer::QuantumCullProfile;
use crate::spatial_event::SpatialEventMap;

/// A fully configured avatar with skeleton, locomotion, and gameplay bindings.
pub struct Avatar {
    /// Unique avatar ID (matches dreamlet_id in the catalog).
    pub dreamlet_id: u32,
    /// Skeleton definition (shared across all instances of this avatar type).
    pub skeleton: Skeleton,
    /// Locomotion controller (maps input to animation clips).
    pub locomotion: LocomotionController,
    /// Spatial events attached to this avatar (footsteps, impacts, etc.).
    pub spatial_events: SpatialEventMap,
    /// Quantum culling profile (which topology layers this avatar is visible on).
    pub cull_profile: QuantumCullProfile,
    /// Physics configuration.
    pub physics: AvatarPhysics,
    /// Input scheme for this avatar.
    pub input_scheme: InputScheme,
}

/// Physics configuration for an avatar.
pub struct AvatarPhysics {
    /// Capsule radius for collision.
    pub radius: f32,
    /// Capsule height (total, not half).
    pub height: f32,
    /// Maximum step-up height.
    pub step_height: f32,
    /// Maximum walkable slope angle in degrees.
    pub slope_limit_degrees: f32,
    /// Mass in kg (for physics interactions).
    pub mass: f32,
}

impl Default for AvatarPhysics {
    fn default() -> Self {
        Self {
            radius: 0.3,
            height: 1.8,
            step_height: 0.35,
            slope_limit_degrees: 45.0,
            mass: 70.0,
        }
    }
}

/// Input scheme for an avatar (how controls map to locomotion).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputScheme {
    /// Standard WASD + mouse look.
    ThirdPerson,
    /// Top-down click-to-move.
    TopDown,
    /// Side-scroller (left/right + jump).
    SideScroller,
    /// Vehicle controls (throttle + steering).
    Vehicle,
}

impl Default for InputScheme {
    fn default() -> Self {
        InputScheme::ThirdPerson
    }
}

impl Avatar {
    /// Create an avatar from FBX import data.
    ///
    /// Sets up skeleton, default locomotion controller (5 clips mapped to 16 states),
    /// empty spatial events, default culling profile, and standard physics.
    pub fn from_fbx(dreamlet_id: u32, bones: &[dreamwell_engine::fbx::FbxBone], clip_count: usize) -> Self {
        let skeleton = Skeleton::from_fbx(bones);

        // Map the first 5 clips to the locomotion controller.
        // If fewer clips are available, map all states to clip 0.
        let (idle, walk, run, jump, crouch) = if clip_count >= 5 {
            (0, 1, 2, 3, 4)
        } else if clip_count >= 1 {
            (0, 0, 0, 0, 0)
        } else {
            (0, 0, 0, 0, 0)
        };

        let locomotion = LocomotionController::new(idle, walk, run, jump, crouch);

        Self {
            dreamlet_id,
            skeleton,
            locomotion,
            spatial_events: SpatialEventMap::new(),
            cull_profile: QuantumCullProfile::default(),
            physics: AvatarPhysics::default(),
            input_scheme: InputScheme::ThirdPerson,
        }
    }

    /// Update the avatar's spatial events by dt.
    pub fn update(&mut self, dt: f32) {
        self.spatial_events.update(dt);
    }

    /// Evaluate bone transforms from a skeleton instance.
    /// Returns the world-space matrix palette.
    pub fn bone_transforms<'a>(&self, instance: &'a SkeletonInstance) -> &'a [[f32; 16]] {
        &instance.bone_transforms
    }

    /// Number of bones in the avatar's skeleton.
    pub fn bone_count(&self) -> usize {
        self.skeleton.bone_count()
    }
}

/// Waymark-serializable avatar defaults for content packs.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct AvatarDefaults {
    pub radius: f32,
    pub height: f32,
    pub step_height: f32,
    pub slope_limit_degrees: f32,
    pub mass: f32,
    #[serde(default = "default_input_scheme")]
    pub input_scheme: String,
}

fn default_input_scheme() -> String {
    "ThirdPerson".into()
}

impl Default for AvatarDefaults {
    fn default() -> Self {
        Self {
            radius: 0.3,
            height: 1.8,
            step_height: 0.35,
            slope_limit_degrees: 45.0,
            mass: 70.0,
            input_scheme: default_input_scheme(),
        }
    }
}

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

    fn identity_mat() -> [f32; 16] {
        [
            1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
        ]
    }

    #[test]
    fn from_fbx_constructs_valid_avatar() {
        let bones = vec![
            FbxBone {
                name: "root".into(),
                parent_index: None,
                bind_pose: identity_mat(),
            },
            FbxBone {
                name: "spine".into(),
                parent_index: Some(0),
                bind_pose: identity_mat(),
            },
            FbxBone {
                name: "head".into(),
                parent_index: Some(1),
                bind_pose: identity_mat(),
            },
        ];
        let avatar = Avatar::from_fbx(42, &bones, 5);
        assert_eq!(avatar.dreamlet_id, 42);
        assert_eq!(avatar.bone_count(), 3);
        assert_eq!(avatar.input_scheme, InputScheme::ThirdPerson);
        assert!(avatar.cull_profile.is_layer_visible(0));
        assert!((avatar.physics.height - 1.8).abs() < 0.001);
    }

    #[test]
    fn waymark_roundtrip() {
        let defaults = AvatarDefaults::default();
        let json = serde_json::to_string(&defaults).unwrap();
        let back: AvatarDefaults = serde_json::from_str(&json).unwrap();
        assert!((back.radius - 0.3).abs() < 0.001);
        assert_eq!(back.input_scheme, "ThirdPerson");
    }
}
