use serde::{Deserialize, Serialize};

use crate::ecs::physics::components::{ColliderComponent, ColliderShape, RigidBodyComponent};
use crate::ecs::physics::types::{InteractionGroups, LockedAxes, RigidBodyType};

use super::asset_uuid::AssetUuid;

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

impl SceneBodyType {
    pub fn to_body_type(self) -> RigidBodyType {
        match self {
            SceneBodyType::Static => RigidBodyType::Fixed,
            SceneBodyType::Dynamic => RigidBodyType::Dynamic,
            SceneBodyType::KinematicPositionBased => RigidBodyType::KinematicPositionBased,
            SceneBodyType::KinematicVelocityBased => RigidBodyType::KinematicVelocityBased,
        }
    }
}

impl From<RigidBodyType> for SceneBodyType {
    fn from(body_type: RigidBodyType) -> Self {
        match body_type {
            RigidBodyType::Fixed => SceneBodyType::Static,
            RigidBodyType::Dynamic => SceneBodyType::Dynamic,
            RigidBodyType::KinematicPositionBased => SceneBodyType::KinematicPositionBased,
            RigidBodyType::KinematicVelocityBased => SceneBodyType::KinematicVelocityBased,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScenePhysics {
    pub body_type: SceneBodyType,
    pub collider: SceneCollider,
    #[serde(default = "default_friction")]
    pub friction: f32,
    #[serde(default = "default_restitution")]
    pub restitution: f32,
    #[serde(default)]
    pub mass: Option<f32>,
    #[serde(default)]
    pub is_sensor: bool,
    #[serde(default = "default_collision_membership")]
    pub collision_membership: u32,
    #[serde(default = "default_collision_filter")]
    pub collision_filter: u32,
    #[serde(default = "default_solver_membership")]
    pub solver_membership: u32,
    #[serde(default = "default_solver_filter")]
    pub solver_filter: u32,
    #[serde(default)]
    pub locked_axes: LockedAxes,
}

fn default_collision_membership() -> u32 {
    0xFFFFFFFF
}

fn default_collision_filter() -> u32 {
    0xFFFFFFFF
}

fn default_solver_membership() -> u32 {
    0xFFFFFFFF
}

fn default_solver_filter() -> u32 {
    0xFFFFFFFF
}

fn default_friction() -> f32 {
    0.8
}

fn default_restitution() -> f32 {
    0.1
}

impl Default for ScenePhysics {
    fn default() -> Self {
        Self {
            body_type: SceneBodyType::Static,
            collider: SceneCollider::Cuboid {
                half_extents: [0.5, 0.5, 0.5],
            },
            friction: default_friction(),
            restitution: default_restitution(),
            mass: None,
            is_sensor: false,
            collision_membership: default_collision_membership(),
            collision_filter: default_collision_filter(),
            solver_membership: default_solver_membership(),
            solver_filter: default_solver_filter(),
            locked_axes: LockedAxes::default(),
        }
    }
}

impl ScenePhysics {
    pub fn static_cuboid(half_extents: [f32; 3]) -> Self {
        Self {
            collider: SceneCollider::Cuboid { half_extents },
            ..Default::default()
        }
    }

    pub fn dynamic_ball(radius: f32, mass: f32) -> Self {
        Self {
            body_type: SceneBodyType::Dynamic,
            collider: SceneCollider::Ball { radius },
            friction: 0.7,
            restitution: 0.3,
            mass: Some(mass),
            ..Default::default()
        }
    }

    pub fn dynamic_cuboid(half_extents: [f32; 3], mass: f32) -> Self {
        Self {
            body_type: SceneBodyType::Dynamic,
            collider: SceneCollider::Cuboid { half_extents },
            friction: 0.7,
            restitution: 0.2,
            mass: Some(mass),
            ..Default::default()
        }
    }

    pub fn to_rigid_body(&self) -> RigidBodyComponent {
        let mut body = match self.body_type {
            SceneBodyType::Static => RigidBodyComponent::new_static(),
            SceneBodyType::Dynamic => RigidBodyComponent::new_dynamic(),
            SceneBodyType::KinematicPositionBased => RigidBodyComponent::new_kinematic(),
            SceneBodyType::KinematicVelocityBased => RigidBodyComponent {
                body_type: RigidBodyType::KinematicVelocityBased,
                ..Default::default()
            },
        };
        if let Some(mass) = self.mass {
            body = body.with_mass(mass);
        }
        body.locked_axes = self.locked_axes;
        body
    }

    pub fn to_collider(&self) -> ColliderComponent {
        let mut collider = match &self.collider {
            SceneCollider::Cuboid { half_extents } => {
                ColliderComponent::new_cuboid(half_extents[0], half_extents[1], half_extents[2])
            }
            SceneCollider::Ball { radius } => ColliderComponent::new_ball(*radius),
            SceneCollider::Cylinder {
                half_height,
                radius,
            } => ColliderComponent::new_cylinder(*half_height, *radius),
            SceneCollider::Capsule {
                half_height,
                radius,
            } => ColliderComponent::new_capsule(*half_height, *radius),
            SceneCollider::TriMesh { vertices, indices } => ColliderComponent {
                shape: ColliderShape::TriMesh {
                    vertices: vertices.clone(),
                    indices: indices.clone(),
                },
                ..Default::default()
            },
            SceneCollider::ConvexHull { points } => ColliderComponent {
                shape: ColliderShape::ConvexMesh {
                    vertices: points.clone(),
                },
                ..Default::default()
            },
            SceneCollider::Cone {
                half_height,
                radius,
            } => ColliderComponent {
                shape: ColliderShape::Cone {
                    half_height: *half_height,
                    radius: *radius,
                },
                ..Default::default()
            },
            SceneCollider::HeightField {
                nrows,
                ncols,
                heights,
                scale,
            } => ColliderComponent {
                shape: ColliderShape::HeightField {
                    nrows: *nrows,
                    ncols: *ncols,
                    heights: heights.clone(),
                    scale: *scale,
                },
                ..Default::default()
            },
        };
        collider = collider
            .with_friction(self.friction)
            .with_restitution(self.restitution);
        collider.is_sensor = self.is_sensor;
        collider.collision_groups =
            InteractionGroups::new(self.collision_membership, self.collision_filter);
        collider.solver_groups = InteractionGroups::new(self.solver_membership, self.solver_filter);
        collider
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SceneJoint {
    Fixed {
        parent_anchor: [f32; 3],
        child_anchor: [f32; 3],
    },
    Revolute {
        parent_anchor: [f32; 3],
        child_anchor: [f32; 3],
        axis: [f32; 3],
        #[serde(default)]
        limits: Option<[f32; 2]>,
    },
    Prismatic {
        parent_anchor: [f32; 3],
        child_anchor: [f32; 3],
        axis: [f32; 3],
        #[serde(default)]
        limits: Option<[f32; 2]>,
    },
    Spherical {
        parent_anchor: [f32; 3],
        child_anchor: [f32; 3],
    },
    Rope {
        parent_anchor: [f32; 3],
        child_anchor: [f32; 3],
        max_distance: f32,
    },
    Spring {
        parent_anchor: [f32; 3],
        child_anchor: [f32; 3],
        rest_length: f32,
        stiffness: f32,
        damping: f32,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneJointConnection {
    pub parent_entity: AssetUuid,
    pub child_entity: AssetUuid,
    pub joint: SceneJoint,
    #[serde(default)]
    pub collisions_enabled: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SceneCollider {
    Cuboid {
        half_extents: [f32; 3],
    },
    Ball {
        radius: f32,
    },
    Cylinder {
        half_height: f32,
        radius: f32,
    },
    Capsule {
        half_height: f32,
        radius: f32,
    },
    TriMesh {
        vertices: Vec<[f32; 3]>,
        indices: Vec<[u32; 3]>,
    },
    ConvexHull {
        points: Vec<[f32; 3]>,
    },
    Cone {
        half_height: f32,
        radius: f32,
    },
    HeightField {
        nrows: usize,
        ncols: usize,
        heights: Vec<f32>,
        scale: [f32; 3],
    },
}
