use std::f32::consts::PI;

use glam::Vec3;

use crate::components::dialog::{
    DialogOutcome, DialogOutcomeEvent, DialogProjectileComponent, ParryButton,
};
use crate::components::particle::{ParticleEmitterConfig, SpawnParticleIntent};
use crate::components::player_states::{LeapingState, RollingState};
use crate::entity::EntityHandle;
use crate::utility::input::InputState;
use crate::utility::transform::Transform;
use crate::world::Storage;

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

pub fn dialog_projectile_system(
    player_tags: &Storage<()>,
    transforms: &mut Storage<Transform>,
    projectile_tags: &mut Storage<()>,
    dialog_projectiles: &mut Storage<DialogProjectileComponent>,
    spawn_particle_intents: &mut Vec<SpawnParticleIntent>,
    dialog_outcomes: &mut Vec<DialogOutcomeEvent>,
    leaping_states: &Storage<LeapingState>,
    rolling_states: &Storage<RollingState>,
    input_state: &InputState,
)
{
    let player_entity = player_tags.all().into_iter().next();
    let Some(player_entity) = player_entity
    else
    {
        return;
    };

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

    let player_is_evading =
        leaping_states.get(player_entity).is_some() || rolling_states.get(player_entity).is_some();

    let projectiles: Vec<EntityHandle> = 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 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 = dialog_projectiles
            .with(proj_entity, |p| p.parry_window_open)
            .unwrap_or(false);

        if window_open
        {
            if let Some(outcome) = resolve_parry(
                dialog_projectiles,
                proj_entity,
                input_state,
                player_is_evading,
            )
            {
                let bubble_entity = 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 = 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
        {
            dialog_projectiles.with_mut(proj_entity, |p| {
                p.parry_window_open = true;
            });
        }

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

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

        let proj_pos = transforms.with(proj_entity, |t| t.position);
        if let Some(pos) = proj_pos
        {
            spawn_particle_intents.push(SpawnParticleIntent {
                origin: pos,
                config: projectile_swarm_config(),
            });
        }
    }

    for entity in to_despawn
    {
        transforms.remove(entity);
        projectile_tags.remove(entity);
        dialog_projectiles.remove(entity);
    }

    dialog_outcomes.extend(outcomes);
}

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

    let correct_parry = 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 projectile_swarm_config() -> ParticleEmitterConfig
{
    ParticleEmitterConfig {
        burst_count: 3,
        lifetime: 0.3..0.6,
        speed: 0.5..2.0,
        direction: None,
        direction_spread: PI,
        gravity: 0.0,
        size: 0.05..0.15,
        color_start: [1.0, 0.3, 0.1, 1.0],
        color_end: [0.2, 0.05, 0.0, 0.0],
    }
}
