use std::collections::HashMap;

use petgraph::graph::Graph;
pub use petgraph::graph::NodeIndex;
use serde::{Deserialize, Serialize};

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::prefab::Prefab;
use crate::ecs::script::components::Script;
use crate::ecs::transform::LocalTransform;

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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Map {
    pub engine_version: String,
    pub name: String,
    pub atmosphere: Atmosphere,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub hdr_skybox: Option<MapHdrSkybox>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub textures: HashMap<String, EmbeddedTexture>,
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub audio: HashMap<String, EmbeddedAudio>,
    pub prefabs: HashMap<String, MapPrefab>,
    pub graph: Graph<MapNode, ()>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub navmesh: Option<MapNavMesh>,
}

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

impl Map {
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            engine_version: ENGINE_VERSION.to_string(),
            name: name.into(),
            atmosphere: Atmosphere::default(),
            hdr_skybox: None,
            textures: HashMap::new(),
            audio: HashMap::new(),
            prefabs: HashMap::new(),
            graph: Graph::new(),
            navmesh: None,
        }
    }

    pub fn add_texture(&mut self, name: impl Into<String>, texture: EmbeddedTexture) {
        self.textures.insert(name.into(), texture);
    }

    pub fn add_texture_from_bytes(&mut self, name: impl Into<String>, data: &[u8]) {
        self.textures
            .insert(name.into(), EmbeddedTexture::from_png(data.to_vec()));
    }

    pub fn add_texture_rgba(
        &mut self,
        name: impl Into<String>,
        rgba_data: Vec<u8>,
        width: u32,
        height: u32,
    ) {
        self.textures.insert(
            name.into(),
            EmbeddedTexture::from_rgba(rgba_data, width, height),
        );
    }

    pub fn add_audio(&mut self, name: impl Into<String>, audio: EmbeddedAudio) {
        self.audio.insert(name.into(), audio);
    }

    pub fn add_audio_from_bytes(&mut self, name: impl Into<String>, data: &[u8]) {
        self.audio
            .insert(name.into(), EmbeddedAudio::from_bytes(data.to_vec()));
    }

    pub fn add_prefab(&mut self, name: impl Into<String>, prefab: MapPrefab) {
        self.prefabs.insert(name.into(), prefab);
    }

    pub fn add_root_node(&mut self, node: MapNode) -> NodeIndex {
        self.graph.add_node(node)
    }

    pub fn add_child_node(&mut self, parent: NodeIndex, node: MapNode) -> NodeIndex {
        let child_index = self.graph.add_node(node);
        self.graph.add_edge(parent, child_index, ());
        child_index
    }

    pub fn remove_node(&mut self, index: NodeIndex) -> Option<MapNode> {
        self.graph.remove_node(index)
    }

    pub fn root_nodes(&self) -> Vec<NodeIndex> {
        self.graph
            .node_indices()
            .filter(|&index| {
                self.graph
                    .neighbors_directed(index, petgraph::Direction::Incoming)
                    .next()
                    .is_none()
            })
            .collect()
    }

    pub fn is_valid(&self) -> bool {
        self.graph.node_count() != 0 || !self.root_nodes().is_empty()
    }

    pub fn children(&self, node: NodeIndex) -> Vec<NodeIndex> {
        self.graph
            .neighbors_directed(node, petgraph::Direction::Outgoing)
            .collect()
    }
}

use crate::ecs::mesh::components::Mesh;
use std::collections::HashMap as StdHashMap;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MapPrefab {
    Embedded {
        prefab: Prefab,
        #[serde(default)]
        meshes: StdHashMap<String, Mesh>,
        #[serde(default)]
        textures: StdHashMap<String, EmbeddedTexture>,
    },
    Reference {
        path: String,
    },
}

#[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 s = String::deserialize(deserializer)?;
        STANDARD.decode(&s).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 MapAudioSource {
    pub audio_ref: 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 MapAudioSource {
    pub fn new(audio_ref: impl Into<String>) -> Self {
        Self {
            audio_ref: audio_ref.into(),
            volume: 1.0,
            looping: false,
            playing: false,
            spatial: false,
            reverb: false,
        }
    }

    pub fn with_volume(mut self, volume: f64) -> Self {
        self.volume = volume;
        self
    }

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

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

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

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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MapNode {
    Entity {
        name: Option<String>,
        transform: LocalTransform,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        script: Option<Script>,
    },
    Light(MapLight),
    Camera(MapCamera),
    Mesh {
        mesh_name: String,
        #[serde(default)]
        material: Option<MapMaterial>,
        #[serde(default = "default_casts_shadow")]
        casts_shadow: bool,
        #[serde(default)]
        bounding_volume: Option<BoundingVolume>,
        #[serde(default, skip_serializing_if = "Option::is_none")]
        physics: Option<MapPhysics>,
    },
    InstancedMesh {
        mesh_name: String,
        instances: Vec<MeshInstance>,
        #[serde(default)]
        material: Option<MapMaterial>,
        #[serde(default = "default_casts_shadow")]
        casts_shadow: bool,
        #[serde(default)]
        bounding_volume: Option<BoundingVolume>,
    },
    Lines(Vec<Line>),
    AudioSource(MapAudioSource),
    Physics(MapPhysics),
    PrefabInstance {
        prefab_name: String,
    },
    Custom {
        type_name: String,
        data: serde_json::Value,
    },
}

fn default_casts_shadow() -> bool {
    true
}

impl MapNode {
    pub fn entity() -> Self {
        Self::Entity {
            name: None,
            transform: LocalTransform::default(),
            script: None,
        }
    }

    pub fn entity_named(name: impl Into<String>) -> Self {
        Self::Entity {
            name: Some(name.into()),
            transform: LocalTransform::default(),
            script: None,
        }
    }

    pub fn entity_with_transform(transform: LocalTransform) -> Self {
        Self::Entity {
            name: None,
            transform,
            script: None,
        }
    }

    pub fn entity_full(name: Option<String>, transform: LocalTransform) -> Self {
        Self::Entity {
            name,
            transform,
            script: None,
        }
    }

    pub fn entity_with_script(
        name: Option<String>,
        transform: LocalTransform,
        script: Script,
    ) -> Self {
        Self::Entity {
            name,
            transform,
            script: Some(script),
        }
    }

    pub fn light(light: MapLight) -> Self {
        Self::Light(light)
    }

    pub fn camera(camera: MapCamera) -> Self {
        Self::Camera(camera)
    }

    pub fn mesh(mesh_name: impl Into<String>) -> Self {
        Self::Mesh {
            mesh_name: mesh_name.into(),
            material: None,
            casts_shadow: true,
            bounding_volume: None,
            physics: None,
        }
    }

    pub fn mesh_with_material(mesh_name: impl Into<String>, material: MapMaterial) -> Self {
        Self::Mesh {
            mesh_name: mesh_name.into(),
            material: Some(material),
            casts_shadow: true,
            bounding_volume: None,
            physics: None,
        }
    }

    pub fn mesh_with_physics(
        mesh_name: impl Into<String>,
        material: MapMaterial,
        physics: MapPhysics,
    ) -> Self {
        Self::Mesh {
            mesh_name: mesh_name.into(),
            material: Some(material),
            casts_shadow: true,
            bounding_volume: None,
            physics: Some(physics),
        }
    }

    pub fn mesh_full(
        mesh_name: impl Into<String>,
        material: Option<MapMaterial>,
        casts_shadow: bool,
        bounding_volume: Option<BoundingVolume>,
        physics: Option<MapPhysics>,
    ) -> Self {
        Self::Mesh {
            mesh_name: mesh_name.into(),
            material,
            casts_shadow,
            bounding_volume,
            physics,
        }
    }

    pub fn instanced_mesh(mesh_name: impl Into<String>, instances: Vec<MeshInstance>) -> Self {
        Self::InstancedMesh {
            mesh_name: mesh_name.into(),
            instances,
            material: None,
            casts_shadow: true,
            bounding_volume: None,
        }
    }

    pub fn instanced_mesh_with_material(
        mesh_name: impl Into<String>,
        instances: Vec<MeshInstance>,
        material: MapMaterial,
    ) -> Self {
        Self::InstancedMesh {
            mesh_name: mesh_name.into(),
            instances,
            material: Some(material),
            casts_shadow: true,
            bounding_volume: None,
        }
    }

    pub fn instanced_mesh_full(
        mesh_name: impl Into<String>,
        instances: Vec<MeshInstance>,
        material: Option<MapMaterial>,
        casts_shadow: bool,
        bounding_volume: Option<BoundingVolume>,
    ) -> Self {
        Self::InstancedMesh {
            mesh_name: mesh_name.into(),
            instances,
            material,
            casts_shadow,
            bounding_volume,
        }
    }

    pub fn lines(lines: Vec<Line>) -> Self {
        Self::Lines(lines)
    }

    pub fn audio_source(audio_source: MapAudioSource) -> Self {
        Self::AudioSource(audio_source)
    }

    pub fn prefab(prefab_name: impl Into<String>) -> Self {
        Self::PrefabInstance {
            prefab_name: prefab_name.into(),
        }
    }

    pub fn custom(type_name: impl Into<String>, data: serde_json::Value) -> Self {
        Self::Custom {
            type_name: type_name.into(),
            data,
        }
    }

    pub fn is_entity(&self) -> bool {
        matches!(self, MapNode::Entity { .. })
    }

    pub fn is_component(&self) -> bool {
        !self.is_entity()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MeshInstance {
    pub translation: [f32; 3],
    pub rotation: [f32; 4],
    pub scale: [f32; 3],
    pub color: Option<[f32; 4]>,
}

impl MeshInstance {
    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 MeshInstance {
    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)]
pub enum MapLight {
    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 MapLight {
    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 {
            MapLight::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,
            },
            MapLight::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,
            },
            MapLight::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 MapLight {
    fn from(light: &Light) -> Self {
        let color = [light.color.x, light.color.y, light.color.z];
        match light.light_type {
            LightType::Directional => MapLight::Directional {
                color,
                intensity: light.intensity,
                cast_shadows: light.cast_shadows,
                shadow_bias: light.shadow_bias,
            },
            LightType::Point => MapLight::Point {
                color,
                intensity: light.intensity,
                range: light.range,
                cast_shadows: light.cast_shadows,
                shadow_bias: light.shadow_bias,
            },
            LightType::Spot => MapLight::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 MapCamera {
    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 MapCamera {
    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 {
            MapCamera::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,
            },
            MapCamera::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 MapCamera {
    fn from(camera: &Camera) -> Self {
        match &camera.projection {
            Projection::Perspective(perspective) => MapCamera::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) => MapCamera::Orthographic {
                x_mag: orthographic.x_mag,
                y_mag: orthographic.y_mag,
                z_far: orthographic.z_far,
                z_near: orthographic.z_near,
            },
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MapMaterial {
    pub base_color: [f32; 4],
    pub emissive_factor: [f32; 3],
    #[serde(default)]
    pub alpha_mode: MapAlphaMode,
    #[serde(default = "default_alpha_cutoff")]
    pub alpha_cutoff: f32,
    pub base_texture: Option<String>,
    pub emissive_texture: Option<String>,
    #[serde(default = "default_roughness")]
    pub roughness: f32,
    #[serde(default)]
    pub metallic: f32,
    #[serde(default)]
    pub unlit: bool,
    #[serde(default = "default_uv_scale")]
    pub uv_scale: [f32; 2],
}

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

fn default_alpha_cutoff() -> f32 {
    0.5
}

fn default_roughness() -> f32 {
    0.5
}

fn default_friction() -> f32 {
    0.8
}

fn default_restitution() -> f32 {
    0.1
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MapPhysics {
    pub body_type: MapBodyType,
    pub collider: MapCollider,
    #[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>,
}

impl MapPhysics {
    pub fn static_cuboid(half_extents: [f32; 3]) -> Self {
        Self {
            body_type: MapBodyType::Static,
            collider: MapCollider::Cuboid { half_extents },
            friction: default_friction(),
            restitution: default_restitution(),
            mass: None,
        }
    }

    pub fn static_from_mesh() -> Self {
        Self {
            body_type: MapBodyType::Static,
            collider: MapCollider::FromMesh,
            friction: default_friction(),
            restitution: default_restitution(),
            mass: None,
        }
    }

    pub fn kinematic_from_mesh() -> Self {
        Self {
            body_type: MapBodyType::Kinematic,
            collider: MapCollider::FromMesh,
            friction: default_friction(),
            restitution: default_restitution(),
            mass: None,
        }
    }

    pub fn dynamic_ball(radius: f32, mass: f32) -> Self {
        Self {
            body_type: MapBodyType::Dynamic,
            collider: MapCollider::Ball { radius },
            friction: 0.7,
            restitution: 0.3,
            mass: Some(mass),
        }
    }

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

    pub fn dynamic_cylinder(half_height: f32, radius: f32, mass: f32) -> Self {
        Self {
            body_type: MapBodyType::Dynamic,
            collider: MapCollider::Cylinder {
                half_height,
                radius,
            },
            friction: 0.5,
            restitution: 0.3,
            mass: Some(mass),
        }
    }
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
pub enum MapBodyType {
    #[default]
    Static,
    Dynamic,
    Kinematic,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MapCollider {
    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]>,
    },
    FromMesh,
}

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

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

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

impl Default for MapMaterial {
    fn default() -> Self {
        Self {
            base_color: [1.0, 1.0, 1.0, 1.0],
            emissive_factor: [0.0, 0.0, 0.0],
            alpha_mode: MapAlphaMode::Opaque,
            alpha_cutoff: 0.5,
            base_texture: None,
            emissive_texture: None,
            roughness: 0.5,
            metallic: 0.0,
            unlit: false,
            uv_scale: [1.0, 1.0],
        }
    }
}

impl MapMaterial {
    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.clone(),
            emissive_texture: self.emissive_texture.clone(),
            normal_texture: None,
            normal_scale: 1.0,
            normal_map_flip_y: false,
            normal_map_two_component: false,
            metallic_roughness_texture: None,
            occlusion_texture: None,
            occlusion_strength: 1.0,
            roughness: self.roughness,
            metallic: self.metallic,
            unlit: self.unlit,
            uv_scale: self.uv_scale,
            transmission_factor: 0.0,
            transmission_texture: None,
            thickness: 0.0,
            thickness_texture: None,
            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: None,
            specular_color_texture: None,
            emissive_strength: 1.0,
        }
    }
}

impl From<&Material> for MapMaterial {
    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: material.base_texture.clone(),
            emissive_texture: material.emissive_texture.clone(),
            roughness: material.roughness,
            metallic: material.metallic,
            unlit: material.unlit,
            uv_scale: material.uv_scale,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MapNavMesh {
    pub vertices: Vec<[f32; 3]>,
    pub triangles: Vec<MapNavMeshTriangle>,
    pub adjacency: HashMap<usize, Vec<MapNavMeshConnection>>,
}

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

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

impl MapNavMesh {
    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<MapNavMeshTriangle> = navmesh
            .triangles
            .iter()
            .map(|t| MapNavMeshTriangle {
                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],
            })
            .collect();

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

        Self {
            vertices,
            triangles,
            adjacency,
        }
    }

    pub fn to_navmesh_world(&self) -> crate::ecs::navmesh::resources::NavMeshWorld {
        use crate::ecs::navmesh::resources::{
            NavMeshConnection, 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: 0.0,
                edge_indices: [0, 0, 0],
            })
            .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: 0,
                        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: Vec::new(),
            adjacency,
            spatial_hash,
            debug_draw: false,
            debug_entity: None,
            algorithm: Default::default(),
        }
    }

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