use std::rc::Rc;

use glam::Vec2;
use nalgebra::vector;
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};

use crate::components::{MeshComponent, PhysicsComponent, TreeInstancesComponent};
use crate::entity::EntityHandle;
use crate::loaders::mesh::{InstanceData, InstanceRaw, Mesh};
use crate::loaders::terrain::load_heightfield_from_exr;
use crate::paths;
use crate::physics::PhysicsManager;
use crate::render;
use crate::world::{Transform, World};

pub struct TerrainConfig
{
    pub gltf_path: String,
    pub heightmap_path: String,
    pub size: Vec2,
}

impl TerrainConfig
{
    pub fn new(gltf_path: &str, heightmap_path: &str, size: Vec2) -> Self
    {
        Self {
            gltf_path: gltf_path.to_string(),
            heightmap_path: heightmap_path.to_string(),
            size,
        }
    }

    pub fn default() -> Self
    {
        Self {
            gltf_path: paths::meshes::terrain(),
            heightmap_path: paths::textures::terrain_heightmap(),
            size: Vec2::new(1000.0, 1000.0),
        }
    }
}

pub struct TerrainBundle;

impl TerrainBundle
{
    pub fn spawn(
        world: &mut World,
        mesh_data: Vec<(Mesh, Vec<InstanceData>)>,
        config: &TerrainConfig,
    ) -> anyhow::Result<EntityHandle>
    {
        let transform = Transform::IDENTITY;
        let mut first_entity = None;
        let mut physics_added = false;

        for (mesh, instances) in mesh_data
        {
            let entity = world.spawn();

            if first_entity.is_none()
            {
                first_entity = Some(entity);
            }

            if instances.is_empty()
            {
                world.transforms.insert(entity, transform);
                world.meshes.insert(
                    entity,
                    MeshComponent {
                        mesh: Rc::new(mesh),
                        pipeline: render::Pipeline::Standard,
                        instance_buffer: None,
                        num_instances: 1,
                        tile_scale: 2.0,
                        enable_dissolve: false,
                        enable_snow_light: true,
                    },
                );
                world.names.insert(entity, "Terrain".to_string());

                if !physics_added
                {
                    let heights = load_heightfield_from_exr(&config.heightmap_path)?;

                    let height_scale = 1.0;
                    let scale = vector![config.size.x, height_scale, config.size.y];

                    let body = RigidBodyBuilder::fixed()
                        .translation(transform.get_position().into())
                        .build();

                    let rigidbody_handle = PhysicsManager::add_rigidbody(body);

                    let collider = ColliderBuilder::heightfield(heights.clone(), scale).build();

                    let collider_handle =
                        PhysicsManager::add_collider(collider, Some(rigidbody_handle));

                    PhysicsManager::set_heightfield_data(
                        heights,
                        scale,
                        transform.get_position().into(),
                    );

                    world.physics.insert(
                        entity,
                        PhysicsComponent {
                            rigidbody: rigidbody_handle,
                            collider: Some(collider_handle),
                        },
                    );

                    physics_added = true;
                }
            }
            else
            {
                let num_instances = instances.len();

                let instance_raw: Vec<InstanceRaw> = instances.iter().map(|i| i.to_raw()).collect();

                let instance_buffer = render::with_device(|device| {
                    use wgpu::util::DeviceExt;
                    device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
                        label: Some("Tree Instance Buffer"),
                        contents: bytemuck::cast_slice(&instance_raw),
                        usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
                    })
                });

                world.transforms.insert(entity, Transform::IDENTITY);
                world.meshes.insert(
                    entity,
                    MeshComponent {
                        mesh: Rc::new(mesh),
                        pipeline: render::Pipeline::Standard,
                        instance_buffer: Some(instance_buffer.clone()),
                        num_instances: num_instances as u32,
                        tile_scale: 4.0,
                        enable_dissolve: true,
                        enable_snow_light: false,
                    },
                );
                world.tree_instances.insert(
                    entity,
                    TreeInstancesComponent::new(instances, Rc::new(instance_buffer)),
                );
                world.names.insert(entity, "Trees".to_string());
            }
        }

        first_entity.ok_or_else(|| anyhow::anyhow!("No meshes found in glTF file"))
    }
}
