use nalgebra_glm::{Vec3, vec3};

use crate::ecs::generational_registry::{registry_entry_by_name, registry_entry_by_name_mut};
use crate::ecs::lattice::components::{Lattice, LatticeInfluenced};
use crate::ecs::mesh::components::{MorphTarget, MorphTargetData};
use crate::ecs::morph::components::MorphWeights;
use crate::ecs::prefab::resources::mesh_cache_mark_dirty;
use crate::ecs::world::{
    Entity, GLOBAL_TRANSFORM, LATTICE, LATTICE_INFLUENCED, MORPH_WEIGHTS, RENDER_MESH, World,
};

pub fn lattice_deformation_system(world: &mut World) {
    let influenced_entities: Vec<Entity> = world
        .query_entities(LATTICE_INFLUENCED | RENDER_MESH | MORPH_WEIGHTS | GLOBAL_TRANSFORM)
        .collect();

    if influenced_entities.is_empty() {
        return;
    }

    let lattice_entities: Vec<(Entity, Lattice)> = world
        .query_entities(LATTICE)
        .filter_map(|entity| world.get_lattice(entity).cloned().map(|l| (entity, l)))
        .collect();

    if lattice_entities.is_empty() {
        return;
    }

    for entity in influenced_entities {
        let Some(influenced) = world.get_lattice_influenced(entity).cloned() else {
            continue;
        };

        let Some((_, lattice)) = lattice_entities
            .iter()
            .find(|(e, _)| *e == influenced.lattice_entity)
        else {
            continue;
        };

        let Some(global_transform) = world.get_global_transform(entity) else {
            continue;
        };

        let current_position = vec3(
            global_transform.0[(0, 3)],
            global_transform.0[(1, 3)],
            global_transform.0[(2, 3)],
        );

        if !influenced.needs_update(lattice.version, current_position) {
            continue;
        }

        let Some(render_mesh) = world.get_render_mesh(entity) else {
            continue;
        };

        let mesh_name = render_mesh.name.clone();

        let Some(mesh) = registry_entry_by_name(&world.resources.mesh_cache.registry, &mesh_name)
        else {
            continue;
        };

        let vertex_count = mesh.vertices.len();
        let model_matrix = global_transform.0;
        let _normal_matrix = nalgebra_glm::transpose(&nalgebra_glm::inverse(
            &nalgebra_glm::mat4_to_mat3(&model_matrix),
        ));

        let mut position_displacements = Vec::with_capacity(vertex_count);
        let mut normal_displacements = Vec::with_capacity(vertex_count);

        let base_positions: Vec<[f32; 3]> = mesh.vertices.iter().map(|v| v.position).collect();
        let base_normals: Vec<[f32; 3]> = mesh.vertices.iter().map(|v| v.normal).collect();

        for vertex in &mesh.vertices {
            let local_pos = vec3(vertex.position[0], vertex.position[1], vertex.position[2]);
            let world_pos = nalgebra_glm::vec4_to_vec3(
                &(model_matrix * nalgebra_glm::vec4(local_pos.x, local_pos.y, local_pos.z, 1.0)),
            );

            let displacement = lattice.sample(world_pos);

            let inverse_model = nalgebra_glm::inverse(&model_matrix);
            let local_displacement = nalgebra_glm::vec4_to_vec3(
                &(inverse_model
                    * nalgebra_glm::vec4(displacement.x, displacement.y, displacement.z, 0.0)),
            );

            position_displacements.push([
                local_displacement.x,
                local_displacement.y,
                local_displacement.z,
            ]);
            normal_displacements.push([0.0, 0.0, 0.0]);
        }

        let morph_target =
            MorphTarget::new(position_displacements).with_normals(normal_displacements);

        let morph_data = MorphTargetData::new(vec![morph_target])
            .with_base_data(base_positions, base_normals)
            .with_default_weights(vec![1.0]);

        let _ = mesh;

        if let Some(mesh) =
            registry_entry_by_name_mut(&mut world.resources.mesh_cache.registry, &mesh_name)
        {
            mesh.morph_targets = Some(morph_data);
        }

        mesh_cache_mark_dirty(&world.resources.mesh_cache, mesh_name.clone());

        if let Some(morph_weights) = world.get_morph_weights_mut(entity) {
            morph_weights.weights = vec![1.0];
        }

        if let Some(influenced) = world.get_lattice_influenced_mut(entity) {
            influenced.mark_updated(lattice.version, current_position);
        }
    }
}

pub fn create_lattice_entity(
    world: &mut World,
    bounds_min: Vec3,
    bounds_max: Vec3,
    dimensions: [usize; 3],
) -> Entity {
    let entity = world.spawn_entities(LATTICE, 1)[0];
    let lattice = Lattice::new(bounds_min, bounds_max, dimensions);
    world.set_lattice(entity, lattice);
    entity
}

pub fn register_entity_for_lattice_deformation(
    world: &mut World,
    entity: Entity,
    lattice_entity: Entity,
) {
    world.add_components(entity, LATTICE_INFLUENCED | MORPH_WEIGHTS);
    world.set_lattice_influenced(entity, LatticeInfluenced::new(lattice_entity));

    if let Some(render_mesh) = world.get_render_mesh(entity) {
        let mesh_name = render_mesh.name.clone();
        world.set_morph_weights(entity, MorphWeights::new(vec![1.0], mesh_name));
    }
}
