use super::AimTarget;
use crate::{
    GameState,
    backpack::UiHeadState,
    heads::{ActiveHeads, HeadsImages},
    hitpoints::Hitpoints,
    loading_assets::UIAssets,
    npc::Npc,
    player::Player,
};
use bevy::prelude::*;

#[derive(Component, Reflect, Default)]
#[reflect(Component)]
struct HeadImage;

#[derive(Component, Reflect, Default)]
#[reflect(Component)]
struct HeadDamage;

#[derive(Resource, Default, PartialEq)]
struct TargetUi {
    head: Option<UiHeadState>,
}

pub fn plugin(app: &mut App) {
    app.add_systems(OnEnter(GameState::Playing), setup);
    app.add_systems(Update, (sync, update).run_if(in_state(GameState::Playing)));
}

fn setup(mut commands: Commands, assets: Res<UIAssets>) {
    commands.spawn((
        Name::new("target-ui"),
        Node {
            position_type: PositionType::Absolute,
            top: Val::Px(150.0),
            left: Val::Px(20.0),
            height: Val::Px(74.0),
            ..default()
        },
        children![spawn_head_ui(
            assets.head_bg.clone(),
            assets.head_regular.clone(),
            assets.head_damage.clone(),
        )],
    ));

    commands.insert_resource(TargetUi::default());
}

fn spawn_head_ui(bg: Handle<Image>, regular: Handle<Image>, damage: Handle<Image>) -> impl Bundle {
    const SIZE: f32 = 90.0;
    const DAMAGE_SIZE: f32 = 74.0;

    (
        Node {
            position_type: PositionType::Relative,
            justify_content: JustifyContent::Center,
            align_items: AlignItems::Center,
            width: Val::Px(SIZE),
            ..default()
        },
        children![
            (
                Node {
                    position_type: PositionType::Absolute,
                    ..default()
                },
                ImageNode::new(bg),
            ),
            (
                Node {
                    position_type: PositionType::Absolute,
                    ..default()
                },
                ImageNode::default(),
                Visibility::Hidden,
                HeadImage,
            ),
            (
                Node {
                    position_type: PositionType::Absolute,
                    ..default()
                },
                ImageNode::new(regular),
            ),
            (
                Node {
                    height: Val::Px(DAMAGE_SIZE),
                    width: Val::Px(DAMAGE_SIZE),
                    ..default()
                },
                children![(
                    HeadDamage,
                    Node {
                        position_type: PositionType::Absolute,
                        display: Display::Block,
                        overflow: Overflow::clip(),
                        top: Val::Px(0.),
                        left: Val::Px(0.),
                        right: Val::Px(0.),
                        height: Val::Percent(25.),
                        ..default()
                    },
                    children![ImageNode::new(damage)]
                )]
            )
        ],
    )
}

fn update(
    target: Res<TargetUi>,
    heads_images: Res<HeadsImages>,
    mut head_image: Query<
        (&mut Visibility, &mut ImageNode),
        (Without<HeadDamage>, With<HeadImage>),
    >,
    mut head_damage: Query<&mut Node, (With<HeadDamage>, Without<HeadImage>)>,
) {
    if target.is_changed() {
        if let Ok((mut vis, mut image)) = head_image.single_mut() {
            if let Some(head) = target.head {
                *vis = Visibility::Visible;
                image.image = heads_images.heads[head.head].clone();
            } else {
                *vis = Visibility::Hidden;
            }
        }

        if let Ok(mut node) = head_damage.single_mut() {
            node.height = Val::Percent(target.head.map(|head| head.damage()).unwrap_or(0.) * 100.);
        }
    }
}

fn sync(
    mut target: ResMut<TargetUi>,
    player_target: Query<&AimTarget, With<Player>>,
    target_data: Query<(&Hitpoints, &ActiveHeads), With<Npc>>,
) {
    let mut new_state = None;
    if let Some(e) = player_target.iter().next().and_then(|target| target.0) {
        if let Ok((hp, heads)) = target_data.get(e) {
            let head = heads.current().expect("target must have a head on");
            new_state = Some(UiHeadState {
                head: head.head,
                health: hp.health(),
                ammo: 1.,
                reloading: None,
            });
        }
    }

    if new_state != target.head {
        target.head = new_state;
    }
}
