use std::rc::Rc;

use glam::Vec3;
use rapier3d::control::{CharacterAutostep, KinematicCharacterController};
use rapier3d::prelude::{ColliderBuilder, RigidBodyBuilder};

use crate::bundles::Bundle;
use crate::components::dialog::DialogSourceComponent;
use crate::components::player_states::{FallingState, IdleState, WalkingState};
use crate::components::trigger::{TriggerComponent, TriggerFilter, TriggerShape, TriggerState};
use crate::components::{MeshComponent, MovementComponent, PhysicsComponent};
use crate::entity::EntityHandle;
use crate::loaders::mesh::Mesh;
use crate::paths;
use crate::physics::PhysicsManager;
use crate::render::Pipeline;
use crate::state::StateMachine;
use crate::world::{Transform, World};

pub struct TestCharBundle
{
    pub position: Vec3,
}

impl Bundle for TestCharBundle
{
    fn spawn(self, world: &mut World) -> Result<EntityHandle, String>
    {
        let entity = world.spawn();

        let spawn_transform = Transform::from_position(self.position);

        let rigidbody = RigidBodyBuilder::kinematic_position_based()
            .translation(spawn_transform.position.into())
            .build();
        let collider = ColliderBuilder::capsule_y(0.5, 0.5).build();
        let _controller = KinematicCharacterController {
            slide: true,
            autostep: Some(CharacterAutostep::default()),
            max_slope_climb_angle: 45.0,
            ..Default::default()
        };

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

        let mesh = Mesh::load_mesh(&paths::meshes::test_char())
            .map_err(|e| format!("missing test_char mesh: {}", e))?;

        let mut state_machine = StateMachine::new::<FallingState>();
        state_machine.register_state(|w: &mut World| &mut w.falling_states);
        state_machine.register_state(|w: &mut World| &mut w.idle_states);
        state_machine.register_state(|w: &mut World| &mut w.walking_states);

        let entity_id = entity;

        state_machine.add_transition::<FallingState, IdleState>(move |world| {
            world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false)
        });

        state_machine.add_transition::<IdleState, FallingState>(move |world| {
            !world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false)
        });

        state_machine.add_transition::<WalkingState, FallingState>(move |world| {
            !world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false)
        });

        world.falling_states.insert(entity, FallingState::default());

        world.transforms.insert(entity, spawn_transform);
        world.movements.insert(entity, MovementComponent::new());
        world.physics.insert(
            entity,
            PhysicsComponent {
                rigidbody: rigidbody_handle,
                collider: Some(collider_handle),
            },
        );
        world.meshes.insert(
            entity,
            MeshComponent {
                mesh: Rc::new(mesh),
                pipeline: Pipeline::Standard,
                instance_buffer: None,
                num_instances: 1,
                tile_scale: 4.0,
                enable_dissolve: false,
                enable_snow_light: false,
            },
        );
        world.state_machines.insert(entity, state_machine);
        world.names.insert(entity, "TestChar".to_string());
        world.triggers.insert(
            entity,
            TriggerComponent {
                shape: TriggerShape::Sphere { radius: 20.0 },
                filter: TriggerFilter::Player,
                state: TriggerState::Idle,
            },
        );

        let ink_json = std::fs::read_to_string(&paths::dialogs::test_char()).unwrap_or_default();
        world
            .dialog_sources
            .insert(entity, DialogSourceComponent { ink_json });

        Ok(entity)
    }
}
