use crate::ecs::material::components::Material;
use crate::ecs::physics::components::{ColliderComponent, ColliderShape, RigidBodyComponent};
use crate::ecs::physics::types::{InteractionGroups, LockedAxes, RigidBodyType};
use crate::ecs::script::components::Script;
use crate::ecs::world::components::{
    BoundingVolume, Camera, LocalTransform, Name, RenderMesh, Visibility,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PrefabBodyType {
    Dynamic,
    Kinematic,
    Static,
}

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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrefabRigidBody {
    pub body_type: PrefabBodyType,
    pub mass: f32,
}

impl Default for PrefabRigidBody {
    fn default() -> Self {
        Self {
            body_type: PrefabBodyType::Dynamic,
            mass: 1.0,
        }
    }
}

impl PrefabRigidBody {
    pub fn to_component(&self) -> RigidBodyComponent {
        RigidBodyComponent {
            handle: None,
            body_type: self.body_type.to_body_type(),
            translation: [0.0, 0.0, 0.0],
            rotation: [0.0, 0.0, 0.0, 1.0],
            linvel: [0.0, 0.0, 0.0],
            angvel: [0.0, 0.0, 0.0],
            mass: self.mass,
            locked_axes: LockedAxes::empty(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrefabCollider {
    pub shape: ColliderShape,
    pub friction: f32,
    pub restitution: f32,
    pub density: f32,
    pub is_sensor: bool,
}

impl Default for PrefabCollider {
    fn default() -> Self {
        Self {
            shape: ColliderShape::Cuboid {
                hx: 0.5,
                hy: 0.5,
                hz: 0.5,
            },
            friction: 0.5,
            restitution: 0.0,
            density: 1.0,
            is_sensor: false,
        }
    }
}

impl PrefabCollider {
    pub fn to_component(&self) -> ColliderComponent {
        ColliderComponent {
            handle: None,
            shape: self.shape.clone(),
            friction: self.friction,
            restitution: self.restitution,
            density: self.density,
            is_sensor: self.is_sensor,
            collision_groups: InteractionGroups::all(),
            solver_groups: InteractionGroups::all(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Prefab {
    pub name: String,
    pub root_nodes: Vec<PrefabNode>,
}

impl Prefab {
    pub fn new(name: String) -> Self {
        Self {
            name,
            root_nodes: Vec::new(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrefabNode {
    pub local_transform: LocalTransform,
    pub components: PrefabComponents,
    pub children: Vec<PrefabNode>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub node_index: Option<usize>,
}

impl PrefabNode {
    pub fn new(local_transform: LocalTransform) -> Self {
        Self {
            local_transform,
            components: PrefabComponents::default(),
            children: Vec::new(),
            node_index: None,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PrefabSource {
    pub prefab_name: String,
    pub source_path: Option<String>,
}

impl PrefabSource {
    pub fn new(prefab_name: impl Into<String>) -> Self {
        Self {
            prefab_name: prefab_name.into(),
            source_path: None,
        }
    }

    pub fn with_path(prefab_name: impl Into<String>, source_path: impl Into<String>) -> Self {
        Self {
            prefab_name: prefab_name.into(),
            source_path: Some(source_path.into()),
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PrefabComponents {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<Name>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub render_mesh: Option<RenderMesh>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub material: Option<Material>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub camera: Option<Camera>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub visibility: Option<Visibility>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub bounding_volume: Option<BoundingVolume>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub rigid_body: Option<PrefabRigidBody>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub collider: Option<PrefabCollider>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub script: Option<Script>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub skin_index: Option<usize>,
}
