use glam::Vec3;

use crate::components::dialog::{DialogOutcome, DialogOutcomeEvent, ParryButton};
use crate::entity::EntityHandle;

use crate::utility::input::InputState;
use crate::world::World;

const PROJECTILE_SPEED: f32 = 6.0;
const PARRY_WINDOW_RADIUS: f32 = 3.5;
const HIT_RADIUS: f32 = 1.2;

pub fn dialog_projectile_system(world: &mut World, input_state: &InputState)
{
    let player_entity = world.player_tags.all().into_iter().next();
    let Some(player_entity) = player_entity
    else
    {
        return;
    };

    let player_pos = world
        .transforms
        .with(player_entity, |t| t.position)
        .unwrap_or(Vec3::ZERO);

    let player_is_evading = is_player_evading(world, player_entity);

    let projectiles: Vec<EntityHandle> = world.projectile_tags.all();
    let mut outcomes: Vec<DialogOutcomeEvent> = Vec::new();
    let mut to_despawn: Vec<EntityHandle> = Vec::new();

    for proj_entity in projectiles
    {
        let proj_pos = match world.transforms.with(proj_entity, |t| t.position)
        {
            Some(p) => p,
            None => continue,
        };

        let to_player = player_pos - proj_pos;
        let distance = to_player.length();

        let window_open = world
            .dialog_projectiles
            .with(proj_entity, |p| p.parry_window_open)
            .unwrap_or(false);

        if window_open
        {
            if let Some(outcome) = resolve_parry(world, proj_entity, input_state, player_is_evading)
            {
                let bubble_entity = world
                    .dialog_projectiles
                    .with(proj_entity, |p| p.bubble_entity)
                    .unwrap();
                outcomes.push(DialogOutcomeEvent {
                    bubble_entity,
                    outcome,
                });
                to_despawn.push(proj_entity);
                continue;
            }
        }

        if distance < HIT_RADIUS
        {
            let bubble_entity = world
                .dialog_projectiles
                .with(proj_entity, |p| p.bubble_entity)
                .unwrap();
            let outcome = if player_is_evading
            {
                DialogOutcome::Evaded
            }
            else
            {
                DialogOutcome::Hit
            };
            outcomes.push(DialogOutcomeEvent {
                bubble_entity,
                outcome,
            });
            to_despawn.push(proj_entity);
            continue;
        }

        if distance < PARRY_WINDOW_RADIUS
        {
            world.dialog_projectiles.with_mut(proj_entity, |p| {
                p.parry_window_open = true;
            });
        }

        let direction = if distance > 0.001
        {
            to_player / distance
        }
        else
        {
            Vec3::ZERO
        };

        world.transforms.with_mut(proj_entity, |t| {
            t.position += direction * PROJECTILE_SPEED * (1.0 / 60.0);
        });
    }

    for entity in to_despawn
    {
        world.despawn(entity);
    }

    world.dialog_outcomes.extend(outcomes);
}

fn resolve_parry(
    world: &World,
    proj_entity: EntityHandle,
    input_state: &InputState,
    player_is_evading: bool,
) -> Option<DialogOutcome>
{
    if player_is_evading
    {
        return Some(DialogOutcome::Evaded);
    }

    let correct_parry = world
        .dialog_projectiles
        .with(proj_entity, |p| p.correct_parry)?;

    if input_state.i_just_pressed
    {
        return Some(
            if correct_parry == ParryButton::I
            {
                DialogOutcome::ParriedI
            }
            else
            {
                DialogOutcome::WrongParry
            },
        );
    }

    if input_state.j_just_pressed
    {
        return Some(
            if correct_parry == ParryButton::J
            {
                DialogOutcome::ParriedJ
            }
            else
            {
                DialogOutcome::WrongParry
            },
        );
    }

    if input_state.l_just_pressed
    {
        return Some(
            if correct_parry == ParryButton::L
            {
                DialogOutcome::ParriedL
            }
            else
            {
                DialogOutcome::WrongParry
            },
        );
    }

    None
}

fn is_player_evading(world: &World, player_entity: EntityHandle) -> bool
{
    world.leaping_states.get(player_entity).is_some()
        || world.rolling_states.get(player_entity).is_some()
}
