use std::f32::consts::PI;
use std::rc::Rc;

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

use crate::bundles::Bundle;
use crate::components::lights::spot::SpotlightComponent;
use crate::components::player_states::{
    FallingState, IdleState, JumpingState, LeapingState, RollingState, WalkingState,
};
use crate::components::{
    InputComponent, JumpComponent, 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::systems::player_states::{LEAP_DURATION, ROLL_DURATION};
use crate::world::{Transform, World};

pub struct PlayerBundle
{
    pub position: Vec3,
}

impl Bundle for PlayerBundle
{
    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::player())
            .map_err(|e| format!("missing player 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);
        state_machine.register_state(|w: &mut World| &mut w.jumping_states);
        state_machine.register_state(|w: &mut World| &mut w.leaping_states);
        state_machine.register_state(|w: &mut World| &mut w.rolling_states);

        let entity_id = entity;

        state_machine.add_transition::<FallingState, IdleState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            let has_input = world
                .inputs
                .with(entity_id, |i| i.move_direction.length() > 0.01)
                .unwrap_or(false);
            is_grounded && !has_input
        });

        state_machine.add_transition::<FallingState, WalkingState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            let has_input = world
                .inputs
                .with(entity_id, |i| i.move_direction.length() > 0.01)
                .unwrap_or(false);
            is_grounded && has_input
        });

        state_machine.add_transition::<IdleState, WalkingState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            let has_input = world
                .inputs
                .with(entity_id, |i| i.move_direction.length() > 0.01)
                .unwrap_or(false);
            is_grounded && has_input
        });

        state_machine.add_transition::<WalkingState, IdleState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            let has_input = world
                .inputs
                .with(entity_id, |i| i.move_direction.length() > 0.01)
                .unwrap_or(false);
            is_grounded && !has_input
        });

        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)
        });

        state_machine.add_transition::<IdleState, JumpingState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            let jump_pressed = world
                .inputs
                .with(entity_id, |i| i.jump_just_pressed)
                .unwrap_or(false);
            is_grounded && jump_pressed
        });

        state_machine.add_transition::<WalkingState, JumpingState>(move |world| {
            let is_grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            let jump_pressed = world
                .inputs
                .with(entity_id, |i| i.jump_just_pressed)
                .unwrap_or(false);
            is_grounded && jump_pressed
        });

        state_machine.add_transition::<JumpingState, FallingState>(move |world| {
            let time = world
                .jumping_states
                .with(entity_id, |s| s.time_in_state)
                .unwrap_or(0.0);
            world
                .jumps
                .with(entity_id, |j| time >= j.jump_duration)
                .unwrap_or(true)
        });

        state_machine.add_transition::<IdleState, LeapingState>(move |world| {
            world
                .inputs
                .with(entity_id, |i| i.roll_just_pressed)
                .unwrap_or(false)
        });

        state_machine.add_transition::<WalkingState, LeapingState>(move |world| {
            world
                .inputs
                .with(entity_id, |i| i.roll_just_pressed)
                .unwrap_or(false)
        });

        state_machine.add_transition::<LeapingState, RollingState>(move |world| {
            world
                .leaping_states
                .with(entity_id, |s| s.time_in_state >= LEAP_DURATION)
                .unwrap_or(false)
        });

        state_machine.add_transition::<LeapingState, FallingState>(move |world| {
            let leap_done = world
                .leaping_states
                .with(entity_id, |s| s.time_in_state >= LEAP_DURATION)
                .unwrap_or(false);
            let not_grounded = !world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(true);
            leap_done && not_grounded
        });

        state_machine.add_transition::<RollingState, IdleState>(move |world| {
            let done = world
                .rolling_states
                .with(entity_id, |s| s.time_in_state >= ROLL_DURATION)
                .unwrap_or(false);
            let grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            done && grounded
        });

        state_machine.add_transition::<RollingState, FallingState>(move |world| {
            let done = world
                .rolling_states
                .with(entity_id, |s| s.time_in_state >= ROLL_DURATION)
                .unwrap_or(false);
            let grounded = world
                .movements
                .with(entity_id, |m| m.movement_context.is_floored)
                .unwrap_or(false);
            done && !grounded
        });

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

        world.transforms.insert(entity, spawn_transform);
        world.movements.insert(entity, MovementComponent::new());
        world.jumps.insert(entity, JumpComponent::default());
        world.inputs.insert(entity, InputComponent::default());
        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.player_tags.insert(entity, ());
        world.state_machines.insert(entity, state_machine);
        world.names.insert(entity, "Player".to_string());

        let outer_angle = PI / 2.0 * 0.9;
        world.spotlights.insert(
            entity,
            SpotlightComponent::new(
                Vec3::new(1.0, 2.5, 1.0),
                Vec3::new(0.0, -1.0, 0.0),
                100.0,
                outer_angle * 0.5,
                outer_angle,
            ),
        );

        Ok(entity)
    }
}
