use super::{ControlState, ControllerSet};
use crate::{
    GameState,
    animation::AnimationFlags,
    control::{controller_common::MovementSpeedFactor, controls::ControllerSettings},
    player::PlayerBodyMesh,
};
use bevy::prelude::*;
use happy_feet::prelude::{Grounding, KinematicVelocity, MoveInput};
use lightyear::prelude::input::native::ActionState;

pub struct CharacterControllerPlugin;

impl Plugin for CharacterControllerPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(
            FixedUpdate,
            (rotate_view, apply_controls)
                .chain()
                .in_set(ControllerSet::ApplyControlsRun)
                .run_if(in_state(GameState::Playing)),
        );
    }
}

fn rotate_view(
    actions: Query<&ActionState<ControlState>>,
    mut player: Query<(&mut Transform, &ChildOf), With<PlayerBodyMesh>>,
) {
    for (mut tr, child_of) in player.iter_mut() {
        let controls = actions.get(child_of.parent()).unwrap();

        if controls.view_mode {
            continue;
        }

        tr.rotate_y(controls.look_dir.x * -0.001);
    }
}

fn apply_controls(
    mut character: Query<(
        &mut MoveInput,
        &mut Grounding,
        &mut KinematicVelocity,
        &mut AnimationFlags,
        &ControllerSettings,
        &MovementSpeedFactor,
        &ActionState<ControlState>,
    )>,
    rig_transform_q: Option<Single<&GlobalTransform, With<PlayerBodyMesh>>>,
) {
    let Ok((
        mut move_input,
        mut grounding,
        mut velocity,
        mut flags,
        settings,
        move_factor,
        controls,
    )) = character.single_mut()
    else {
        return;
    };

    let mut direction = -controls.move_dir.extend(0.0).xzy();

    if let Some(ref rig_transform) = rig_transform_q {
        direction = (rig_transform.forward() * direction.z) + (rig_transform.right() * direction.x);
    }

    let ground_normal = *grounding.normal().unwrap_or(Dir3::Y);

    let y_projection = direction.project_onto(ground_normal);
    direction -= y_projection;
    direction = direction.normalize_or_zero();

    move_input.set(direction * move_factor.0);

    if controls.jump && grounding.is_grounded() {
        if cfg!(feature = "server") {
            flags.jumping = true;
            flags.jump_count += 1;
        }
        happy_feet::movement::jump(settings.jump_force, &mut velocity, &mut grounding, Dir3::Y)
    }
}
