use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::ecs::animation::components::{AnimationClip, AnimationPlayer};
use crate::ecs::bounding_volume::components::BoundingVolume;
use crate::ecs::camera::{Camera, OrthographicCamera, PerspectiveCamera, Projection};
use crate::ecs::graphics::resources::Atmosphere;
use crate::ecs::light::{Light, LightType};
use crate::ecs::lines::components::Line;
use crate::ecs::material::components::{AlphaMode, Material};
use crate::ecs::physics::components::{ColliderComponent, ColliderShape, RigidBodyComponent};
use crate::ecs::physics::types::InteractionGroups;
use crate::ecs::physics::types::RigidBodyType;
use crate::ecs::script::components::Script;
use crate::ecs::transform::LocalTransform;

use super::asset_uuid::AssetUuid;

pub const SCENE_FORMAT_VERSION: u32 = 1;
pub const ENGINE_VERSION: &str = env!("CARGO_PKG_VERSION");

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum AssetMode {
    #[default]
    Referenced,
    Embedded,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneHeader {
    pub format_version: u32,
    pub engine_version: String,
    pub name: String,
    pub asset_mode: AssetMode,
}

impl Default for SceneHeader {
    fn default() -> Self {
        Self {
            format_version: SCENE_FORMAT_VERSION,
            engine_version: ENGINE_VERSION.to_string(),
            name: String::new(),
            asset_mode: AssetMode::Referenced,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct ChunkId(pub u32);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct LayerId(pub u32);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneLayerConfig {
    pub id: LayerId,
    pub name: String,
    #[serde(default)]
    pub enabled: bool,
    #[serde(default)]
    pub order: i32,
}

impl SceneLayerConfig {
    pub fn new(id: u32, name: impl Into<String>) -> Self {
        Self {
            id: LayerId(id),
            name: name.into(),
            enabled: true,
            order: 0,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneChunkConfig {
    pub id: ChunkId,
    pub name: String,
    pub bounds_min: [f32; 3],
    pub bounds_max: [f32; 3],
    #[serde(default)]
    pub loaded: bool,
    #[serde(default)]
    pub load_distance: f32,
    #[serde(default)]
    pub unload_distance: f32,
}

impl SceneChunkConfig {
    pub fn new(
        id: u32,
        name: impl Into<String>,
        bounds_min: [f32; 3],
        bounds_max: [f32; 3],
    ) -> Self {
        Self {
            id: ChunkId(id),
            name: name.into(),
            bounds_min,
            bounds_max,
            loaded: true,
            load_distance: 100.0,
            unload_distance: 150.0,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SceneChunk {
    pub config: SceneChunkConfig,
    pub entities: Vec<SceneEntity>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub joints: Vec<SceneJointConnection>,
}

impl Default for SceneChunkConfig {
    fn default() -> Self {
        Self {
            id: ChunkId(0),
            name: String::new(),
            bounds_min: [-f32::INFINITY, -f32::INFINITY, -f32::INFINITY],
            bounds_max: [f32::INFINITY, f32::INFINITY, f32::INFINITY],
            loaded: true,
            load_distance: 100.0,
            unload_distance: 150.0,
        }
    }
}

impl SceneChunk {
    pub fn new(config: SceneChunkConfig) -> Self {
        Self {
            config,
            entities: Vec::new(),
            joints: Vec::new(),
        }
    }

    pub fn add_entity(&mut self, mut entity: SceneEntity) -> AssetUuid {
        entity.chunk_id = Some(self.config.id);
        let uuid = entity.uuid;
        self.entities.push(entity);
        uuid
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Scene {
    pub header: SceneHeader,
    pub atmosphere: Atmosphere,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub hdr_skybox: Option<SceneHdrSkybox>,
    pub entities: Vec<SceneEntity>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub joints: Vec<SceneJointConnection>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub layers: Vec<SceneLayerConfig>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub chunks: Vec<SceneChunkConfig>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub embedded_textures: HashMap<AssetUuid, EmbeddedTexture>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub embedded_audio: HashMap<AssetUuid, EmbeddedAudio>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub metadata: HashMap<String, serde_json::Value>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub navmesh: Option<SceneNavMesh>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub spawn_order: Vec<AssetUuid>,
    #[serde(skip)]
    pub uuid_index: HashMap<AssetUuid, usize>,
    #[serde(skip)]
    pub chunk_index: HashMap<ChunkId, Vec<usize>>,
}

impl Scene {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            header: SceneHeader {
                format_version: SCENE_FORMAT_VERSION,
                engine_version: ENGINE_VERSION.to_string(),
                name: name.into(),
                asset_mode: AssetMode::Referenced,
            },
            atmosphere: Atmosphere::default(),
            hdr_skybox: None,
            entities: Vec::new(),
            joints: Vec::new(),
            layers: Vec::new(),
            chunks: Vec::new(),
            embedded_textures: HashMap::new(),
            embedded_audio: HashMap::new(),
            metadata: HashMap::new(),
            navmesh: None,
            spawn_order: Vec::new(),
            uuid_index: HashMap::new(),
            chunk_index: HashMap::new(),
        }
    }

    pub fn add_entity(&mut self, entity: SceneEntity) -> AssetUuid {
        let uuid = entity.uuid;
        let index = self.entities.len();
        self.uuid_index.insert(uuid, index);
        self.entities.push(entity);
        uuid
    }

    pub fn find_entity(&self, uuid: AssetUuid) -> Option<&SceneEntity> {
        if let Some(&index) = self.uuid_index.get(&uuid) {
            self.entities.get(index)
        } else {
            self.entities.iter().find(|entity| entity.uuid == uuid)
        }
    }

    pub fn find_entity_mut(&mut self, uuid: AssetUuid) -> Option<&mut SceneEntity> {
        if let Some(&index) = self.uuid_index.get(&uuid) {
            self.entities.get_mut(index)
        } else {
            self.entities.iter_mut().find(|entity| entity.uuid == uuid)
        }
    }

    pub fn root_entities(&self) -> impl Iterator<Item = &SceneEntity> {
        self.entities
            .iter()
            .filter(|entity| entity.parent.is_none())
    }

    pub fn children_of(&self, parent_uuid: AssetUuid) -> impl Iterator<Item = &SceneEntity> {
        self.entities
            .iter()
            .filter(move |entity| entity.parent == Some(parent_uuid))
    }

    pub fn add_joint(&mut self, joint: SceneJointConnection) {
        self.joints.push(joint);
    }

    pub fn rebuild_uuid_index(&mut self) {
        self.uuid_index.clear();
        self.chunk_index.clear();
        for (index, entity) in self.entities.iter().enumerate() {
            self.uuid_index.insert(entity.uuid, index);
            if let Some(chunk_id) = entity.chunk_id {
                self.chunk_index.entry(chunk_id).or_default().push(index);
            }
        }
    }

    pub fn compute_spawn_order(&mut self) {
        let uuid_set: std::collections::HashSet<AssetUuid> =
            self.entities.iter().map(|entity| entity.uuid).collect();

        let mut result: Vec<AssetUuid> = Vec::with_capacity(self.entities.len());
        let mut visited: std::collections::HashSet<AssetUuid> = std::collections::HashSet::new();
        let mut in_stack: std::collections::HashSet<AssetUuid> = std::collections::HashSet::new();

        let entity_map: HashMap<AssetUuid, &SceneEntity> = self
            .entities
            .iter()
            .map(|entity| (entity.uuid, entity))
            .collect();

        fn visit(
            uuid: AssetUuid,
            entity_map: &HashMap<AssetUuid, &SceneEntity>,
            uuid_set: &std::collections::HashSet<AssetUuid>,
            visited: &mut std::collections::HashSet<AssetUuid>,
            in_stack: &mut std::collections::HashSet<AssetUuid>,
            result: &mut Vec<AssetUuid>,
        ) {
            if visited.contains(&uuid) {
                return;
            }

            if in_stack.contains(&uuid) {
                return;
            }

            in_stack.insert(uuid);

            if let Some(entity) = entity_map.get(&uuid)
                && let Some(parent_uuid) = entity.parent
                && uuid_set.contains(&parent_uuid)
            {
                visit(parent_uuid, entity_map, uuid_set, visited, in_stack, result);
            }

            in_stack.remove(&uuid);
            visited.insert(uuid);
            result.push(uuid);
        }

        for entity in &self.entities {
            visit(
                entity.uuid,
                &entity_map,
                &uuid_set,
                &mut visited,
                &mut in_stack,
                &mut result,
            );
        }

        self.spawn_order = result;
    }

    pub fn remove_entity(&mut self, uuid: AssetUuid) -> Option<SceneEntity> {
        if let Some(index) = self.uuid_index.remove(&uuid) {
            let entity = self.entities.remove(index);
            for (existing_uuid, existing_index) in self.uuid_index.iter_mut() {
                if *existing_index > index {
                    *existing_index -= 1;
                }
                let _ = existing_uuid;
            }
            Some(entity)
        } else if let Some(pos) = self.entities.iter().position(|entity| entity.uuid == uuid) {
            Some(self.entities.remove(pos))
        } else {
            None
        }
    }

    pub fn add_layer(&mut self, layer: SceneLayerConfig) {
        self.layers.push(layer);
    }

    pub fn add_chunk(&mut self, chunk: SceneChunkConfig) {
        self.chunks.push(chunk);
    }

    pub fn get_layer(&self, layer_id: LayerId) -> Option<&SceneLayerConfig> {
        self.layers.iter().find(|layer| layer.id == layer_id)
    }

    pub fn get_chunk(&self, chunk_id: ChunkId) -> Option<&SceneChunkConfig> {
        self.chunks.iter().find(|chunk| chunk.id == chunk_id)
    }

    pub fn entities_in_layer(&self, layer_id: u32) -> impl Iterator<Item = &SceneEntity> {
        self.entities
            .iter()
            .filter(move |entity| entity.layer == Some(layer_id))
    }

    pub fn entities_in_chunk(&self, chunk_id: ChunkId) -> impl Iterator<Item = &SceneEntity> {
        self.chunk_index
            .get(&chunk_id)
            .into_iter()
            .flatten()
            .filter_map(|&index| self.entities.get(index))
    }

    pub fn extract_chunk(&self, chunk_id: ChunkId) -> SceneChunk {
        let config = self
            .get_chunk(chunk_id)
            .cloned()
            .unwrap_or_else(|| SceneChunkConfig {
                id: chunk_id,
                ..Default::default()
            });

        let entities: Vec<SceneEntity> = self
            .entities
            .iter()
            .filter(|entity| entity.chunk_id == Some(chunk_id))
            .cloned()
            .collect();

        let entity_uuids: std::collections::HashSet<AssetUuid> =
            entities.iter().map(|entity| entity.uuid).collect();

        let joints: Vec<SceneJointConnection> = self
            .joints
            .iter()
            .filter(|joint| {
                entity_uuids.contains(&joint.parent_entity)
                    || entity_uuids.contains(&joint.child_entity)
            })
            .cloned()
            .collect();

        SceneChunk {
            config,
            entities,
            joints,
        }
    }

    pub fn merge_chunk(&mut self, chunk: SceneChunk) {
        if !self.chunks.iter().any(|c| c.id == chunk.config.id) {
            self.chunks.push(chunk.config);
        }

        for entity in chunk.entities {
            self.add_entity(entity);
        }

        for joint in chunk.joints {
            self.joints.push(joint);
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SceneHdrSkybox {
    Embedded { data: Vec<u8> },
    Reference { path: String },
    Asset { uuid: AssetUuid },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneEntity {
    pub uuid: AssetUuid,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub parent: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    pub transform: LocalTransform,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub layer: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub chunk_id: Option<ChunkId>,
    #[serde(default)]
    pub components: SceneComponents,
}

impl SceneEntity {
    pub fn new() -> Self {
        Self {
            uuid: AssetUuid::new(),
            parent: None,
            name: None,
            transform: LocalTransform::default(),
            layer: None,
            chunk_id: None,
            components: SceneComponents::default(),
        }
    }

    pub fn with_name(mut self, name: impl Into<String>) -> Self {
        self.name = Some(name.into());
        self
    }

    pub fn with_transform(mut self, transform: LocalTransform) -> Self {
        self.transform = transform;
        self
    }

    pub fn with_parent(mut self, parent: AssetUuid) -> Self {
        self.parent = Some(parent);
        self
    }

    pub fn with_layer(mut self, layer: u32) -> Self {
        self.layer = Some(layer);
        self
    }

    pub fn with_chunk(mut self, chunk_id: ChunkId) -> Self {
        self.chunk_id = Some(chunk_id);
        self
    }

    pub fn with_components(mut self, components: SceneComponents) -> Self {
        self.components = components;
        self
    }

    pub fn with_mesh(mut self, mesh: SceneMesh) -> Self {
        self.components.mesh = Some(mesh);
        self
    }

    pub fn with_light(mut self, light: SceneLight) -> Self {
        self.components.light = Some(light);
        self
    }

    pub fn with_camera(mut self, camera: SceneCamera) -> Self {
        self.components.camera = Some(camera);
        self
    }

    pub fn with_casts_shadow(mut self, casts_shadow: bool) -> Self {
        self.components.casts_shadow = casts_shadow;
        self
    }

    pub fn with_visible(mut self, visible: bool) -> Self {
        self.components.visible = visible;
        self
    }
}

impl Default for SceneEntity {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SceneComponents {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub mesh: Option<SceneMesh>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub instanced_mesh: Option<SceneInstancedMesh>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub light: Option<SceneLight>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub camera: Option<SceneCamera>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub physics: Option<ScenePhysics>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub script: Option<Script>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub audio: Option<SceneAudioSource>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub prefab: Option<ScenePrefabInstance>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub bounding_volume: Option<BoundingVolume>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub animation_player: Option<SceneAnimationPlayer>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub lines: Vec<Line>,
    #[serde(default)]
    pub casts_shadow: bool,
    #[serde(default)]
    pub visible: bool,
}

impl SceneComponents {
    pub fn new() -> Self {
        Self {
            visible: true,
            casts_shadow: true,
            ..Default::default()
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneMesh {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub mesh_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub mesh_name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub material: Option<SceneMaterial>,
}

impl SceneMesh {
    pub fn from_uuid(uuid: AssetUuid) -> Self {
        Self {
            mesh_uuid: Some(uuid),
            mesh_name: None,
            material: None,
        }
    }

    pub fn from_name(name: impl Into<String>) -> Self {
        Self {
            mesh_uuid: None,
            mesh_name: Some(name.into()),
            material: None,
        }
    }

    pub fn with_material(mut self, material: SceneMaterial) -> Self {
        self.material = Some(material);
        self
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneMaterial {
    pub base_color: [f32; 4],
    pub emissive_factor: [f32; 3],
    #[serde(default, skip_serializing_if = "is_default_alpha_mode")]
    pub alpha_mode: SceneAlphaMode,
    #[serde(
        default = "default_alpha_cutoff",
        skip_serializing_if = "is_default_alpha_cutoff"
    )]
    pub alpha_cutoff: f32,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub base_texture_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub base_texture_name: Option<String>,
    #[serde(default, skip_serializing_if = "is_zero_u32")]
    pub base_texture_uv_set: u32,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub emissive_texture_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub emissive_texture_name: Option<String>,
    #[serde(default, skip_serializing_if = "is_zero_u32")]
    pub emissive_texture_uv_set: u32,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub normal_texture_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub normal_texture_name: Option<String>,
    #[serde(default, skip_serializing_if = "is_zero_u32")]
    pub normal_texture_uv_set: u32,
    #[serde(
        default = "default_normal_scale",
        skip_serializing_if = "is_default_normal_scale"
    )]
    pub normal_scale: f32,
    #[serde(default, skip_serializing_if = "is_false")]
    pub normal_map_flip_y: bool,
    #[serde(default, skip_serializing_if = "is_false")]
    pub normal_map_two_component: bool,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub metallic_roughness_texture_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub metallic_roughness_texture_name: Option<String>,
    #[serde(default, skip_serializing_if = "is_zero_u32")]
    pub metallic_roughness_texture_uv_set: u32,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub occlusion_texture_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub occlusion_texture_name: Option<String>,
    #[serde(default, skip_serializing_if = "is_zero_u32")]
    pub occlusion_texture_uv_set: u32,
    #[serde(
        default = "default_occlusion_strength",
        skip_serializing_if = "is_default_occlusion_strength"
    )]
    pub occlusion_strength: f32,
    #[serde(
        default = "default_roughness",
        skip_serializing_if = "is_default_roughness"
    )]
    pub roughness: f32,
    #[serde(default, skip_serializing_if = "is_zero_f32")]
    pub metallic: f32,
    #[serde(default, skip_serializing_if = "is_false")]
    pub unlit: bool,
    #[serde(default, skip_serializing_if = "is_false")]
    pub double_sided: bool,
    #[serde(
        default = "default_uv_scale",
        skip_serializing_if = "is_default_uv_scale"
    )]
    pub uv_scale: [f32; 2],
    #[serde(default, skip_serializing_if = "is_zero_f32")]
    pub transmission_factor: f32,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub transmission_texture_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub transmission_texture_name: Option<String>,
    #[serde(default, skip_serializing_if = "is_zero_u32")]
    pub transmission_texture_uv_set: u32,
    #[serde(default, skip_serializing_if = "is_zero_f32")]
    pub thickness: f32,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub thickness_texture_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub thickness_texture_name: Option<String>,
    #[serde(default, skip_serializing_if = "is_zero_u32")]
    pub thickness_texture_uv_set: u32,
    #[serde(
        default = "default_attenuation_color",
        skip_serializing_if = "is_default_attenuation_color"
    )]
    pub attenuation_color: [f32; 3],
    #[serde(
        default = "default_attenuation_distance",
        skip_serializing_if = "is_default_attenuation_distance"
    )]
    pub attenuation_distance: f32,
    #[serde(default = "default_ior", skip_serializing_if = "is_default_ior")]
    pub ior: f32,
    #[serde(
        default = "default_specular_factor",
        skip_serializing_if = "is_default_specular_factor"
    )]
    pub specular_factor: f32,
    #[serde(
        default = "default_specular_color_factor",
        skip_serializing_if = "is_default_specular_color_factor"
    )]
    pub specular_color_factor: [f32; 3],
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub specular_texture_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub specular_texture_name: Option<String>,
    #[serde(default, skip_serializing_if = "is_zero_u32")]
    pub specular_texture_uv_set: u32,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub specular_color_texture_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub specular_color_texture_name: Option<String>,
    #[serde(default, skip_serializing_if = "is_zero_u32")]
    pub specular_color_texture_uv_set: u32,
    #[serde(
        default = "default_emissive_strength",
        skip_serializing_if = "is_default_emissive_strength"
    )]
    pub emissive_strength: f32,
}

fn default_alpha_cutoff() -> f32 {
    0.5
}

fn default_roughness() -> f32 {
    0.5
}

fn default_uv_scale() -> [f32; 2] {
    [1.0, 1.0]
}

fn default_normal_scale() -> f32 {
    1.0
}

fn default_occlusion_strength() -> f32 {
    1.0
}

fn default_attenuation_color() -> [f32; 3] {
    [1.0, 1.0, 1.0]
}

fn default_attenuation_distance() -> f32 {
    f32::INFINITY
}

fn default_ior() -> f32 {
    1.5
}

fn default_specular_factor() -> f32 {
    1.0
}

fn default_specular_color_factor() -> [f32; 3] {
    [1.0, 1.0, 1.0]
}

fn default_emissive_strength() -> f32 {
    1.0
}

fn is_default_alpha_mode(value: &SceneAlphaMode) -> bool {
    *value == SceneAlphaMode::Opaque
}

fn is_default_alpha_cutoff(value: &f32) -> bool {
    (*value - 0.5).abs() < f32::EPSILON
}

fn is_zero_u32(value: &u32) -> bool {
    *value == 0
}

fn is_default_normal_scale(value: &f32) -> bool {
    (*value - 1.0).abs() < f32::EPSILON
}

fn is_false(value: &bool) -> bool {
    !*value
}

fn is_default_occlusion_strength(value: &f32) -> bool {
    (*value - 1.0).abs() < f32::EPSILON
}

fn is_default_roughness(value: &f32) -> bool {
    (*value - 0.5).abs() < f32::EPSILON
}

fn is_zero_f32(value: &f32) -> bool {
    value.abs() < f32::EPSILON
}

fn is_default_uv_scale(value: &[f32; 2]) -> bool {
    (value[0] - 1.0).abs() < f32::EPSILON && (value[1] - 1.0).abs() < f32::EPSILON
}

fn is_default_attenuation_color(value: &[f32; 3]) -> bool {
    (value[0] - 1.0).abs() < f32::EPSILON
        && (value[1] - 1.0).abs() < f32::EPSILON
        && (value[2] - 1.0).abs() < f32::EPSILON
}

fn is_default_attenuation_distance(value: &f32) -> bool {
    value.is_infinite() && value.is_sign_positive()
}

fn is_default_ior(value: &f32) -> bool {
    (*value - 1.5).abs() < f32::EPSILON
}

fn is_default_specular_factor(value: &f32) -> bool {
    (*value - 1.0).abs() < f32::EPSILON
}

fn is_default_specular_color_factor(value: &[f32; 3]) -> bool {
    (value[0] - 1.0).abs() < f32::EPSILON
        && (value[1] - 1.0).abs() < f32::EPSILON
        && (value[2] - 1.0).abs() < f32::EPSILON
}

fn is_default_emissive_strength(value: &f32) -> bool {
    (*value - 1.0).abs() < f32::EPSILON
}

impl Default for SceneMaterial {
    fn default() -> Self {
        Self {
            base_color: [1.0, 1.0, 1.0, 1.0],
            emissive_factor: [0.0, 0.0, 0.0],
            alpha_mode: SceneAlphaMode::Opaque,
            alpha_cutoff: 0.5,
            base_texture_uuid: None,
            base_texture_name: None,
            base_texture_uv_set: 0,
            emissive_texture_uuid: None,
            emissive_texture_name: None,
            emissive_texture_uv_set: 0,
            normal_texture_uuid: None,
            normal_texture_name: None,
            normal_texture_uv_set: 0,
            normal_scale: 1.0,
            normal_map_flip_y: false,
            normal_map_two_component: false,
            metallic_roughness_texture_uuid: None,
            metallic_roughness_texture_name: None,
            metallic_roughness_texture_uv_set: 0,
            occlusion_texture_uuid: None,
            occlusion_texture_name: None,
            occlusion_texture_uv_set: 0,
            occlusion_strength: 1.0,
            roughness: 0.5,
            metallic: 0.0,
            unlit: false,
            double_sided: false,
            uv_scale: [1.0, 1.0],
            transmission_factor: 0.0,
            transmission_texture_uuid: None,
            transmission_texture_name: None,
            transmission_texture_uv_set: 0,
            thickness: 0.0,
            thickness_texture_uuid: None,
            thickness_texture_name: None,
            thickness_texture_uv_set: 0,
            attenuation_color: [1.0, 1.0, 1.0],
            attenuation_distance: f32::INFINITY,
            ior: 1.5,
            specular_factor: 1.0,
            specular_color_factor: [1.0, 1.0, 1.0],
            specular_texture_uuid: None,
            specular_texture_name: None,
            specular_texture_uv_set: 0,
            specular_color_texture_uuid: None,
            specular_color_texture_name: None,
            specular_color_texture_uv_set: 0,
            emissive_strength: 1.0,
        }
    }
}

impl SceneMaterial {
    pub fn to_material(&self) -> Material {
        Material {
            base_color: self.base_color,
            emissive_factor: self.emissive_factor,
            alpha_mode: self.alpha_mode.into(),
            alpha_cutoff: self.alpha_cutoff,
            base_texture: self.base_texture_name.clone(),
            base_texture_uv_set: self.base_texture_uv_set,
            emissive_texture: self.emissive_texture_name.clone(),
            emissive_texture_uv_set: self.emissive_texture_uv_set,
            normal_texture: self.normal_texture_name.clone(),
            normal_texture_uv_set: self.normal_texture_uv_set,
            normal_scale: self.normal_scale,
            normal_map_flip_y: self.normal_map_flip_y,
            normal_map_two_component: self.normal_map_two_component,
            metallic_roughness_texture: self.metallic_roughness_texture_name.clone(),
            metallic_roughness_texture_uv_set: self.metallic_roughness_texture_uv_set,
            occlusion_texture: self.occlusion_texture_name.clone(),
            occlusion_texture_uv_set: self.occlusion_texture_uv_set,
            occlusion_strength: self.occlusion_strength,
            roughness: self.roughness,
            metallic: self.metallic,
            unlit: self.unlit,
            double_sided: self.double_sided,
            uv_scale: self.uv_scale,
            transmission_factor: self.transmission_factor,
            transmission_texture: self.transmission_texture_name.clone(),
            transmission_texture_uv_set: self.transmission_texture_uv_set,
            thickness: self.thickness,
            thickness_texture: self.thickness_texture_name.clone(),
            thickness_texture_uv_set: self.thickness_texture_uv_set,
            attenuation_color: self.attenuation_color,
            attenuation_distance: self.attenuation_distance,
            ior: self.ior,
            specular_factor: self.specular_factor,
            specular_color_factor: self.specular_color_factor,
            specular_texture: self.specular_texture_name.clone(),
            specular_texture_uv_set: self.specular_texture_uv_set,
            specular_color_texture: self.specular_color_texture_name.clone(),
            specular_color_texture_uv_set: self.specular_color_texture_uv_set,
            emissive_strength: self.emissive_strength,
        }
    }
}

impl From<&Material> for SceneMaterial {
    fn from(material: &Material) -> Self {
        Self {
            base_color: material.base_color,
            emissive_factor: material.emissive_factor,
            alpha_mode: material.alpha_mode.into(),
            alpha_cutoff: material.alpha_cutoff,
            base_texture_uuid: None,
            base_texture_name: material.base_texture.clone(),
            base_texture_uv_set: material.base_texture_uv_set,
            emissive_texture_uuid: None,
            emissive_texture_name: material.emissive_texture.clone(),
            emissive_texture_uv_set: material.emissive_texture_uv_set,
            normal_texture_uuid: None,
            normal_texture_name: material.normal_texture.clone(),
            normal_texture_uv_set: material.normal_texture_uv_set,
            normal_scale: material.normal_scale,
            normal_map_flip_y: material.normal_map_flip_y,
            normal_map_two_component: material.normal_map_two_component,
            metallic_roughness_texture_uuid: None,
            metallic_roughness_texture_name: material.metallic_roughness_texture.clone(),
            metallic_roughness_texture_uv_set: material.metallic_roughness_texture_uv_set,
            occlusion_texture_uuid: None,
            occlusion_texture_name: material.occlusion_texture.clone(),
            occlusion_texture_uv_set: material.occlusion_texture_uv_set,
            occlusion_strength: material.occlusion_strength,
            roughness: material.roughness,
            metallic: material.metallic,
            unlit: material.unlit,
            double_sided: material.double_sided,
            uv_scale: material.uv_scale,
            transmission_factor: material.transmission_factor,
            transmission_texture_uuid: None,
            transmission_texture_name: material.transmission_texture.clone(),
            transmission_texture_uv_set: material.transmission_texture_uv_set,
            thickness: material.thickness,
            thickness_texture_uuid: None,
            thickness_texture_name: material.thickness_texture.clone(),
            thickness_texture_uv_set: material.thickness_texture_uv_set,
            attenuation_color: material.attenuation_color,
            attenuation_distance: material.attenuation_distance,
            ior: material.ior,
            specular_factor: material.specular_factor,
            specular_color_factor: material.specular_color_factor,
            specular_texture_uuid: None,
            specular_texture_name: material.specular_texture.clone(),
            specular_texture_uv_set: material.specular_texture_uv_set,
            specular_color_texture_uuid: None,
            specular_color_texture_name: material.specular_color_texture.clone(),
            specular_color_texture_uv_set: material.specular_color_texture_uv_set,
            emissive_strength: material.emissive_strength,
        }
    }
}

#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)]
pub enum SceneAlphaMode {
    #[default]
    Opaque,
    Mask,
    Blend,
}

impl From<AlphaMode> for SceneAlphaMode {
    fn from(mode: AlphaMode) -> Self {
        match mode {
            AlphaMode::Opaque => SceneAlphaMode::Opaque,
            AlphaMode::Mask => SceneAlphaMode::Mask,
            AlphaMode::Blend => SceneAlphaMode::Blend,
        }
    }
}

impl From<SceneAlphaMode> for AlphaMode {
    fn from(mode: SceneAlphaMode) -> Self {
        match mode {
            SceneAlphaMode::Opaque => AlphaMode::Opaque,
            SceneAlphaMode::Mask => AlphaMode::Mask,
            SceneAlphaMode::Blend => AlphaMode::Blend,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SceneLight {
    Directional {
        color: [f32; 3],
        intensity: f32,
        cast_shadows: bool,
        shadow_bias: f32,
    },
    Point {
        color: [f32; 3],
        intensity: f32,
        range: f32,
        cast_shadows: bool,
        shadow_bias: f32,
    },
    Spot {
        color: [f32; 3],
        intensity: f32,
        range: f32,
        inner_cone_angle: f32,
        outer_cone_angle: f32,
        cast_shadows: bool,
        shadow_bias: f32,
    },
}

impl SceneLight {
    pub fn directional(color: [f32; 3], intensity: f32) -> Self {
        Self::Directional {
            color,
            intensity,
            cast_shadows: true,
            shadow_bias: 0.005,
        }
    }

    pub fn point(color: [f32; 3], intensity: f32, range: f32) -> Self {
        Self::Point {
            color,
            intensity,
            range,
            cast_shadows: false,
            shadow_bias: 0.0,
        }
    }

    pub fn spot(
        color: [f32; 3],
        intensity: f32,
        range: f32,
        inner_cone_angle: f32,
        outer_cone_angle: f32,
    ) -> Self {
        Self::Spot {
            color,
            intensity,
            range,
            inner_cone_angle,
            outer_cone_angle,
            cast_shadows: false,
            shadow_bias: 0.0,
        }
    }

    pub fn to_light(&self) -> Light {
        match self {
            SceneLight::Directional {
                color,
                intensity,
                cast_shadows,
                shadow_bias,
            } => Light {
                light_type: LightType::Directional,
                color: nalgebra_glm::Vec3::new(color[0], color[1], color[2]),
                intensity: *intensity,
                range: 0.0,
                inner_cone_angle: 0.0,
                outer_cone_angle: 0.0,
                cast_shadows: *cast_shadows,
                shadow_bias: *shadow_bias,
            },
            SceneLight::Point {
                color,
                intensity,
                range,
                cast_shadows,
                shadow_bias,
            } => Light {
                light_type: LightType::Point,
                color: nalgebra_glm::Vec3::new(color[0], color[1], color[2]),
                intensity: *intensity,
                range: *range,
                inner_cone_angle: 0.0,
                outer_cone_angle: 0.0,
                cast_shadows: *cast_shadows,
                shadow_bias: *shadow_bias,
            },
            SceneLight::Spot {
                color,
                intensity,
                range,
                inner_cone_angle,
                outer_cone_angle,
                cast_shadows,
                shadow_bias,
            } => Light {
                light_type: LightType::Spot,
                color: nalgebra_glm::Vec3::new(color[0], color[1], color[2]),
                intensity: *intensity,
                range: *range,
                inner_cone_angle: *inner_cone_angle,
                outer_cone_angle: *outer_cone_angle,
                cast_shadows: *cast_shadows,
                shadow_bias: *shadow_bias,
            },
        }
    }
}

impl From<&Light> for SceneLight {
    fn from(light: &Light) -> Self {
        let color = [light.color.x, light.color.y, light.color.z];
        match light.light_type {
            LightType::Directional => SceneLight::Directional {
                color,
                intensity: light.intensity,
                cast_shadows: light.cast_shadows,
                shadow_bias: light.shadow_bias,
            },
            LightType::Point => SceneLight::Point {
                color,
                intensity: light.intensity,
                range: light.range,
                cast_shadows: light.cast_shadows,
                shadow_bias: light.shadow_bias,
            },
            LightType::Spot => SceneLight::Spot {
                color,
                intensity: light.intensity,
                range: light.range,
                inner_cone_angle: light.inner_cone_angle,
                outer_cone_angle: light.outer_cone_angle,
                cast_shadows: light.cast_shadows,
                shadow_bias: light.shadow_bias,
            },
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SceneCamera {
    Perspective {
        aspect_ratio: Option<f32>,
        y_fov_rad: f32,
        z_far: Option<f32>,
        z_near: f32,
    },
    Orthographic {
        x_mag: f32,
        y_mag: f32,
        z_far: f32,
        z_near: f32,
    },
}

impl SceneCamera {
    pub fn perspective(y_fov_rad: f32, z_near: f32) -> Self {
        Self::Perspective {
            aspect_ratio: None,
            y_fov_rad,
            z_far: None,
            z_near,
        }
    }

    pub fn orthographic(x_mag: f32, y_mag: f32, z_near: f32, z_far: f32) -> Self {
        Self::Orthographic {
            x_mag,
            y_mag,
            z_far,
            z_near,
        }
    }

    pub fn to_camera(&self) -> Camera {
        match self {
            SceneCamera::Perspective {
                aspect_ratio,
                y_fov_rad,
                z_far,
                z_near,
            } => Camera {
                projection: Projection::Perspective(PerspectiveCamera {
                    aspect_ratio: *aspect_ratio,
                    y_fov_rad: *y_fov_rad,
                    z_far: *z_far,
                    z_near: *z_near,
                }),
                smoothing: None,
            },
            SceneCamera::Orthographic {
                x_mag,
                y_mag,
                z_far,
                z_near,
            } => Camera {
                projection: Projection::Orthographic(OrthographicCamera {
                    x_mag: *x_mag,
                    y_mag: *y_mag,
                    z_far: *z_far,
                    z_near: *z_near,
                }),
                smoothing: None,
            },
        }
    }
}

impl From<&Camera> for SceneCamera {
    fn from(camera: &Camera) -> Self {
        match &camera.projection {
            Projection::Perspective(perspective) => SceneCamera::Perspective {
                aspect_ratio: perspective.aspect_ratio,
                y_fov_rad: perspective.y_fov_rad,
                z_far: perspective.z_far,
                z_near: perspective.z_near,
            },
            Projection::Orthographic(orthographic) => SceneCamera::Orthographic {
                x_mag: orthographic.x_mag,
                y_mag: orthographic.y_mag,
                z_far: orthographic.z_far,
                z_near: orthographic.z_near,
            },
        }
    }
}

#[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, skip_serializing_if = "Option::is_none")]
    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,
}

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(),
        }
    }
}

impl ScenePhysics {
    pub fn static_cuboid(half_extents: [f32; 3]) -> Self {
        Self {
            body_type: SceneBodyType::Static,
            collider: SceneCollider::Cuboid { half_extents },
            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(),
        }
    }

    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),
            is_sensor: false,
            collision_membership: default_collision_membership(),
            collision_filter: default_collision_filter(),
            solver_membership: default_solver_membership(),
            solver_filter: default_solver_filter(),
        }
    }

    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),
            is_sensor: false,
            collision_membership: default_collision_membership(),
            collision_filter: default_collision_filter(),
            solver_membership: default_solver_membership(),
            solver_filter: default_solver_filter(),
        }
    }

    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
    }

    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, skip_serializing_if = "Option::is_none")]
        limits: Option<[f32; 2]>,
    },
    Prismatic {
        parent_anchor: [f32; 3],
        child_anchor: [f32; 3],
        axis: [f32; 3],
        #[serde(default, skip_serializing_if = "Option::is_none")]
        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],
    },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneAudioSource {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub audio_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub audio_name: Option<String>,
    #[serde(default = "default_volume")]
    pub volume: f64,
    #[serde(default)]
    pub looping: bool,
    #[serde(default)]
    pub playing: bool,
    #[serde(default)]
    pub spatial: bool,
    #[serde(default)]
    pub reverb: bool,
}

fn default_volume() -> f64 {
    1.0
}

impl SceneAudioSource {
    pub fn from_name(name: impl Into<String>) -> Self {
        Self {
            audio_uuid: None,
            audio_name: Some(name.into()),
            volume: 1.0,
            looping: false,
            playing: false,
            spatial: false,
            reverb: false,
        }
    }

    pub fn from_uuid(uuid: AssetUuid) -> Self {
        Self {
            audio_uuid: Some(uuid),
            audio_name: None,
            volume: 1.0,
            looping: false,
            playing: false,
            spatial: false,
            reverb: false,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScenePrefabInstance {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub prefab_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub prefab_path: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub prefab_name: Option<String>,
}

impl ScenePrefabInstance {
    pub fn from_uuid(uuid: AssetUuid) -> Self {
        Self {
            prefab_uuid: Some(uuid),
            prefab_path: None,
            prefab_name: None,
        }
    }

    pub fn from_path(path: impl Into<String>) -> Self {
        Self {
            prefab_uuid: None,
            prefab_path: Some(path.into()),
            prefab_name: None,
        }
    }

    pub fn from_name(name: impl Into<String>) -> Self {
        Self {
            prefab_uuid: None,
            prefab_path: None,
            prefab_name: Some(name.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EmbeddedTexture {
    Rgba {
        #[serde(with = "base64_bytes")]
        rgba_data: Vec<u8>,
        width: u32,
        height: u32,
    },
    Png {
        #[serde(with = "base64_bytes")]
        png_data: Vec<u8>,
    },
}

mod base64_bytes {
    use base64::{Engine, engine::general_purpose::STANDARD};
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S: Serializer>(data: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
        STANDARD.encode(data).serialize(serializer)
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
        let string = String::deserialize(deserializer)?;
        STANDARD.decode(&string).map_err(serde::de::Error::custom)
    }
}

impl EmbeddedTexture {
    pub fn from_rgba(rgba_data: Vec<u8>, width: u32, height: u32) -> Self {
        Self::Rgba {
            rgba_data,
            width,
            height,
        }
    }

    pub fn from_png(png_data: Vec<u8>) -> Self {
        Self::Png { png_data }
    }

    pub fn to_rgba(&self) -> Option<(Vec<u8>, u32, u32)> {
        match self {
            Self::Rgba {
                rgba_data,
                width,
                height,
            } => Some((rgba_data.clone(), *width, *height)),
            Self::Png { png_data } => {
                if let Ok(img) = image::load_from_memory(png_data) {
                    let rgba = img.to_rgba8();
                    let (width, height) = rgba.dimensions();
                    Some((rgba.into_raw(), width, height))
                } else {
                    None
                }
            }
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddedAudio {
    #[serde(with = "base64_bytes")]
    pub data: Vec<u8>,
}

impl EmbeddedAudio {
    pub fn from_bytes(data: Vec<u8>) -> Self {
        Self { data }
    }

    pub fn data(&self) -> &[u8] {
        &self.data
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneInstancedMesh {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub mesh_uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub mesh_name: Option<String>,
    pub instances: Vec<SceneMeshInstance>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub material: Option<SceneMaterial>,
}

impl SceneInstancedMesh {
    pub fn from_name(name: impl Into<String>, instances: Vec<SceneMeshInstance>) -> Self {
        Self {
            mesh_uuid: None,
            mesh_name: Some(name.into()),
            instances,
            material: None,
        }
    }

    pub fn from_uuid(uuid: AssetUuid, instances: Vec<SceneMeshInstance>) -> Self {
        Self {
            mesh_uuid: Some(uuid),
            mesh_name: None,
            instances,
            material: None,
        }
    }

    pub fn with_material(mut self, material: SceneMaterial) -> Self {
        self.material = Some(material);
        self
    }
}

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

impl SceneMeshInstance {
    pub fn new(translation: [f32; 3]) -> Self {
        Self {
            translation,
            rotation: [0.0, 0.0, 0.0, 1.0],
            scale: [1.0, 1.0, 1.0],
            color: None,
        }
    }

    pub fn with_rotation(mut self, rotation: [f32; 4]) -> Self {
        self.rotation = rotation;
        self
    }

    pub fn with_scale(mut self, scale: [f32; 3]) -> Self {
        self.scale = scale;
        self
    }

    pub fn with_uniform_scale(mut self, scale: f32) -> Self {
        self.scale = [scale, scale, scale];
        self
    }

    pub fn with_color(mut self, color: [f32; 4]) -> Self {
        self.color = Some(color);
        self
    }

    pub fn to_local_transform(&self) -> LocalTransform {
        LocalTransform {
            translation: nalgebra_glm::Vec3::new(
                self.translation[0],
                self.translation[1],
                self.translation[2],
            ),
            rotation: nalgebra_glm::Quat::new(
                self.rotation[3],
                self.rotation[0],
                self.rotation[1],
                self.rotation[2],
            ),
            scale: nalgebra_glm::Vec3::new(self.scale[0], self.scale[1], self.scale[2]),
        }
    }
}

impl From<LocalTransform> for SceneMeshInstance {
    fn from(transform: LocalTransform) -> Self {
        Self {
            translation: [
                transform.translation.x,
                transform.translation.y,
                transform.translation.z,
            ],
            rotation: [
                transform.rotation.i,
                transform.rotation.j,
                transform.rotation.k,
                transform.rotation.w,
            ],
            scale: [transform.scale.x, transform.scale.y, transform.scale.z],
            color: None,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SceneNavMesh {
    pub vertices: Vec<[f32; 3]>,
    pub triangles: Vec<SceneNavMeshTriangle>,
    pub edges: Vec<SceneNavMeshEdge>,
    pub adjacency: HashMap<usize, Vec<SceneNavMeshConnection>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneNavMeshTriangle {
    pub vertex_indices: [usize; 3],
    pub center: [f32; 3],
    pub normal: [f32; 3],
    pub area: f32,
    pub edge_indices: [usize; 3],
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneNavMeshEdge {
    pub vertex_indices: [usize; 2],
    pub triangle_indices: [Option<usize>; 2],
    pub midpoint: [f32; 3],
    pub length: f32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneNavMeshConnection {
    pub target_triangle: usize,
    pub shared_edge: usize,
    pub cost: f32,
}

impl SceneNavMesh {
    pub fn from_navmesh_world(navmesh: &crate::ecs::navmesh::resources::NavMeshWorld) -> Self {
        let vertices: Vec<[f32; 3]> = navmesh.vertices.iter().map(|v| [v.x, v.y, v.z]).collect();

        let triangles: Vec<SceneNavMeshTriangle> = navmesh
            .triangles
            .iter()
            .map(|t| SceneNavMeshTriangle {
                vertex_indices: t.vertex_indices,
                center: [t.center.x, t.center.y, t.center.z],
                normal: [t.normal.x, t.normal.y, t.normal.z],
                area: t.area,
                edge_indices: t.edge_indices,
            })
            .collect();

        let edges: Vec<SceneNavMeshEdge> = navmesh
            .edges
            .iter()
            .map(|e| SceneNavMeshEdge {
                vertex_indices: e.vertex_indices,
                triangle_indices: e.triangle_indices,
                midpoint: [e.midpoint.x, e.midpoint.y, e.midpoint.z],
                length: e.length,
            })
            .collect();

        let adjacency: HashMap<usize, Vec<SceneNavMeshConnection>> = navmesh
            .adjacency
            .iter()
            .map(|(key, connections)| {
                let scene_connections: Vec<SceneNavMeshConnection> = connections
                    .iter()
                    .map(|c| SceneNavMeshConnection {
                        target_triangle: c.target_triangle,
                        shared_edge: c.shared_edge,
                        cost: c.cost,
                    })
                    .collect();
                (*key, scene_connections)
            })
            .collect();

        Self {
            vertices,
            triangles,
            edges,
            adjacency,
        }
    }

    pub fn to_navmesh_world(&self) -> crate::ecs::navmesh::resources::NavMeshWorld {
        use crate::ecs::navmesh::resources::{
            NavMeshConnection, NavMeshEdge, NavMeshTriangle, NavMeshWorld, SpatialHash,
        };

        let vertices: Vec<nalgebra_glm::Vec3> = self
            .vertices
            .iter()
            .map(|v| nalgebra_glm::vec3(v[0], v[1], v[2]))
            .collect();

        let triangles: Vec<NavMeshTriangle> = self
            .triangles
            .iter()
            .map(|t| NavMeshTriangle {
                vertex_indices: t.vertex_indices,
                center: nalgebra_glm::vec3(t.center[0], t.center[1], t.center[2]),
                normal: nalgebra_glm::vec3(t.normal[0], t.normal[1], t.normal[2]),
                area: t.area,
                edge_indices: t.edge_indices,
            })
            .collect();

        let edges: Vec<NavMeshEdge> = self
            .edges
            .iter()
            .map(|e| NavMeshEdge {
                vertex_indices: e.vertex_indices,
                triangle_indices: e.triangle_indices,
                midpoint: nalgebra_glm::vec3(e.midpoint[0], e.midpoint[1], e.midpoint[2]),
                length: e.length,
            })
            .collect();

        let adjacency: HashMap<usize, Vec<NavMeshConnection>> = self
            .adjacency
            .iter()
            .map(|(key, connections)| {
                let nav_connections: Vec<NavMeshConnection> = connections
                    .iter()
                    .map(|c| NavMeshConnection {
                        target_triangle: c.target_triangle,
                        shared_edge: c.shared_edge,
                        cost: c.cost,
                    })
                    .collect();
                (*key, nav_connections)
            })
            .collect();

        let mut spatial_hash = SpatialHash::new(2.0);
        for (triangle_index, triangle) in triangles.iter().enumerate() {
            let vertex_a = vertices[triangle.vertex_indices[0]];
            let vertex_b = vertices[triangle.vertex_indices[1]];
            let vertex_c = vertices[triangle.vertex_indices[2]];

            let min_x = vertex_a.x.min(vertex_b.x).min(vertex_c.x);
            let max_x = vertex_a.x.max(vertex_b.x).max(vertex_c.x);
            let min_z = vertex_a.z.min(vertex_b.z).min(vertex_c.z);
            let max_z = vertex_a.z.max(vertex_b.z).max(vertex_c.z);

            spatial_hash.insert_with_bounds(
                triangle_index,
                nalgebra_glm::vec3(min_x, 0.0, min_z),
                nalgebra_glm::vec3(max_x, 0.0, max_z),
            );
        }

        NavMeshWorld {
            vertices,
            triangles,
            edges,
            adjacency,
            spatial_hash,
            debug_draw: false,
            debug_entity: None,
            algorithm: Default::default(),
        }
    }

    pub fn is_empty(&self) -> bool {
        self.triangles.is_empty()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneAnimationClipRef {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub uuid: Option<AssetUuid>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub path: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
}

impl SceneAnimationClipRef {
    pub fn from_uuid(uuid: AssetUuid) -> Self {
        Self {
            uuid: Some(uuid),
            path: None,
            name: None,
        }
    }

    pub fn from_path(path: impl Into<String>) -> Self {
        Self {
            uuid: None,
            path: Some(path.into()),
            name: None,
        }
    }

    pub fn from_name(name: impl Into<String>) -> Self {
        Self {
            uuid: None,
            path: None,
            name: Some(name.into()),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SceneAnimationPlayer {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub clips: Vec<AnimationClip>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub clip_refs: Vec<SceneAnimationClipRef>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub current_clip: Option<usize>,
    #[serde(default, skip_serializing_if = "is_zero_f32")]
    pub time: f32,
    #[serde(
        default = "default_animation_speed",
        skip_serializing_if = "is_default_animation_speed"
    )]
    pub speed: f32,
    #[serde(default = "default_true", skip_serializing_if = "is_true")]
    pub looping: bool,
    #[serde(default, skip_serializing_if = "is_false")]
    pub playing: bool,
    #[serde(default, skip_serializing_if = "is_false")]
    pub play_all: bool,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub bone_name_to_node: HashMap<String, AssetUuid>,
}

fn default_animation_speed() -> f32 {
    1.0
}

fn is_default_animation_speed(value: &f32) -> bool {
    (*value - 1.0).abs() < f32::EPSILON
}

fn default_true() -> bool {
    true
}

fn is_true(value: &bool) -> bool {
    *value
}

impl Default for SceneAnimationPlayer {
    fn default() -> Self {
        Self {
            clips: Vec::new(),
            clip_refs: Vec::new(),
            current_clip: None,
            time: 0.0,
            speed: 1.0,
            looping: true,
            playing: false,
            play_all: false,
            bone_name_to_node: HashMap::new(),
        }
    }
}

impl SceneAnimationPlayer {
    pub fn new(clips: Vec<AnimationClip>) -> Self {
        Self {
            clips,
            ..Default::default()
        }
    }

    pub fn with_current_clip(mut self, clip_index: usize) -> Self {
        self.current_clip = Some(clip_index);
        self
    }

    pub fn with_playing(mut self, playing: bool) -> Self {
        self.playing = playing;
        self
    }

    pub fn with_looping(mut self, looping: bool) -> Self {
        self.looping = looping;
        self
    }

    pub fn with_speed(mut self, speed: f32) -> Self {
        self.speed = speed;
        self
    }

    pub fn to_animation_player(&self) -> AnimationPlayer {
        AnimationPlayer {
            clips: self.clips.clone(),
            current_clip: self.current_clip,
            time: self.time,
            speed: self.speed,
            looping: self.looping,
            playing: self.playing,
            play_all: self.play_all,
            node_index_to_entity: HashMap::new(),
            bone_name_to_entity: HashMap::new(),
            blend_from_clip: None,
            blend_from_time: 0.0,
            blend_factor: 1.0,
            blend_duration: 0.0,
        }
    }

    pub fn to_animation_player_with_resolved_clips(
        &self,
        asset_registry: Option<&super::registry::AssetRegistry>,
        warnings: &mut Vec<String>,
    ) -> AnimationPlayer {
        let mut clips = self.clips.clone();

        for clip_ref in &self.clip_refs {
            let path = if let Some(uuid) = clip_ref.uuid {
                asset_registry.and_then(|registry| registry.resolve_uuid(uuid))
            } else if let Some(ref path_str) = clip_ref.path {
                Some(std::path::PathBuf::from(path_str))
            } else if let Some(ref name) = clip_ref.name {
                asset_registry
                    .and_then(|registry| registry.resolve_name(name))
                    .and_then(|uuid| {
                        asset_registry.and_then(|registry| registry.resolve_uuid(uuid))
                    })
            } else {
                None
            };

            if let Some(ref path) = path {
                match load_animation_clip_from_path(path) {
                    Ok(clip) => clips.push(clip),
                    Err(error) => warnings.push(format!(
                        "Failed to load animation clip from {}: {}",
                        path.display(),
                        error
                    )),
                }
            } else {
                warnings.push(format!(
                    "Could not resolve animation clip reference: {:?}",
                    clip_ref
                ));
            }
        }

        AnimationPlayer {
            clips,
            current_clip: self.current_clip,
            time: self.time,
            speed: self.speed,
            looping: self.looping,
            playing: self.playing,
            play_all: self.play_all,
            node_index_to_entity: HashMap::new(),
            bone_name_to_entity: HashMap::new(),
            blend_from_clip: None,
            blend_from_time: 0.0,
            blend_factor: 1.0,
            blend_duration: 0.0,
        }
    }
}

fn load_animation_clip_from_path(path: &std::path::Path) -> Result<AnimationClip, String> {
    let data = std::fs::read(path).map_err(|error| error.to_string())?;

    match path.extension().and_then(|extension| extension.to_str()) {
        Some("json") => serde_json::from_slice(&data).map_err(|error| error.to_string()),
        Some("bin") => {
            let decompressed =
                lz4_flex::decompress_size_prepended(&data).map_err(|error| error.to_string())?;
            bincode::deserialize(&decompressed).map_err(|error| error.to_string())
        }
        _ => bincode::deserialize(&data).map_err(|error| error.to_string()),
    }
}

impl From<&AnimationPlayer> for SceneAnimationPlayer {
    fn from(player: &AnimationPlayer) -> Self {
        Self {
            clips: player.clips.clone(),
            clip_refs: Vec::new(),
            current_clip: player.current_clip,
            time: player.time,
            speed: player.speed,
            looping: player.looping,
            playing: player.playing,
            play_all: player.play_all,
            bone_name_to_node: HashMap::new(),
        }
    }
}
