use std::time::{Duration, Instant};

use bevy::prelude::*;
use bevy_xpbd_2d::components::{AngularVelocity, LinearVelocity};

pub struct UndertakerPlugin;

impl Plugin for UndertakerPlugin {
    fn build(&self, app: &mut App) {
        app.init_resource::<UndertakerConfig>()
            .add_event::<Undertaken>()
            .add_systems(Update, (undertaker_debug_line, update_mortals));
    }
}

/// Event that occurs when something is undertaken
#[derive(Event)]
pub struct Undertaken {
    position: Vec3,
}

#[derive(Resource)]
pub struct UndertakerConfig {
    pub under_y: f32,
    pub max_time_under: Duration,
}

impl Default for UndertakerConfig {
    fn default() -> Self {
        Self {
            under_y: 0.0,
            max_time_under: Duration::from_secs(1),
        }
    }
}

#[derive(Component, Default)]
pub struct Mortal {
    // Z might have info, so allow it to be controlled
    /// If [`None`], entity is destroyed. Otherwise
    /// move the entity to a valid location close to the
    /// given point, and zero any velocity.
    pub respawn: Option<Vec3>,
    instant_under: Option<Instant>,
}

impl Mortal {
    pub fn respawnable(respawn: Vec3) -> Self {
        Self {
            respawn: Some(respawn),
            ..default()
        }
    }
}

#[allow(clippy::type_complexity)]
fn update_mortals(
    mut query: Query<(
        Entity,
        &mut Mortal,
        &mut Transform,
        &GlobalTransform,
        Option<&mut LinearVelocity>,
        Option<&mut AngularVelocity>,
    )>,
    config: Res<UndertakerConfig>,
    mut commands: Commands,
    mut writer: EventWriter<Undertaken>,
) {
    for (entity, mut mortality, mut transform, global_transform, lin_vel, ang_vel) in
        query.iter_mut()
    {
        if global_transform.translation().y >= config.under_y {
            mortality.instant_under = None;
        } else {
            let mortal_instant = mortality.instant_under.unwrap_or(Instant::now());
            if mortal_instant.elapsed() > config.max_time_under {
                if let Some(declared_respawn) = mortality.respawn {
                    let respawn_point = Vec3::new(
                        declared_respawn.x,
                        declared_respawn.y.max(config.under_y),
                        declared_respawn.z,
                    );
                    transform.translation = respawn_point;
                    if let Some(mut linear) = lin_vel {
                        linear.0 = Vec2::ZERO;
                    }
                    if let Some(mut angular) = ang_vel {
                        angular.0 = 0.;
                    }
                    writer.send(Undertaken {
                        position: global_transform.translation(),
                    });
                    mortality.instant_under = None;
                } else {
                    commands.entity(entity).despawn_recursive();
                }
            } else {
                mortality.instant_under = Some(mortal_instant);
            }
        }
    }
}

fn undertaker_debug_line(mut gizmos: Gizmos, config: Res<UndertakerConfig>) {
    gizmos.primitive_2d(
        Plane2d::new(Vec2::Y),
        Vec2::Y * config.under_y,
        0.,
        Color::ORANGE_RED,
    );
}