use super::{BackbackSwapEvent, Backpack, UiHeadState};
use crate::{
    GameState, global_observer, heads::HeadsImages, loading_assets::UIAssets, sounds::PlaySound,
};
use bevy::{ecs::spawn::SpawnIter, prelude::*};

static HEAD_SLOTS: usize = 5;

#[derive(Event, Clone, Copy, Reflect, PartialEq)]
pub enum BackpackAction {
    Left,
    Right,
    Swap,
    OpenClose,
}

#[derive(Component, Default)]
struct BackpackMarker;

#[derive(Component, Default)]
struct BackpackCountText;

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

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

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

#[derive(Resource, Default, Debug)]
struct BackpackUiState {
    heads: [Option<UiHeadState>; 5],
    scroll: usize,
    count: usize,
    current_slot: usize,
    open: bool,
}

impl BackpackUiState {
    fn relative_current_slot(&self) -> usize {
        self.current_slot.saturating_sub(self.scroll)
    }
}

pub fn plugin(app: &mut App) {
    app.init_resource::<BackpackUiState>();
    app.add_systems(OnEnter(GameState::Playing), setup);
    app.add_systems(
        Update,
        (update, sync_on_change, update_visibility, update_count)
            .run_if(in_state(GameState::Playing)),
    );

    global_observer!(app, swap_head_inputs);
}

fn setup(mut commands: Commands, assets: Res<UIAssets>) {
    commands.spawn((
        Name::new("backpack-ui"),
        BackpackMarker,
        Visibility::Hidden,
        Node {
            position_type: PositionType::Absolute,
            top: 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.spawn((
        Name::new("backpack-head-count-ui"),
        Text::new("0"),
        TextShadow::default(),
        BackpackCountText,
        TextFont {
            font: assets.font.clone(),
            font_size: 34.0,
            ..default()
        },
        TextColor(Color::Srgba(Srgba::rgb(0., 1., 0.))),
        TextLayout::new_with_justify(JustifyText::Center),
        Node {
            position_type: PositionType::Absolute,
            top: Val::Px(20.0),
            right: Val::Px(20.0),
            ..default()
        },
    ));
}

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![
            (
                Name::new("selector"),
                Node {
                    position_type: PositionType::Absolute,
                    bottom: Val::Px(-30.0),
                    ..default()
                },
                Visibility::Hidden,
                ImageNode::new(selector).with_flip_y(),
                HeadSelector(head_slot),
            ),
            (
                Name::new("bg"),
                Node {
                    position_type: PositionType::Absolute,
                    ..default()
                },
                ImageNode::new(bg),
            ),
            (
                Name::new("head"),
                Node {
                    position_type: PositionType::Absolute,
                    ..default()
                },
                ImageNode::default(),
                Visibility::Hidden,
                HeadImage(head_slot),
            ),
            (
                Name::new("rings"),
                Node {
                    position_type: PositionType::Absolute,
                    ..default()
                },
                ImageNode::new(regular),
            ),
            (
                Name::new("health"),
                Node {
                    height: Val::Px(DAMAGE_SIZE),
                    width: Val::Px(DAMAGE_SIZE),
                    ..default()
                },
                children![(
                    Name::new("damage_ring"),
                    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(0.),
                        ..default()
                    },
                    children![ImageNode::new(damage)]
                )]
            )
        ],
    )
}

fn update_visibility(
    state: Res<BackpackUiState>,
    mut backpack: Query<&mut Visibility, (With<BackpackMarker>, Without<BackpackCountText>)>,
    mut count: Query<&mut Visibility, (Without<BackpackMarker>, With<BackpackCountText>)>,
) {
    if state.is_changed() {
        for mut vis in backpack.iter_mut() {
            *vis = if state.open {
                Visibility::Visible
            } else {
                Visibility::Hidden
            };
        }

        for mut vis in count.iter_mut() {
            *vis = if !state.open {
                Visibility::Visible
            } else {
                Visibility::Hidden
            };
        }
    }
}

fn update_count(
    state: Res<BackpackUiState>,
    text: Query<Entity, With<BackpackCountText>>,
    mut writer: TextUiWriter,
) {
    if state.is_changed() {
        let Some(text) = text.iter().next() else {
            return;
        };

        *writer.text(text, 0) = state.count.to_string();
    }
}

fn update(
    state: Res<BackpackUiState>,
    heads_images: Res<HeadsImages>,
    mut head_image: Query<(&HeadImage, &mut Visibility, &mut ImageNode), Without<HeadSelector>>,
    mut head_damage: Query<(&HeadDamage, &mut Node), Without<HeadSelector>>,
    mut head_selector: Query<(&HeadSelector, &mut Visibility), Without<HeadImage>>,
) {
    if state.is_changed() {
        for (HeadImage(head), mut vis, mut image) in head_image.iter_mut() {
            if let Some(head) = &state.heads[*head] {
                *vis = Visibility::Inherited;
                image.image = heads_images.heads[head.head].clone();
            } else {
                *vis = Visibility::Hidden;
            }
        }
        for (HeadDamage(head), mut node) in head_damage.iter_mut() {
            if let Some(head) = &state.heads[*head] {
                node.height = Val::Percent(head.damage() * 100.0);
            }
        }

        for (HeadSelector(head), mut vis) in head_selector.iter_mut() {
            *vis = if *head == state.relative_current_slot() {
                Visibility::Inherited
            } else {
                Visibility::Hidden
            };
        }
    }
}

fn swap_head_inputs(
    trigger: Trigger<BackpackAction>,
    backpack: Res<Backpack>,
    mut commands: Commands,
    mut state: ResMut<BackpackUiState>,
    time: Res<Time>,
) {
    if state.count == 0 {
        return;
    }

    let action = *trigger.event();
    if action == BackpackAction::OpenClose {
        state.open = !state.open;
        commands.trigger(PlaySound::Backpack { open: state.open });
    }

    if !state.open {
        return;
    }

    let mut changed = false;
    if action == BackpackAction::Left && state.current_slot > 0 {
        state.current_slot -= 1;
        changed = true;
    }
    if action == BackpackAction::Right && state.current_slot < state.count.saturating_sub(1) {
        state.current_slot += 1;
        changed = true;
    }
    if action == BackpackAction::Swap {
        commands.trigger(BackbackSwapEvent(state.current_slot));
    }

    if changed {
        commands.trigger(PlaySound::Selection);
        sync(&backpack, &mut state, time.elapsed_secs());
    }
}

fn sync_on_change(backpack: Res<Backpack>, mut state: ResMut<BackpackUiState>, time: Res<Time>) {
    if backpack.is_changed() || backpack.reloading() {
        sync(&backpack, &mut state, time.elapsed_secs());
    }
}

fn sync(backpack: &Res<Backpack>, state: &mut ResMut<BackpackUiState>, time: f32) {
    state.count = backpack.heads.len();

    state.scroll = state.scroll.min(state.count.saturating_sub(HEAD_SLOTS));

    if state.current_slot >= state.scroll + HEAD_SLOTS {
        state.scroll = state.current_slot.saturating_sub(HEAD_SLOTS - 1);
    }
    if state.current_slot < state.scroll {
        state.scroll = state.current_slot;
    }

    for i in 0..HEAD_SLOTS {
        if let Some(head) = backpack.heads.get(i + state.scroll) {
            state.heads[i] = Some(UiHeadState::new(*head, time));
        } else {
            state.heads[i] = None;
        }
    }
}
