use crate::ecs::light::components::Light;
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::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,
}

pub fn prefab_body_type_to_rigid_body_type(body_type: PrefabBodyType) -> RigidBodyType {
    match body_type {
        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,
        }
    }
}

pub fn prefab_rigid_body_to_component(prefab: &PrefabRigidBody) -> RigidBodyComponent {
    RigidBodyComponent {
        handle: None,
        body_type: prefab_body_type_to_rigid_body_type(prefab.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: prefab.mass,
        locked_axes: LockedAxes::empty(),
        ccd_enabled: false,
    }
}

#[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,
        }
    }
}

pub fn prefab_collider_to_component(prefab: &PrefabCollider) -> ColliderComponent {
    ColliderComponent {
        handle: None,
        shape: prefab.shape.clone(),
        friction: prefab.friction,
        restitution: prefab.restitution,
        density: prefab.density,
        is_sensor: prefab.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>,
    /// Names declared by `KHR_materials_variants` at the document root.
    /// Indices into this list match `MaterialVariants::mappings`'s
    /// `variant_indices`.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub material_variants: Vec<String>,
}

/// A single variant override on a primitive node: when any of the listed
/// variant names is active, swap the entity's material to the supplied
/// [`Material`].
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrefabMaterialVariant {
    pub variant_names: Vec<String>,
    pub material: Material,
}

#[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>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PrefabSource {
    pub prefab_name: String,
    pub source_path: Option<String>,
    /// Stable identifier of the originating prefab file (the `prefab_id`
    /// in the `.nsprefab` SceneHeader). Persists the link from a
    /// spawned prefab instance back to its source so editor tooling
    /// (e.g. "refresh from source", "find all instances of prefab X")
    /// can reach across maps without relying on filesystem paths.
    #[cfg(feature = "scene_graph")]
    #[serde(default)]
    pub source_uuid: Option<crate::ecs::scene::AssetUuid>,
}

pub fn prefab_source_new(prefab_name: impl Into<String>) -> PrefabSource {
    PrefabSource {
        prefab_name: prefab_name.into(),
        source_path: None,
        #[cfg(feature = "scene_graph")]
        source_uuid: None,
    }
}

pub fn prefab_source_with_path(
    prefab_name: impl Into<String>,
    source_path: impl Into<String>,
) -> PrefabSource {
    PrefabSource {
        prefab_name: prefab_name.into(),
        source_path: Some(source_path.into()),
        #[cfg(feature = "scene_graph")]
        source_uuid: None,
    }
}

#[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(default, skip_serializing_if = "Vec::is_empty")]
    pub material_variants: Vec<PrefabMaterialVariant>,

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

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

    #[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 skin_index: Option<usize>,

    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub instances: Vec<crate::ecs::world::components::LocalTransform>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrefabInstance {
    pub prefab_id: PrefabId,
    pub instance_id: uuid::Uuid,
    pub overrides: PrefabOverrides,
    pub node_path: NodePath,
}

impl Default for PrefabInstance {
    fn default() -> Self {
        Self {
            prefab_id: PrefabId::default(),
            instance_id: uuid::Uuid::nil(),
            overrides: PrefabOverrides::default(),
            node_path: node_path_root(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Default)]
pub struct PrefabId {
    pub name: String,
    pub source_path: Option<String>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PrefabOverrides {
    #[serde(skip_serializing_if = "TransformOverride::is_none")]
    pub transform_override: TransformOverride,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub component_overrides: Vec<ComponentOverride>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub added_components: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub removed_components: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub child_additions: Vec<ChildAddition>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub child_removals: Vec<NodePath>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TransformOverride {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub translation: Option<[f32; 3]>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub rotation: Option<[f32; 4]>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scale: Option<[f32; 3]>,
}

impl TransformOverride {
    fn is_none(&self) -> bool {
        self.translation.is_none() && self.rotation.is_none() && self.scale.is_none()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComponentOverride {
    pub component_type: String,
    pub overridden_fields: Vec<FieldOverride>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FieldOverride {
    pub field_name: String,
    pub value: crate::ecs::scene::TypedPayload,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChildAddition {
    pub name: Option<String>,
    pub transform: LocalTransform,
    pub components: PrefabComponents,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct NodePath {
    pub segments: Vec<NodePathSegment>,
}

pub fn node_path_root() -> NodePath {
    NodePath {
        segments: Vec::new(),
    }
}

impl std::fmt::Display for NodePath {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.segments.is_empty() {
            write!(f, "/")
        } else {
            for segment in &self.segments {
                write!(f, "/{}", segment.name)?;
                if segment.index > 0 {
                    write!(f, "[{}]", segment.index)?;
                }
            }
            Ok(())
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct NodePathSegment {
    pub name: String,
    pub index: usize,
}
