#![feature(iter_intersperse)]

//! This example illustrates scrolling in Bevy UI.

use bevy::picking::hover::HoverMap;
use games::*;
use lipsum::*;
use rand::*;

fn main() {
    let mut app = App::new();
    app.add_plugins(BaseGamePlugin::default())
        .add_systems(Startup, (setup_list, setup_nav_tree, setup_button))
        .add_systems(
            Update,
            (
                hide_menu,
                control_menu.run_if(on_message::<Pointer<Over>>.or(on_message::<Pointer<Out>>)),
            ),
        );
    app.run();
}

fn setup_list(mut commands: Commands) {
    // Scrolling list
    commands
        .spawn((Node {
            flex_direction: FlexDirection::Column,
            // If height is not set, we need both align_self: Stetch and overflow: scroll()
            height: Val::Percent(50.0),
            // align_self: AlignSelf::Stretch,
            overflow: Overflow::scroll(),
            ..default()
        },))
        .with_children(|parent| {
            parent.spawn(Text("This scrolls...".into()));
            // List items
            (0..250).for_each(|i| {
                parent.spawn(Text(format!("Item {i}")));
            });
        })
        .observe(scroll);
}

fn setup_button(mut commands: Commands) {
    // Spawn a random button at the center of the screen for testing
    commands.spawn((
        Node {
            align_self: AlignSelf::Center,
            justify_self: JustifySelf::Center,
            ..default()
        },
        Button,
        children![Text("Hello World".into()),],
    ));
}

fn setup_nav_tree(mut commands: Commands) {
    // Nav Tree
    commands
        .spawn((
            Node {
                top: Val::Percent(25.0),
                justify_self: JustifySelf::End,
                min_width: Val::Px(25.0),
                min_height: Val::Px(25.0),
                align_content: AlignContent::Center,
                justify_content: JustifyContent::Center,
                ..default()
            },
            Button,
            children![Text::new("+Menu"),],
        ))
        .with_children(|parent| {
            parent
                .spawn((
                    Name::new("Buttons"),
                    NavState::default(),
                    Node {
                        position_type: PositionType::Absolute,
                        flex_direction: FlexDirection::Column,
                        right: Val::Percent(100.0),
                        ..default()
                    },
                ))
                .with_children(|parent| {
                    (0..5).for_each(|_| {
                        let title: String = Iterator::intersperse(
                            lipsum_title_with_rng(thread_rng())
                                .split_whitespace()
                                .take(2),
                            " ",
                        )
                        .collect();
                        parent.spawn((
                            children![Text::new(title),],
                            Node {
                                width: Val::Auto,
                                ..default()
                            },
                            Button,
                        ));
                    });
                })
                .with_children(|parent| {
                    parent.spawn((
                        Name::new("Preview"),
                        NavState::default(),
                        children![Text::new(lipsum_with_rng(thread_rng(), 50)),],
                        Node {
                            position_type: PositionType::Absolute,
                            right: Val::Percent(100.0),
                            width: Val::Percent(100.0),
                            max_height: Val::Percent(100.0),
                            ..default()
                        },
                    ));
                });
        });
}

// When you pointer over the '+' make the entire menu visible
fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed<NavState>>) {
    nodes.iter_mut().for_each(|(mut v, n)| {
        *v = match n {
            NavState::Open => Visibility::Inherited,
            NavState::Closed => Visibility::Hidden,
        };
    });
}

// When you pointer goes off of the '+' or any of it's children make the entire menu invisible
fn control_menu(
    mut over_events: MessageReader<Pointer<Over>>,
    mut out_events: MessageReader<Pointer<Out>>,
    children: Query<&ChildOf>,
    parents: Query<&Children>,
    mut nav: Query<&mut NavState>,
    hover_map: Res<HoverMap>,
) {
    over_events.read().for_each(|over| {
        let root = children.root_ancestor(over.entity);

        parents.iter_descendants(root).for_each(|child| {
            if let Ok(mut n) = nav.get_mut(child) {
                *n = NavState::Open;
            }
        });
    });

    // Gives us the entiies covered by the HoverMap
    let is_hovered: Vec<&Entity> = hover_map
        .iter()
        .flat_map(|(_, submap)| {
            let x: Vec<&Entity> = submap.iter().map(|(node, _)| node).collect();
            x
        })
        .collect();

    // For all pointer out events
    out_events.read().for_each(|out| {
        // If a relative of out.entity is hovered, do nothing
        // Otherwise set to closed
        let root = children.root_ancestor(out.entity);
        let tree_still_hovered = parents
            .iter_descendants(root)
            .any(|child| is_hovered.contains(&&child));
        if !tree_still_hovered {
            parents.iter_descendants(root).for_each(|child| {
                if let Ok(mut n) = nav.get_mut(child) {
                    *n = NavState::Closed;
                }
            })
        }
    })
}
