/// TODO:
/// * Text box w/ "reset" button
/// * Generic Minimize/Close Components
/// * Title bar w/ min/close controls
/// * Notice/Warning/Error Popups
/// * Textbox (ReadOnly)
/// * Textbox (ReadWrite)
/// * Move code to submodules
///
use bevy::{
    asset::Asset,
    input::mouse::{MouseScrollUnit, MouseWheel},
    prelude::*,
    window::PrimaryWindow,
};
use std::cmp::PartialEq;

#[derive(Debug, Component, PartialEq)]
pub struct TargetAsset<T: Asset> {
    pub handle: Handle<T>,
}

#[derive(Debug, Component, PartialEq)]
pub struct TargetEntity {
    pub entity: Entity,
}

pub struct GameUiPlugin;

impl Plugin for GameUiPlugin {
    fn build(&self, app: &mut App) {
        app.add_event::<Alert>()
            .add_systems(Startup, init_alerts)
            .add_systems(PreUpdate, create_titles)
            .add_systems(
                Update,
                (
                    manage_titles,
                    manage_button_interaction,
                    manage_select_active,
                    manage_cursor,
                    manage_scroll,
                    manage_collapse_hiding,
                    manage_collapse_active,
                    show_child_number,
                    manage_sort,
                    spawn_alert,
                ),
            )
            .add_systems(PostUpdate, close_window);
    }
}

pub use title::*;
mod title {
    use super::*;

    #[derive(Debug, Component, Default)]
    pub struct Title {
        pub text: String,
        pub font: Option<Handle<Font>>,
    }

    #[derive(Debug)]
    pub struct TitleBarBase {
        bg_color: Color,
    }

    impl TitleBarBase {
        pub fn new(bg_color: Color) -> Self {
            Self { bg_color }
        }

        pub fn bundle(&self) -> NodeBundle {
            NodeBundle {
                style: Style {
                    padding: UiRect::all(Val::Px(1.0)),
                    margin: UiRect::all(Val::Px(1.0)),
                    border: UiRect::all(Val::Px(1.0)),
                    flex_direction: FlexDirection::Row,
                    align_items: AlignItems::Center,
                    align_content: AlignContent::Center,
                    justify_content: JustifyContent::SpaceBetween,
                    ..default()
                },
                background_color: self.bg_color.into(),
                border_color: Color::BLACK.into(),
                ..default()
            }
        }
    }

    #[derive(Debug, Component)]
    pub struct Note {
        pub text: String,
    }

    #[derive(Debug, Component)]
    pub struct TitleText;

    #[derive(Debug, Component)]
    pub struct Minimize {
        pub target: Entity,
    }

    #[derive(Debug, Component)]
    pub struct Close {
        pub target: Entity,
    }

    pub fn create_titles(
        events: Query<
            (
                Entity,
                &Title,
                Option<&Note>,
                Option<&Minimize>,
                Option<&Close>,
            ),
            Added<Title>,
        >,
        mut commands: Commands,
    ) {
        events.for_each(|(entity, title, note, minimize, close)| {
            commands.entity(entity).with_children(|parent| {
                let style = match &title.font {
                    Some(handle) => TextStyle {
                        color: Color::BLACK,
                        font: handle.clone(),
                        ..default()
                    },
                    None => TextStyle {
                        color: Color::BLACK,
                        ..default()
                    },
                };
                parent.spawn((
                    TextBundle {
                        text: Text {
                            sections: vec![
                                TextSection {
                                    value: title.text.clone(),
                                    style,
                                },
                                TextSection {
                                    value: note
                                        .unwrap_or(&Note {
                                            text: String::new(),
                                        })
                                        .text
                                        .clone(),
                                    style: TextStyle {
                                        color: Color::BLACK,
                                        ..default()
                                    },
                                },
                            ],
                            ..default()
                        },
                        style: Style {
                            margin: UiRect::all(Val::Px(3.0)),
                            padding: UiRect::all(Val::Px(3.0)),
                            left: Val::Px(0.0),
                            ..default()
                        },
                        ..default()
                    },
                    Sorting(0),
                    TitleText,
                ));
                if minimize.is_some() || close.is_some() {
                    parent
                        .spawn(NodeBundle {
                            style: Style {
                                // border: UiRect::all(Val::Px(1.0)),
                                // margin: UiRect::all(Val::Px(1.0)),
                                // padding: UiRect::all(Val::Px(1.0)),
                                right: Val::Px(0.0),
                                ..default()
                            },
                            background_color: Color::WHITE.into(),
                            border_color: Color::BLACK.into(),
                            ..default()
                        })
                        .with_children(|parent| {
                            if let Some(Minimize { target }) = minimize {
                                parent
                                    .spawn((
                                        ButtonBundle {
                                            style: Style {
                                                border: UiRect::all(Val::Px(1.0)),
                                                // margin: UiRect::all(Val::Px(1.0)),
                                                padding: UiRect::all(Val::Px(1.0)),
                                                ..default()
                                            },
                                            background_color: Color::WHITE.into(),
                                            border_color: Color::BLACK.into(),
                                            ..default()
                                        },
                                        Collapse { target: *target },
                                        Active,
                                    ))
                                    .with_children(|parent| {
                                        parent.spawn(TextBundle::from_section(
                                            "-",
                                            TextStyle {
                                                color: Color::BLACK,
                                                ..default()
                                            },
                                        ));
                                    });
                            }
                            if let Some(Close { target }) = close {
                                parent
                                    .spawn((
                                        ButtonBundle {
                                            style: Style {
                                                border: UiRect::all(Val::Px(1.0)),
                                                // margin: UiRect::all(Val::Px(1.0)),
                                                padding: UiRect::all(Val::Px(1.0)),
                                                ..default()
                                            },
                                            background_color: Color::WHITE.into(),
                                            border_color: Color::BLACK.into(),
                                            ..default()
                                        },
                                        Close { target: *target },
                                    ))
                                    .with_children(|parent| {
                                        parent.spawn(TextBundle::from_section(
                                            "x",
                                            TextStyle {
                                                color: Color::BLACK,
                                                ..default()
                                            },
                                        ));
                                    });
                            }
                        });
                }
            });
        });
    }

    pub fn manage_titles(
        events: Query<(&Title, Option<&Note>, &Children), Or<(Changed<Title>, Changed<Note>)>>,
        mut texts: Query<&mut Text, With<TitleText>>,
    ) {
        events.iter().for_each(|(title, note, children)| {
            children.iter().for_each(|child| {
                if let Ok(mut text) = texts.get_mut(*child) {
                    *text = Text {
                        sections: vec![
                            TextSection {
                                value: title.text.clone(),
                                style: TextStyle {
                                    color: Color::BLACK,
                                    ..default()
                                },
                            },
                            TextSection {
                                value: note
                                    .unwrap_or(&Note {
                                        text: String::new(),
                                    })
                                    .text
                                    .clone(),
                                style: TextStyle {
                                    color: Color::BLACK,
                                    ..default()
                                },
                            },
                        ],
                        ..default()
                    }
                }
            })
        })
    }

    pub fn close_window(
        events: Query<(&Interaction, &Close), (With<Button>, Changed<Interaction>)>,
        mut commands: Commands,
    ) {
        events
            .iter()
            .filter(|(&interaction, _)| interaction == Interaction::Pressed)
            .for_each(|(_, Close { target })| {
                commands.entity(*target).despawn_recursive();
            });
    }
}

pub use collapse::*;
mod collapse {
    use super::*;

    #[derive(Debug, Component)]
    pub struct Collapse {
        pub target: Entity,
    }

    pub fn manage_collapse_active(
        events: Query<
            (Entity, Option<&Active>, &Interaction),
            (Changed<Interaction>, With<Button>, With<Collapse>),
        >,
        mut commands: Commands,
    ) {
        events
            .iter()
            .filter(|(_, _, &interaction)| interaction == Interaction::Pressed)
            .for_each(|(entity, active, _)| {
                match active {
                    Some(_) => {
                        commands.entity(entity).remove::<Active>();
                    }
                    None => {
                        // Set this butotn to active
                        commands.entity(entity).insert(Active);
                    }
                };
            });
    }

    pub fn manage_collapse_hiding(
        added: Query<Entity, (Added<Active>, With<Collapse>)>,
        mut removed: RemovedComponents<Active>,
        collapses: Query<&Collapse, With<Button>>,
        mut styles: Query<&mut Style>,
    ) {
        // Removed collapse, hide the target entity
        removed.iter().for_each(|e| {
            if let Ok(Collapse { target }) = collapses.get(e) {
                if let Ok(mut style) = styles.get_mut(*target) {
                    style.display = Display::None;
                }
            }
        });
        // Added collapse, display the target entity
        added.iter().for_each(|e| {
            if let Ok(Collapse { target }) = collapses.get(e) {
                if let Ok(mut style) = styles.get_mut(*target) {
                    style.display = Display::Flex;
                }
            }
        });
    }

    pub fn show_child_number(
        events: Query<(Entity, &Children), (Changed<Children>, With<Node>)>,
        mut tabs: Query<(Entity, &Collapse)>,
        mut commands: Commands,
    ) {
        // Any time a widget changes
        events.iter().for_each(|(entity, children)| {
            // Find any tabs which have this as a target
            tabs.iter_mut()
                .find_map(|(button, collapse)| {
                    if entity == collapse.target {
                        Some(button)
                    } else {
                        None
                    }
                })
                .iter()
                .for_each(|button| {
                    let num_children = children.len();
                    commands.entity(*button).insert(Note {
                        text: format!("({})", num_children),
                    });
                });
        });
    }
}

pub use buttons::*;
mod buttons {
    use super::*;

    #[derive(Debug, Component)]
    pub struct Active;

    pub fn manage_button_interaction(
        events: Query<
            (Entity, &Interaction, Option<&Active>),
            (Changed<Interaction>, With<Button>),
        >,
        added_active: Query<Entity, Added<Active>>,
        mut removed_active: RemovedComponents<Active>,
        mut bg_colors: Query<&mut BackgroundColor>,
    ) {
        removed_active.iter().for_each(|e| {
            if let Ok(mut bg_color) = bg_colors.get_mut(e) {
                *bg_color = Color::WHITE.into();
            }
        });
        added_active.for_each(|e| {
            if let Ok(mut bg_color) = bg_colors.get_mut(e) {
                *bg_color = Color::ORANGE.into();
            }
        });
        events.for_each(|(entity, interaction, active)| {
            if let Ok(mut bg_color) = bg_colors.get_mut(entity) {
                match active {
                    Some(_) => match interaction {
                        Interaction::None => *bg_color = Color::ORANGE.into(),
                        Interaction::Pressed => *bg_color = Color::YELLOW.into(),
                        Interaction::Hovered => *bg_color = Color::RED.into(),
                    },
                    None => match interaction {
                        Interaction::None => *bg_color = Color::WHITE.into(),
                        Interaction::Pressed => *bg_color = Color::WHITE.into(),
                        Interaction::Hovered => *bg_color = Color::GRAY.into(),
                    },
                }
            }
        })
    }

    /// Marks a container node as having single- or multi-active components
    #[derive(Debug, Component)]
    pub enum Select {
        Multi,
        Single,
    }

    pub fn manage_select_active(
        events: Query<(Entity, &Parent), Added<Active>>,
        children: Query<(&Select, &Children)>,
        mut commands: Commands,
    ) {
        events.iter().for_each(|(entity, parent)| {
            if let Ok((select, childs)) = children.get(parent.get()) {
                match select {
                    Select::Single => {
                        childs
                            .iter()
                            .filter(|&child| *child != entity)
                            .for_each(|&child| {
                                commands.entity(child).remove::<Active>();
                            })
                    }
                    Select::Multi => (),
                }
            }
        });
    }
}

pub use scroll::*;
mod scroll {
    use super::*;

    #[derive(Debug, Component)]
    pub struct Scroll;

    pub fn manage_scroll(
        mut events: EventReader<MouseWheel>,
        mut query: Query<(&Interaction, &Parent, &Children, &mut Style), With<Scroll>>,
        interactions: Query<&Interaction>,
    ) {
        events.iter().for_each(|MouseWheel { unit, y, .. }| {
            query
                .iter_mut()
                .filter(|(&interaction, parent, children, _)| {
                    let self_hover = interaction == Interaction::Hovered;
                    let child_hover = children.iter().any(|&child| {
                        if let Ok(&interaction) = interactions.get(child) {
                            interaction == Interaction::Hovered
                        } else {
                            false
                        }
                    });
                    let parent_hover = interactions
                        .get(parent.get())
                        .map_or(false, |&interaction| interaction == Interaction::Hovered);
                    self_hover || child_hover || parent_hover
                })
                .for_each(|(_, _, _, mut style)| {
                    let factor = 2.0;
                    let val = match unit {
                        MouseScrollUnit::Line => match style.top {
                            Val::Px(v) => v + (y * factor),
                            _ => (*y) * factor,
                        },
                        MouseScrollUnit::Pixel => match style.top {
                            Val::Px(v) => v + (y * factor),
                            _ => (*y) * factor,
                        },
                    };
                    style.top = Val::Px(val.min(0.0));
                });
        });
    }
}

use cursor::*;
mod cursor {
    use super::*;

    pub fn manage_cursor(
        events: Query<&Interaction, Changed<Interaction>>,
        mut window: Query<&mut Window, With<PrimaryWindow>>,
    ) {
        events.for_each(|interaction| {
            let mut win = window.single_mut();

            win.cursor.icon = match interaction {
                Interaction::None => CursorIcon::Default,
                Interaction::Hovered => CursorIcon::Hand,
                Interaction::Pressed => CursorIcon::Grabbing,
            }
        })
    }
}

pub use sort::*;
mod sort {
    use std::cmp::Ordering;

    use super::*;

    #[derive(Debug, Component)]
    pub struct Sorting(pub u8);

    pub fn manage_sort(
        mut events: Query<&mut Children, Changed<Children>>,
        sorting: Query<Option<&Sorting>>,
    ) {
        events.iter_mut().for_each(|mut children| {
            children.sort_by(|&a, &b| match (sorting.get(a), sorting.get(b)) {
                (Ok(Some(Sorting(ord_a))), Ok(Some(Sorting(ord_b)))) => {
                    if ord_a > ord_b {
                        Ordering::Greater
                    } else if ord_a < ord_b {
                        Ordering::Less
                    } else {
                        Ordering::Equal
                    }
                }
                (Ok(Some(_)), Err(_)) | (Ok(Some(_)), Ok(None)) => Ordering::Less,
                (Err(_), Ok(Some(_))) | (Ok(None), Ok(Some(_))) => Ordering::Greater,
                _ => Ordering::Equal,
            });
        })
    }
}

pub use alert::*;
pub mod alert {
    use super::*;

    #[derive(Debug, Event)]
    pub enum Alert {
        Info(String),
        Warn(String),
        Danger(String),
    }

    #[derive(Debug, Component)]
    pub struct AlertsWidget;

    pub fn init_alerts(mut commands: Commands) {
        commands.spawn((
            NodeBundle {
                style: Style {
                    top: Val::Px(0.0),
                    right: Val::Px(0.0),
                    position_type: PositionType::Absolute,
                    width: Val::Percent(33.0),
                    padding: UiRect::all(Val::Px(1.0)),
                    margin: UiRect::all(Val::Px(1.0)),
                    border: UiRect::all(Val::Px(1.0)),
                    flex_direction: FlexDirection::Column,
                    justify_items: JustifyItems::Center,
                    ..default()
                },
                border_color: Color::WHITE.into(),
                ..default()
            },
            AlertsWidget,
        ));
    }

    pub fn spawn_alert(
        mut events: EventReader<Alert>,
        root: Query<Entity, With<AlertsWidget>>,
        mut commands: Commands,
    ) {
        events.iter().for_each(|alert| {
            info!("Processing alert {:?}", alert);

            let (color, text) = match alert {
                Alert::Info(text) => (Color::BLUE, text),
                Alert::Warn(text) => (Color::ORANGE, text),
                Alert::Danger(text) => (Color::RED, text),
            };
            commands.entity(root.single()).with_children(|parent| {
                parent
                    .spawn(NodeBundle {
                        style: Style {
                            width: Val::Percent(100.0),
                            padding: UiRect::all(Val::Px(1.0)),
                            margin: UiRect::all(Val::Px(1.0)),
                            border: UiRect::all(Val::Px(2.0)),
                            flex_direction: FlexDirection::Column,
                            justify_self: JustifySelf::Center,
                            ..default()
                        },
                        background_color: Color::WHITE.into(),
                        border_color: color.into(),
                        ..default()
                    })
                    .with_children(|parent| {
                        parent.spawn((
                            TitleBarBase::new(color).bundle(),
                            Title {
                                text: "Heads up!".into(),
                                ..default()
                            },
                            Close {
                                target: parent.parent_entity(),
                            },
                            Sorting(0),
                        ));
                        parent.spawn(TextBundle::from_section(
                            text,
                            TextStyle {
                                color: Color::BLACK.into(),
                                ..default()
                            },
                        ));
                    });
            });
        });
    }
}

use dragndrop::*;
pub mod dragndrop {
    use super::*;

    #[derive(Debug, Component)]
    struct DragNDrop;

    pub fn dragndrop() {
        todo!()
    }
}
