use std::collections::HashMap;

use serde::{Deserialize, Serialize};

#[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 is_empty(&self) -> bool {
        self.triangles.is_empty()
    }
}

#[cfg(feature = "navmesh")]
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(),
        }
    }
}
