use std::collections::HashMap;

use freecs::Entity;

use crate::ecs::physics::resources::{
    JointType, physics_world_add_collider, physics_world_add_rigid_body,
};
use crate::ecs::world::{CHARACTER_CONTROLLER, COLLIDER, PHYSICS_INTERPOLATION, RIGID_BODY, World};

use super::asset_uuid::AssetUuid;
use super::physics::{SceneBodyType, SceneJoint, SceneJointConnection};

pub(super) fn scene_joint_to_generic_joint(
    joint: &SceneJoint,
) -> (rapier3d::prelude::GenericJoint, JointType) {
    use rapier3d::prelude::*;

    match joint {
        SceneJoint::Fixed {
            parent_anchor,
            child_anchor,
        } => (
            FixedJointBuilder::new()
                .local_anchor1(point![parent_anchor[0], parent_anchor[1], parent_anchor[2]])
                .local_anchor2(point![child_anchor[0], child_anchor[1], child_anchor[2]])
                .build()
                .into(),
            JointType::Fixed,
        ),
        SceneJoint::Revolute {
            parent_anchor,
            child_anchor,
            axis,
            limits,
        } => {
            let axis_unit = UnitVector::new_normalize(vector![axis[0], axis[1], axis[2]]);
            let mut builder = RevoluteJointBuilder::new(axis_unit)
                .local_anchor1(point![parent_anchor[0], parent_anchor[1], parent_anchor[2]])
                .local_anchor2(point![child_anchor[0], child_anchor[1], child_anchor[2]]);
            if let Some([min, max]) = limits {
                builder = builder.limits([*min, *max]);
            }
            (builder.build().into(), JointType::Revolute)
        }
        SceneJoint::Prismatic {
            parent_anchor,
            child_anchor,
            axis,
            limits,
        } => {
            let axis_unit = UnitVector::new_normalize(vector![axis[0], axis[1], axis[2]]);
            let mut builder = PrismaticJointBuilder::new(axis_unit)
                .local_anchor1(point![parent_anchor[0], parent_anchor[1], parent_anchor[2]])
                .local_anchor2(point![child_anchor[0], child_anchor[1], child_anchor[2]]);
            if let Some([min, max]) = limits {
                builder = builder.limits([*min, *max]);
            }
            (builder.build().into(), JointType::Prismatic)
        }
        SceneJoint::Spherical {
            parent_anchor,
            child_anchor,
        } => (
            SphericalJointBuilder::new()
                .local_anchor1(point![parent_anchor[0], parent_anchor[1], parent_anchor[2]])
                .local_anchor2(point![child_anchor[0], child_anchor[1], child_anchor[2]])
                .build()
                .into(),
            JointType::Spherical,
        ),
        SceneJoint::Rope {
            parent_anchor,
            child_anchor,
            max_distance,
        } => (
            RopeJointBuilder::new(*max_distance)
                .local_anchor1(point![parent_anchor[0], parent_anchor[1], parent_anchor[2]])
                .local_anchor2(point![child_anchor[0], child_anchor[1], child_anchor[2]])
                .build()
                .into(),
            JointType::Rope,
        ),
        SceneJoint::Spring {
            parent_anchor,
            child_anchor,
            rest_length,
            stiffness,
            damping,
        } => (
            SpringJointBuilder::new(*rest_length, *stiffness, *damping)
                .local_anchor1(point![parent_anchor[0], parent_anchor[1], parent_anchor[2]])
                .local_anchor2(point![child_anchor[0], child_anchor[1], child_anchor[2]])
                .build()
                .into(),
            JointType::Spring,
        ),
    }
}

pub(super) fn apply_physics_component(
    world: &mut World,
    entity: Entity,
    physics: &super::physics::ScenePhysics,
) {
    let transform = world
        .core
        .get_local_transform(entity)
        .copied()
        .unwrap_or_default();

    let is_dynamic = matches!(physics.body_type, SceneBodyType::Dynamic);

    let mut components = RIGID_BODY | COLLIDER;
    if is_dynamic {
        components |= PHYSICS_INTERPOLATION;
    }
    world.core.add_components(entity, components);

    let mut rigid_body = physics.to_rigid_body();
    rigid_body = rigid_body.with_translation(
        transform.translation.x,
        transform.translation.y,
        transform.translation.z,
    );
    world.core.set_rigid_body(entity, rigid_body);

    let collider = physics.to_collider();
    world.core.set_collider(entity, collider);

    let rigid_body_comp = world.core.get_rigid_body(entity).cloned().unwrap();
    let collider_comp = world.core.get_collider(entity).cloned();
    let rapier_body = rigid_body_comp.to_rapier_rigid_body();
    let rapier_handle = physics_world_add_rigid_body(&mut world.resources.physics, rapier_body);
    if let Some(collider_comp) = collider_comp {
        let rapier_collider = collider_comp.to_rapier_collider();
        physics_world_add_collider(&mut world.resources.physics, rapier_collider, rapier_handle);
    }
    if let Some(rigid_body_mut) = world.core.get_rigid_body_mut(entity) {
        rigid_body_mut.handle = Some(crate::ecs::physics::types::rigid_body_handle_from_rapier(
            rapier_handle,
        ));
    }
    world
        .resources
        .physics
        .handle_to_entity
        .insert(rapier_handle, entity);

    if is_dynamic && let Some(interpolation) = world.core.get_physics_interpolation_mut(entity) {
        interpolation.enabled = true;
        interpolation.previous_translation = transform.translation;
        interpolation.current_translation = transform.translation;
        interpolation.previous_rotation = transform.rotation;
        interpolation.current_rotation = transform.rotation;
    }
}

pub(super) fn spawn_scene_joints(
    world: &mut World,
    joints: &[SceneJointConnection],
    uuid_to_entity: &HashMap<AssetUuid, Entity>,
    warnings: &mut Vec<String>,
) {
    for joint_connection in joints {
        if let (Some(&parent_entity), Some(&child_entity)) = (
            uuid_to_entity.get(&joint_connection.parent_entity),
            uuid_to_entity.get(&joint_connection.child_entity),
        ) {
            let (generic_joint, joint_type) = scene_joint_to_generic_joint(&joint_connection.joint);
            let (spring_rest_length, spring_stiffness, spring_damping) =
                if let SceneJoint::Spring {
                    rest_length,
                    stiffness,
                    damping,
                    ..
                } = &joint_connection.joint
                {
                    (Some(*rest_length), Some(*stiffness), Some(*damping))
                } else {
                    (None, None, None)
                };
            world.resources.physics.pending_joints.push(
                crate::ecs::physics::resources::PendingJoint {
                    parent_entity,
                    child_entity,
                    joint: generic_joint,
                    joint_type,
                    collisions_enabled: joint_connection.collisions_enabled,
                    spring_rest_length,
                    spring_stiffness,
                    spring_damping,
                },
            );
        } else {
            warnings.push(format!(
                "Joint references unknown entities: parent={}, child={}",
                joint_connection.parent_entity, joint_connection.child_entity
            ));
        }
    }
}

pub(super) fn export_entity_physics(
    world: &World,
    entity: Entity,
    components: &mut super::components::SceneComponents,
) {
    if let Some(rigid_body) = world.core.get_rigid_body(entity)
        && let Some(collider) = world.core.get_collider(entity)
    {
        use crate::ecs::physics::types::RigidBodyType;

        let body_type = match rigid_body.body_type {
            RigidBodyType::Fixed => SceneBodyType::Static,
            RigidBodyType::Dynamic => SceneBodyType::Dynamic,
            RigidBodyType::KinematicPositionBased => SceneBodyType::KinematicPositionBased,
            RigidBodyType::KinematicVelocityBased => SceneBodyType::KinematicVelocityBased,
        };

        let scene_collider = match &collider.shape {
            crate::ecs::physics::ColliderShape::Ball { radius } => {
                super::physics::SceneCollider::Ball { radius: *radius }
            }
            crate::ecs::physics::ColliderShape::Cuboid { hx, hy, hz } => {
                super::physics::SceneCollider::Cuboid {
                    half_extents: [*hx, *hy, *hz],
                }
            }
            crate::ecs::physics::ColliderShape::Cylinder {
                half_height,
                radius,
            } => super::physics::SceneCollider::Cylinder {
                half_height: *half_height,
                radius: *radius,
            },
            crate::ecs::physics::ColliderShape::Capsule {
                half_height,
                radius,
            } => super::physics::SceneCollider::Capsule {
                half_height: *half_height,
                radius: *radius,
            },
            crate::ecs::physics::ColliderShape::TriMesh { vertices, indices } => {
                super::physics::SceneCollider::TriMesh {
                    vertices: vertices.clone(),
                    indices: indices.clone(),
                }
            }
            crate::ecs::physics::ColliderShape::ConvexMesh { vertices } => {
                super::physics::SceneCollider::ConvexHull {
                    points: vertices.clone(),
                }
            }
            crate::ecs::physics::ColliderShape::Cone {
                half_height,
                radius,
            } => super::physics::SceneCollider::Cone {
                half_height: *half_height,
                radius: *radius,
            },
            crate::ecs::physics::ColliderShape::HeightField {
                nrows,
                ncols,
                heights,
                scale,
            } => super::physics::SceneCollider::HeightField {
                nrows: *nrows,
                ncols: *ncols,
                heights: heights.clone(),
                scale: *scale,
            },
        };

        components.physics = Some(super::physics::ScenePhysics {
            body_type,
            collider: scene_collider,
            friction: collider.friction,
            restitution: collider.restitution,
            mass: Some(rigid_body.mass),
            is_sensor: collider.is_sensor,
            collision_membership: collider.collision_groups.memberships,
            collision_filter: collider.collision_groups.filter,
            solver_membership: collider.solver_groups.memberships,
            solver_filter: collider.solver_groups.filter,
            locked_axes: rigid_body.locked_axes,
        });
    }

    if let Some(character_controller) = world.core.get_character_controller(entity) {
        components.character_controller = Some(
            super::character_controller::SceneCharacterController::from(character_controller),
        );
    }
}

pub(super) fn export_scene_joints(
    world: &World,
    entity_to_uuid: &HashMap<Entity, AssetUuid>,
    scene: &mut super::components::Scene,
) {
    for (handle, info) in &world.resources.physics.joint_registry {
        let parent_uuid = entity_to_uuid.get(&info.parent_entity).copied();
        let child_uuid = entity_to_uuid.get(&info.child_entity).copied();

        if let (Some(parent_uuid), Some(child_uuid)) = (parent_uuid, child_uuid)
            && let Some(rapier_joint) = world.resources.physics.impulse_joint_set.get(*handle)
        {
            let anchor1 = rapier_joint.data.local_anchor1();
            let anchor2 = rapier_joint.data.local_anchor2();
            let parent_anchor = [anchor1.x, anchor1.y, anchor1.z];
            let child_anchor = [anchor2.x, anchor2.y, anchor2.z];

            let scene_joint = match info.joint_type {
                JointType::Fixed => SceneJoint::Fixed {
                    parent_anchor,
                    child_anchor,
                },
                JointType::Revolute => {
                    let axis = rapier_joint.data.local_axis1();
                    SceneJoint::Revolute {
                        parent_anchor,
                        child_anchor,
                        axis: [axis.x, axis.y, axis.z],
                        limits: rapier_joint
                            .data
                            .limits(rapier3d::prelude::JointAxis::AngX)
                            .map(|l| [l.min, l.max]),
                    }
                }
                JointType::Prismatic => {
                    let axis = rapier_joint.data.local_axis1();
                    SceneJoint::Prismatic {
                        parent_anchor,
                        child_anchor,
                        axis: [axis.x, axis.y, axis.z],
                        limits: rapier_joint
                            .data
                            .limits(rapier3d::prelude::JointAxis::LinX)
                            .map(|l| [l.min, l.max]),
                    }
                }
                JointType::Spherical => SceneJoint::Spherical {
                    parent_anchor,
                    child_anchor,
                },
                JointType::Rope => SceneJoint::Rope {
                    parent_anchor,
                    child_anchor,
                    max_distance: rapier_joint
                        .data
                        .limits(rapier3d::prelude::JointAxis::LinX)
                        .map(|l| l.max)
                        .unwrap_or(1.0),
                },
                JointType::Spring => SceneJoint::Spring {
                    parent_anchor,
                    child_anchor,
                    rest_length: info.spring_rest_length.unwrap_or(1.0),
                    stiffness: info.spring_stiffness.unwrap_or(1.0),
                    damping: info.spring_damping.unwrap_or(0.1),
                },
            };

            scene.joints.push(SceneJointConnection {
                parent_entity: parent_uuid,
                child_entity: child_uuid,
                joint: scene_joint,
                collisions_enabled: info.collisions_enabled,
            });
        }
    }
}

pub(super) fn apply_physics_settings(world: &mut World, settings: &super::settings::SceneSettings) {
    world.resources.physics.gravity = rapier3d::prelude::vector![
        settings.gravity[0],
        settings.gravity[1],
        settings.gravity[2]
    ];
    world.resources.physics.fixed_timestep = settings.physics_timestep;
    world.resources.physics.integration_parameters.dt = settings.physics_timestep;
    world.resources.physics.max_substeps = settings.physics_max_substeps;
}

pub(super) fn capture_physics_settings(world: &World) -> ([f32; 3], f32, u32) {
    let physics = &world.resources.physics;
    (
        [physics.gravity.x, physics.gravity.y, physics.gravity.z],
        physics.fixed_timestep,
        physics.max_substeps,
    )
}

pub(super) fn apply_character_controller(
    world: &mut World,
    entity: Entity,
    scene_cc: &super::character_controller::SceneCharacterController,
) {
    world.core.add_components(entity, CHARACTER_CONTROLLER);
    world
        .core
        .set_character_controller(entity, scene_cc.to_character_controller());
}
