use super::*;
use crate::ecs::world::{Entity, Vec3, World};

pub fn create_textured_material(
    base_color: Vec3,
    roughness: f32,
    metallic: f32,
) -> crate::ecs::material::components::Material {
    crate::ecs::material::components::Material {
        base_color: [base_color.x, base_color.y, base_color.z, 1.0],
        emissive_factor: [0.0, 0.0, 0.0],
        roughness,
        metallic,
        ..Default::default()
    }
}

pub fn create_emissive_material(
    base_color: Vec3,
    emissive_strength: f32,
) -> crate::ecs::material::components::Material {
    crate::ecs::material::components::Material {
        base_color: [base_color.x, base_color.y, base_color.z, 1.0],
        emissive_factor: [
            base_color.x * emissive_strength,
            base_color.y * emissive_strength,
            base_color.z * emissive_strength,
        ],
        ..Default::default()
    }
}

pub fn spawn_static_physics_cube_with_material(
    world: &mut World,
    position: Vec3,
    size: Vec3,
    material: crate::ecs::material::components::Material,
) -> Entity {
    let entity = world.spawn_entities(
        crate::ecs::world::NAME
            | crate::ecs::world::LOCAL_TRANSFORM
            | crate::ecs::world::GLOBAL_TRANSFORM
            | crate::ecs::world::LOCAL_TRANSFORM_DIRTY
            | crate::ecs::world::RENDER_MESH
            | crate::ecs::world::MATERIAL
            | crate::ecs::world::BOUNDING_VOLUME
            | crate::ecs::world::CASTS_SHADOW
            | crate::ecs::world::RIGID_BODY
            | crate::ecs::world::COLLIDER
            | crate::ecs::world::VISIBILITY,
        1,
    )[0];

    if let Some(name) = world.get_name_mut(entity) {
        name.0 = "Physics Cube".to_string();
    }

    if let Some(transform) = world.get_local_transform_mut(entity) {
        transform.translation = position;
        transform.scale = size;
    }

    if let Some(mesh) = world.get_render_mesh_mut(entity) {
        mesh.name = "Cube".to_string();
    }

    if let Some(mat) = world.get_material_mut(entity) {
        *mat = material;
    }

    if let Some(bv) = world.get_bounding_volume_mut(entity) {
        *bv = crate::ecs::world::components::BoundingVolume::from_mesh_type("Cube");
    }

    if let Some(rigid_body) = world.get_rigid_body_mut(entity) {
        *rigid_body =
            RigidBodyComponent::new_static().with_translation(position.x, position.y, position.z);
    }

    if let Some(collider) = world.get_collider_mut(entity) {
        *collider = ColliderComponent::new_cuboid(size.x / 2.0, size.y / 2.0, size.z / 2.0)
            .with_friction(0.8)
            .with_restitution(0.1);
    }

    entity
}

pub fn spawn_dynamic_physics_sphere_with_material(
    world: &mut World,
    position: Vec3,
    radius: f32,
    mass: f32,
    material: crate::ecs::material::components::Material,
) -> Entity {
    let scale_factor = radius;
    let entity = world.spawn_entities(
        crate::ecs::world::NAME
            | crate::ecs::world::LOCAL_TRANSFORM
            | crate::ecs::world::GLOBAL_TRANSFORM
            | crate::ecs::world::LOCAL_TRANSFORM_DIRTY
            | crate::ecs::world::RENDER_MESH
            | crate::ecs::world::MATERIAL
            | crate::ecs::world::BOUNDING_VOLUME
            | crate::ecs::world::CASTS_SHADOW
            | crate::ecs::world::RIGID_BODY
            | crate::ecs::world::COLLIDER
            | crate::ecs::world::PHYSICS_INTERPOLATION
            | crate::ecs::world::VISIBILITY,
        1,
    )[0];

    if let Some(name) = world.get_name_mut(entity) {
        name.0 = "Physics Sphere".to_string();
    }

    if let Some(transform) = world.get_local_transform_mut(entity) {
        transform.translation = position;
        transform.scale = nalgebra_glm::vec3(scale_factor, scale_factor, scale_factor);
    }

    if let Some(mesh) = world.get_render_mesh_mut(entity) {
        mesh.name = "Sphere".to_string();
    }

    if let Some(mat) = world.get_material_mut(entity) {
        *mat = material;
    }

    if let Some(bv) = world.get_bounding_volume_mut(entity) {
        *bv = crate::ecs::world::components::BoundingVolume::from_mesh_type("Sphere");
    }

    if let Some(rigid_body) = world.get_rigid_body_mut(entity) {
        *rigid_body = RigidBodyComponent::new_dynamic()
            .with_translation(position.x, position.y, position.z)
            .with_mass(mass);
    }

    if let Some(collider) = world.get_collider_mut(entity) {
        *collider = ColliderComponent::new_ball(radius)
            .with_friction(0.7)
            .with_restitution(0.3);
    }

    entity
}

pub fn spawn_dynamic_physics_cube_with_material(
    world: &mut World,
    position: Vec3,
    size: Vec3,
    mass: f32,
    material: crate::ecs::material::components::Material,
) -> Entity {
    let entity = world.spawn_entities(
        crate::ecs::world::NAME
            | crate::ecs::world::LOCAL_TRANSFORM
            | crate::ecs::world::GLOBAL_TRANSFORM
            | crate::ecs::world::LOCAL_TRANSFORM_DIRTY
            | crate::ecs::world::RENDER_MESH
            | crate::ecs::world::MATERIAL
            | crate::ecs::world::BOUNDING_VOLUME
            | crate::ecs::world::CASTS_SHADOW
            | crate::ecs::world::RIGID_BODY
            | crate::ecs::world::COLLIDER
            | crate::ecs::world::PHYSICS_INTERPOLATION
            | crate::ecs::world::VISIBILITY,
        1,
    )[0];

    if let Some(name) = world.get_name_mut(entity) {
        name.0 = "Physics Cube".to_string();
    }

    if let Some(transform) = world.get_local_transform_mut(entity) {
        transform.translation = position;
        transform.scale = size;
    }

    if let Some(mesh) = world.get_render_mesh_mut(entity) {
        mesh.name = "Cube".to_string();
    }

    if let Some(mat) = world.get_material_mut(entity) {
        *mat = material;
    }

    if let Some(bv) = world.get_bounding_volume_mut(entity) {
        *bv = crate::ecs::world::components::BoundingVolume::from_mesh_type("Cube");
    }

    if let Some(rigid_body) = world.get_rigid_body_mut(entity) {
        *rigid_body = RigidBodyComponent::new_dynamic()
            .with_translation(position.x, position.y, position.z)
            .with_mass(mass);
    }

    if let Some(collider) = world.get_collider_mut(entity) {
        *collider = ColliderComponent::new_cuboid(size.x / 2.0, size.y / 2.0, size.z / 2.0)
            .with_friction(0.7)
            .with_restitution(0.2);
    }

    entity
}

pub fn spawn_dynamic_physics_cylinder_with_material(
    world: &mut World,
    position: Vec3,
    half_height: f32,
    radius: f32,
    mass: f32,
    material: crate::ecs::material::components::Material,
) -> Entity {
    let entity = world.spawn_entities(
        crate::ecs::world::NAME
            | crate::ecs::world::LOCAL_TRANSFORM
            | crate::ecs::world::GLOBAL_TRANSFORM
            | crate::ecs::world::LOCAL_TRANSFORM_DIRTY
            | crate::ecs::world::RENDER_MESH
            | crate::ecs::world::MATERIAL
            | crate::ecs::world::BOUNDING_VOLUME
            | crate::ecs::world::CASTS_SHADOW
            | crate::ecs::world::RIGID_BODY
            | crate::ecs::world::COLLIDER
            | crate::ecs::world::PHYSICS_INTERPOLATION
            | crate::ecs::world::VISIBILITY,
        1,
    )[0];

    if let Some(name) = world.get_name_mut(entity) {
        name.0 = "Physics Cylinder".to_string();
    }

    if let Some(transform) = world.get_local_transform_mut(entity) {
        transform.translation = position;
        transform.scale = nalgebra_glm::vec3(radius * 2.0, half_height * 2.0, radius * 2.0);
    }

    if let Some(mesh) = world.get_render_mesh_mut(entity) {
        mesh.name = "Cylinder".to_string();
    }

    if let Some(mat) = world.get_material_mut(entity) {
        *mat = material;
    }

    if let Some(bv) = world.get_bounding_volume_mut(entity) {
        *bv = crate::ecs::world::components::BoundingVolume::from_mesh_type("Cylinder");
    }

    if let Some(rigid_body) = world.get_rigid_body_mut(entity) {
        *rigid_body = RigidBodyComponent::new_dynamic()
            .with_translation(position.x, position.y, position.z)
            .with_mass(mass);
    }

    if let Some(collider) = world.get_collider_mut(entity) {
        *collider = ColliderComponent::new_cylinder(half_height, radius)
            .with_friction(0.5)
            .with_restitution(0.3);
    }

    entity
}

pub fn spawn_first_person_player(world: &mut World, position: Vec3) -> (Entity, Entity) {
    let player_entity = world.spawn_entities(
        crate::ecs::world::NAME
            | crate::ecs::world::LOCAL_TRANSFORM
            | crate::ecs::world::GLOBAL_TRANSFORM
            | crate::ecs::world::LOCAL_TRANSFORM_DIRTY
            | crate::ecs::world::CHARACTER_CONTROLLER,
        1,
    )[0];

    if let Some(name) = world.get_name_mut(player_entity) {
        name.0 = "Player".to_string();
    }

    if let Some(transform) = world.get_local_transform_mut(player_entity) {
        transform.translation = position;
    }

    if let Some(controller) = world.get_character_controller_mut(player_entity) {
        *controller = CharacterControllerComponent::new_capsule(0.7, 0.3);
        controller.max_speed = 5.0;
        controller.acceleration = 50.0;
        controller.jump_impulse = 5.0;
    }

    let camera_entity = world.spawn_entities(
        crate::ecs::world::NAME
            | crate::ecs::world::CAMERA
            | crate::ecs::world::LOCAL_TRANSFORM
            | crate::ecs::world::GLOBAL_TRANSFORM
            | crate::ecs::world::LOCAL_TRANSFORM_DIRTY
            | crate::ecs::world::PARENT,
        1,
    )[0];

    if let Some(name) = world.get_name_mut(camera_entity) {
        name.0 = "Player Camera".to_string();
    }

    if let Some(transform) = world.get_local_transform_mut(camera_entity) {
        transform.translation = nalgebra_glm::vec3(0.0, 1.3, 0.0);
    }

    if let Some(parent) = world.get_parent_mut(camera_entity) {
        *parent = crate::ecs::transform::components::Parent(Some(player_entity));
    }

    if let Some(camera) = world.get_camera_mut(camera_entity) {
        camera.projection = crate::ecs::camera::components::Projection::Perspective(
            crate::ecs::camera::components::PerspectiveCamera {
                y_fov_rad: 85.0_f32.to_radians(),
                z_near: 0.01,
                z_far: Some(1000.0),
                aspect_ratio: None,
            },
        );
        camera.smoothing = Some(crate::ecs::camera::components::Smoothing {
            mouse_sensitivity: 1.5,
            ..Default::default()
        });
    }

    world.resources.active_camera = Some(camera_entity);

    (player_entity, camera_entity)
}

pub fn look_camera_system(world: &mut World) {
    let Some(camera_entity) = world.resources.active_camera else {
        return;
    };

    let mouse_delta = if world
        .resources
        .input
        .mouse
        .state
        .contains(crate::ecs::input::resources::MouseState::RIGHT_CLICKED)
    {
        let raw_delta = world.resources.input.mouse.raw_mouse_delta;
        let mouse_sensitivity = 1.5;
        let mouse_dpi_scale = 1.0;

        let pixels_to_radians = (std::f32::consts::PI / 1000.0) * mouse_dpi_scale;
        let mut delta = raw_delta * mouse_sensitivity * pixels_to_radians;
        delta.x *= -1.0;
        delta.y *= -1.0;
        Some(delta)
    } else {
        None
    };

    let gamepad_delta =
        if let Some(gamepad) = crate::ecs::input::queries::query_active_gamepad(world) {
            let right_stick_x = gamepad.value(gilrs::Axis::RightStickX);
            let right_stick_y = gamepad.value(gilrs::Axis::RightStickY);

            let deadzone = 0.15;
            let right_stick_magnitude =
                (right_stick_x * right_stick_x + right_stick_y * right_stick_y).sqrt();

            if right_stick_magnitude > deadzone {
                let normalized_magnitude = (right_stick_magnitude - deadzone) / (1.0 - deadzone);
                let gamepad_sensitivity = 2.0;
                let delta_time = world.resources.window.timing.delta_time;
                let delta = nalgebra_glm::vec2(-right_stick_x, right_stick_y)
                    * normalized_magnitude
                    * gamepad_sensitivity
                    * delta_time;
                Some(delta)
            } else {
                None
            }
        } else {
            None
        };

    let final_delta = match (mouse_delta, gamepad_delta) {
        (Some(mouse), Some(gamepad)) => mouse + gamepad,
        (Some(mouse), None) => mouse,
        (None, Some(gamepad)) => gamepad,
        (None, None) => return,
    };

    let Some(local_transform) = world.get_local_transform_mut(camera_entity) else {
        return;
    };

    let yaw = nalgebra_glm::quat_angle_axis(final_delta.x, &nalgebra_glm::vec3(0.0, 1.0, 0.0));
    local_transform.rotation = yaw * local_transform.rotation;

    let forward_vector = local_transform.forward_vector();
    let current_pitch = forward_vector.y.asin();

    let new_pitch = current_pitch + final_delta.y;
    if new_pitch.abs() <= 89_f32.to_radians() {
        let pitch =
            nalgebra_glm::quat_angle_axis(final_delta.y, &nalgebra_glm::vec3(1.0, 0.0, 0.0));
        local_transform.rotation *= pitch;
    }

    crate::ecs::transform::commands::mark_local_transform_dirty(world, camera_entity);
}
