use crate::{GameState, backpack::UiHeadState, loading_assets::UIAssets, player::Player};
use bevy::{ecs::spawn::SpawnIter, prelude::*};
use bevy_ui_gradients::{AngularColorStop, BackgroundGradient, ConicGradient, Gradient, Position};
use std::f32::consts::PI;

use super::{ActiveHeads, HEAD_SLOTS, HeadsImages};

#[derive(Component, Reflect, Default)]
#[reflect(Component)]
struct HeadSelector(pub usize);

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

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

#[derive(Resource, Default, Reflect)]
#[reflect(Resource)]
struct UiActiveHeads {
    heads: [Option<UiHeadState>; 5],
    current_slot: usize,
}

pub fn plugin(app: &mut App) {
    app.register_type::<HeadDamage>();
    app.register_type::<UiActiveHeads>();

    app.add_systems(OnEnter(GameState::Playing), setup);
    app.add_systems(
        Update,
        (sync, update, update_ammo, update_health).run_if(in_state(GameState::Playing)),
    );
}

fn setup(mut commands: Commands, assets: Res<UIAssets>) {
    commands.spawn((
        Name::new("heads-ui"),
        Node {
            position_type: PositionType::Absolute,
            bottom: Val::Px(20.0),
            right: Val::Px(20.0),
            height: Val::Px(74.0),
            ..default()
        },
        Children::spawn(SpawnIter((0..HEAD_SLOTS).map({
            let bg = assets.head_bg.clone();
            let regular = assets.head_regular.clone();
            let selector = assets.head_selector.clone();
            let damage = assets.head_damage.clone();

            move |i| {
                spawn_head_ui(
                    bg.clone(),
                    regular.clone(),
                    selector.clone(),
                    damage.clone(),
                    i,
                )
            }
        }))),
    ));

    commands.init_resource::<UiActiveHeads>();
}

fn spawn_head_ui(
    bg: Handle<Image>,
    regular: Handle<Image>,
    selector: Handle<Image>,
    damage: Handle<Image>,
    head_slot: usize,
) -> 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,
                    top: Val::Px(-30.0),
                    ..default()
                },
                Visibility::Hidden,
                ImageNode::new(selector),
                HeadSelector(head_slot),
            ),
            (
                Node {
                    position_type: PositionType::Absolute,
                    ..default()
                },
                ImageNode::new(bg),
            ),
            (
                Name::new("head-icon"),
                Node {
                    position_type: PositionType::Absolute,
                    ..default()
                },
                BorderRadius::all(Val::Px(9999.)),
                BackgroundGradient::from(ConicGradient {
                    start: 0.,
                    stops: vec![
                        AngularColorStop::new(Color::linear_rgba(0., 0., 0., 0.9), 0.),
                        AngularColorStop::new(Color::linear_rgba(0., 0., 0., 0.9), PI * 1.5),
                        AngularColorStop::new(Color::linear_rgba(0., 0., 0., 0.0), PI * 1.5),
                    ],
                    position: Position::CENTER,
                }),
                ImageNode::default(),
                Visibility::Hidden,
                HeadImage(head_slot),
            ),
            (
                Node {
                    position_type: PositionType::Absolute,
                    ..default()
                },
                ImageNode::new(regular),
            ),
            (
                Node {
                    height: Val::Px(DAMAGE_SIZE),
                    width: Val::Px(DAMAGE_SIZE),
                    ..default()
                },
                children![(
                    HeadDamage(head_slot),
                    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(
    res: Res<UiActiveHeads>,
    heads_images: Res<HeadsImages>,
    mut head_image: Query<(&HeadImage, &mut Visibility, &mut ImageNode), Without<HeadSelector>>,
    mut head_selector: Query<(&HeadSelector, &mut Visibility), Without<HeadImage>>,
) {
    if res.is_changed() {
        for (HeadImage(head), mut vis, mut image) in head_image.iter_mut() {
            if let Some(head) = res.heads[*head] {
                *vis = Visibility::Visible;
                image.image = heads_images.heads[head.head].clone();
            } else {
                *vis = Visibility::Hidden;
            }
        }
        for (HeadSelector(head), mut vis) in head_selector.iter_mut() {
            *vis = if *head == res.current_slot {
                Visibility::Visible
            } else {
                Visibility::Hidden
            };
        }
    }
}

fn update_ammo(
    res: Res<UiActiveHeads>,
    mut gradients: Query<(&mut BackgroundGradient, &HeadImage)>,
) {
    if res.is_changed() {
        for (mut gradient, HeadImage(head)) in gradients.iter_mut() {
            if let Some(head) = res.heads[*head] {
                let Gradient::Conic(gradient) = &mut gradient.0[0] else {
                    continue;
                };

                let progress = if let Some(reloading) = head.reloading() {
                    1. - reloading
                } else {
                    head.ammo_used()
                };

                let angle = progress * PI * 2.;

                gradient.stops[1].angle = Some(angle);
                gradient.stops[2].angle = Some(angle);
            }
        }
    }
}

fn update_health(res: Res<UiActiveHeads>, mut query: Query<(&mut Node, &HeadDamage)>) {
    if res.is_changed() {
        for (mut node, HeadDamage(head)) in query.iter_mut() {
            node.height =
                Val::Percent(res.heads[*head].map(|head| head.damage()).unwrap_or(0.) * 100.);
        }
    }
}

fn sync(
    active_heads: Query<Ref<ActiveHeads>, With<Player>>,
    mut state: ResMut<UiActiveHeads>,
    time: Res<Time>,
) {
    let Ok(active_heads) = active_heads.single() else {
        return;
    };

    if active_heads.is_changed() || active_heads.reloading() {
        state.current_slot = active_heads.slot();
        for i in 0..HEAD_SLOTS {
            state.heads[i] = active_heads
                .head(i)
                .map(|state| UiHeadState::new(state, time.elapsed_secs()));
        }
    }
}
