use crate::ecs::animation::components::{AnimationClip, AnimationPlayer};
use crate::ecs::bounding_volume::components::BoundingVolume;
use crate::ecs::world::components::{GlobalTransform, LocalTransformDirty, Parent};
use crate::ecs::world::{
    ANIMATION_PLAYER, BOUNDING_VOLUME, CAMERA, CASTS_SHADOW, COLLIDER, GLOBAL_TRANSFORM,
    LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY, MATERIAL, NAME, PARENT, RENDER_MESH, RIGID_BODY,
    VISIBILITY, Vec3, World,
};
use freecs::Entity;
use std::collections::HashMap;

use super::super::components::{Prefab, PrefabNode};

fn get_bounding_volume_from_mesh_name(world: &World, mesh_name: &str) -> Option<BoundingVolume> {
    let mesh = world.resources.mesh_cache.get(mesh_name)?;
    mesh.bounding_volume
}

pub fn spawn_prefab(world: &mut World, prefab: &Prefab, offset: Vec3) -> Entity {
    let parent_entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY,
        1,
    )[0];

    world.set_name(
        parent_entity,
        crate::ecs::world::components::Name(prefab.name.clone()),
    );
    world.set_local_transform(
        parent_entity,
        crate::ecs::world::components::LocalTransform {
            translation: offset,
            rotation: nalgebra_glm::Quat::identity(),
            scale: nalgebra_glm::vec3(1.0, 1.0, 1.0),
        },
    );
    world.set_global_transform(parent_entity, GlobalTransform::default());
    world.set_local_transform_dirty(parent_entity, LocalTransformDirty);

    for root_node in &prefab.root_nodes {
        spawn_prefab_node(world, root_node, Vec3::zeros(), Some(parent_entity));
    }

    parent_entity
}

pub fn spawn_prefab_node(
    world: &mut World,
    node: &PrefabNode,
    offset: Vec3,
    parent_entity: Option<Entity>,
) -> Entity {
    let calculated_bounding_volume = if node.components.bounding_volume.is_none() {
        if let Some(render_mesh) = &node.components.render_mesh {
            get_bounding_volume_from_mesh_name(world, &render_mesh.name)
        } else {
            None
        }
    } else {
        None
    };

    let mut component_mask = LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY;

    if node.components.name.is_some() {
        component_mask |= NAME;
    }

    if node.components.render_mesh.is_some() {
        component_mask |= RENDER_MESH | CASTS_SHADOW;
    }

    if node.components.material.is_some() {
        component_mask |= MATERIAL;
    }

    if node.components.camera.is_some() {
        component_mask |= CAMERA;
    }

    if node.components.visibility.is_some() {
        component_mask |= VISIBILITY;
    }

    if node.components.bounding_volume.is_some() || calculated_bounding_volume.is_some() {
        component_mask |= BOUNDING_VOLUME;
    }

    if node.components.rigid_body.is_some() {
        component_mask |= RIGID_BODY;
    }

    if node.components.collider.is_some() {
        component_mask |= COLLIDER;
    }

    if parent_entity.is_some() {
        component_mask |= PARENT;
    }

    let entity = world.spawn_entities(component_mask, 1)[0];

    let mut local_transform = node.local_transform;
    if parent_entity.is_none() {
        local_transform.translation += offset;
    }

    world.set_local_transform(entity, local_transform);
    world.set_global_transform(entity, GlobalTransform::default());
    world.set_local_transform_dirty(entity, LocalTransformDirty);

    if let Some(name) = &node.components.name {
        world.set_name(entity, name.clone());
    }

    if let Some(render_mesh) = &node.components.render_mesh {
        if !world.resources.mesh_cache.contains(&render_mesh.name) {
            log::error!(
                "Mesh '{}' not found in mesh_cache when spawning entity {:?}. Skipping RenderMesh component.",
                render_mesh.name,
                entity
            );
        } else {
            world.resources.mesh_cache.add_reference(&render_mesh.name);
            world.set_render_mesh(entity, render_mesh.clone());
            world.set_casts_shadow(entity, crate::ecs::shadow::components::CastsShadow);
        }
    }

    if let Some(material) = &node.components.material {
        let mut material_to_set = material.clone();

        if let Some(base_texture) = &material.base_texture {
            world.resources.texture_cache.add_reference(base_texture);
        }
        if let Some(emissive_texture) = &material.emissive_texture {
            world
                .resources
                .texture_cache
                .add_reference(emissive_texture);
        }

        let material_id = world
            .resources
            .material_cache
            .insert_or_get_id(&material_to_set);
        world.resources.material_cache.add_reference(&material_id);
        material_to_set.cache_id = Some(material_id);

        world.set_material(entity, material_to_set);
    }

    if let Some(camera) = &node.components.camera {
        world.set_camera(entity, *camera);
    }

    if let Some(visibility) = &node.components.visibility {
        world.set_visibility(entity, visibility.clone());
    }

    if let Some(bounding_volume) = &node.components.bounding_volume {
        world.set_bounding_volume(entity, *bounding_volume);
    } else if let Some(calculated_bv) = calculated_bounding_volume {
        world.set_bounding_volume(entity, calculated_bv);
    }

    if let Some(prefab_rigid_body) = &node.components.rigid_body {
        let mut rigid_body_component = prefab_rigid_body.to_component();
        rigid_body_component.translation = local_transform.translation.into();
        let quat = local_transform.rotation;
        rigid_body_component.rotation = [quat.i, quat.j, quat.k, quat.w];

        let rapier_rigid_body = rigid_body_component.to_rapier_rigid_body();
        let handle = world.resources.physics.add_rigid_body(rapier_rigid_body);
        rigid_body_component.handle = Some(handle);

        if let Some(prefab_collider) = &node.components.collider {
            let mut collider_component = prefab_collider.to_component();
            let rapier_collider = collider_component.to_rapier_collider();
            let collider_handle = world
                .resources
                .physics
                .add_collider(rapier_collider, handle);
            collider_component.handle = Some(collider_handle);
            world.set_collider(entity, collider_component);
        }

        world.set_rigid_body(entity, rigid_body_component);
    }

    if let Some(parent) = parent_entity {
        if world.get_local_transform(parent).is_some() {
            world.set_parent(entity, Parent(Some(parent)));

            world
                .resources
                .children_cache
                .entry(parent)
                .or_default()
                .push(entity);
        } else {
            log::warn!(
                "Parent entity {:?} does not exist or lacks LocalTransform component when spawning entity {:?}",
                parent,
                entity
            );
        }
    }

    for child_node in &node.children {
        spawn_prefab_node(world, child_node, offset, Some(entity));
    }

    entity
}

pub fn spawn_prefab_with_animations(
    world: &mut World,
    prefab: &Prefab,
    animations: &[AnimationClip],
    offset: Vec3,
) -> Entity {
    let parent_entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY,
        1,
    )[0];

    world.set_name(
        parent_entity,
        crate::ecs::world::components::Name(prefab.name.clone()),
    );
    world.set_local_transform(
        parent_entity,
        crate::ecs::world::components::LocalTransform {
            translation: offset,
            rotation: nalgebra_glm::Quat::identity(),
            scale: nalgebra_glm::vec3(1.0, 1.0, 1.0),
        },
    );
    world.set_global_transform(parent_entity, GlobalTransform::default());
    world.set_local_transform_dirty(parent_entity, LocalTransformDirty);

    let mut node_index_to_entity: HashMap<usize, Entity> = HashMap::new();

    for root_node in &prefab.root_nodes {
        spawn_prefab_node_with_mapping(
            world,
            root_node,
            Vec3::zeros(),
            Some(parent_entity),
            &mut node_index_to_entity,
        );
    }

    if !animations.is_empty() {
        let mut animation_player = AnimationPlayer {
            clips: animations.to_vec(),
            current_clip: if animations.is_empty() { None } else { Some(0) },
            time: 0.0,
            speed: 1.0,
            looping: true,
            playing: true,
            node_index_to_entity,
        };

        if !animation_player.clips.is_empty() {
            animation_player.play(0);
        }

        world.add_components(parent_entity, ANIMATION_PLAYER);
        world.set_animation_player(parent_entity, animation_player);
    }

    parent_entity
}

fn spawn_prefab_node_with_mapping(
    world: &mut World,
    node: &PrefabNode,
    offset: Vec3,
    parent_entity: Option<Entity>,
    node_index_to_entity: &mut HashMap<usize, Entity>,
) -> Entity {
    let calculated_bounding_volume = if node.components.bounding_volume.is_none() {
        if let Some(render_mesh) = &node.components.render_mesh {
            get_bounding_volume_from_mesh_name(world, &render_mesh.name)
        } else {
            None
        }
    } else {
        None
    };

    let mut component_mask = LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY;

    if node.components.name.is_some() {
        component_mask |= NAME;
    }

    if node.components.render_mesh.is_some() {
        component_mask |= RENDER_MESH | CASTS_SHADOW;
    }

    if node.components.material.is_some() {
        component_mask |= MATERIAL;
    }

    if node.components.camera.is_some() {
        component_mask |= CAMERA;
    }

    if node.components.visibility.is_some() {
        component_mask |= VISIBILITY;
    }

    if node.components.bounding_volume.is_some() || calculated_bounding_volume.is_some() {
        component_mask |= BOUNDING_VOLUME;
    }

    if node.components.rigid_body.is_some() {
        component_mask |= RIGID_BODY;
    }

    if node.components.collider.is_some() {
        component_mask |= COLLIDER;
    }

    if parent_entity.is_some() {
        component_mask |= PARENT;
    }

    let entity = world.spawn_entities(component_mask, 1)[0];

    if let Some(node_index) = node.node_index {
        node_index_to_entity.insert(node_index, entity);
    }

    let mut local_transform = node.local_transform;
    if parent_entity.is_none() {
        local_transform.translation += offset;
    }

    world.set_local_transform(entity, local_transform);
    world.set_global_transform(entity, GlobalTransform::default());
    world.set_local_transform_dirty(entity, LocalTransformDirty);

    if let Some(name) = &node.components.name {
        world.set_name(entity, name.clone());
    }

    if let Some(render_mesh) = &node.components.render_mesh {
        if !world.resources.mesh_cache.contains(&render_mesh.name) {
            log::error!(
                "Mesh '{}' not found in mesh_cache when spawning entity {:?}. Skipping RenderMesh component.",
                render_mesh.name,
                entity
            );
        } else {
            world.resources.mesh_cache.add_reference(&render_mesh.name);
            world.set_render_mesh(entity, render_mesh.clone());
            world.set_casts_shadow(entity, crate::ecs::shadow::components::CastsShadow);
        }
    }

    if let Some(material) = &node.components.material {
        let mut material_to_set = material.clone();

        if let Some(base_texture) = &material.base_texture {
            world.resources.texture_cache.add_reference(base_texture);
        }
        if let Some(emissive_texture) = &material.emissive_texture {
            world
                .resources
                .texture_cache
                .add_reference(emissive_texture);
        }

        let material_id = world
            .resources
            .material_cache
            .insert_or_get_id(&material_to_set);
        world.resources.material_cache.add_reference(&material_id);
        material_to_set.cache_id = Some(material_id);

        world.set_material(entity, material_to_set);
    }

    if let Some(camera) = &node.components.camera {
        world.set_camera(entity, *camera);
    }

    if let Some(visibility) = &node.components.visibility {
        world.set_visibility(entity, visibility.clone());
    }

    if let Some(bounding_volume) = &node.components.bounding_volume {
        world.set_bounding_volume(entity, *bounding_volume);
    } else if let Some(calculated_bv) = calculated_bounding_volume {
        world.set_bounding_volume(entity, calculated_bv);
    }

    if let Some(prefab_rigid_body) = &node.components.rigid_body {
        let mut rigid_body_component = prefab_rigid_body.to_component();
        rigid_body_component.translation = local_transform.translation.into();
        let quat = local_transform.rotation;
        rigid_body_component.rotation = [quat.i, quat.j, quat.k, quat.w];

        let rapier_rigid_body = rigid_body_component.to_rapier_rigid_body();
        let handle = world.resources.physics.add_rigid_body(rapier_rigid_body);
        rigid_body_component.handle = Some(handle);

        if let Some(prefab_collider) = &node.components.collider {
            let mut collider_component = prefab_collider.to_component();
            let rapier_collider = collider_component.to_rapier_collider();
            let collider_handle = world
                .resources
                .physics
                .add_collider(rapier_collider, handle);
            collider_component.handle = Some(collider_handle);
            world.set_collider(entity, collider_component);
        }

        world.set_rigid_body(entity, rigid_body_component);
    }

    if let Some(parent) = parent_entity {
        if world.get_local_transform(parent).is_some() {
            world.set_parent(entity, Parent(Some(parent)));

            world
                .resources
                .children_cache
                .entry(parent)
                .or_default()
                .push(entity);
        } else {
            log::warn!(
                "Parent entity {:?} does not exist or lacks LocalTransform component when spawning entity {:?}",
                parent,
                entity
            );
        }
    }

    for child_node in &node.children {
        spawn_prefab_node_with_mapping(
            world,
            child_node,
            offset,
            Some(entity),
            node_index_to_entity,
        );
    }

    entity
}
