use bevy::{
    color::palettes::css::WHITE,
    input::common_conditions::input_just_released,
    input_focus::InputDispatchPlugin, picking::hover::Hovered,
    prelude::*,
    reflect::Is,
    sprite::Anchor, ui::{
        InteractionDisabled,
        Pressed,
    }, ui_widgets::{
        Activate,
        Button,
        UiWidgetsPlugins,
        observe,
    }
};

#[derive(Component)]
pub struct Menu;

#[derive(Component)]
pub struct TwoD;

#[derive(Component)]
pub struct ThreeD;

#[derive(Component)]
pub struct MenuButton;

fn menu_button(asset_server: &AssetServer, width: Val, height: Val, text: impl ToString) -> impl Bundle {
    (
        Node {
            width,
            height,
            border: UiRect::all(px(5)),
            justify_content: JustifyContent::Center,
            align_items: AlignItems::Center,
            border_radius: BorderRadius::MAX,
            ..default()
        },
        Button,
        MenuButton,
        Hovered::default(),
        BorderColor::all(Color::BLACK),
        BackgroundColor(Color::srgb(0.7, 0.7, 0.7)),
        children![(
            Text::new(text.to_string()),
            TextFont {
                font: asset_server.load("fonts/default.ttf"),
                font_size: 24.0,
                ..default()
            },
            TextColor(Color::srgb(0.2, 0.2, 0.2)),
        )],
    )
}

fn button_on_interaction<E: EntityEvent, C: Component>(
    event: On<E, C>,
    mut buttons: Query<
        (
            &Hovered,
            Has<InteractionDisabled>,
            Has<Pressed>,
            &mut BorderColor,
            &Children,
        ),
        With<MenuButton>,
    >,
) {
    if let Ok((hovered, disabled, pressed, mut border_color, children)) =
        buttons.get_mut(event.event_target())
    {
        if children.is_empty() {
            return;
        }
        let hovered = hovered.get();
        let pressed = pressed && !(E::is::<Remove>() && C::is::<Pressed>());
        let disabled = disabled && !(E::is::<Remove>() && C::is::<InteractionDisabled>());
        match (disabled, hovered, pressed) {
            // Disabled button
            (true, _, _) => {
                border_color.set_all(Color::srgb(0.8, 0.8, 0.8));
            }

            // Pressed and hovered button
            (false, true, true) => {
                border_color.set_all(Color::srgb(1.0, 0.3, 0.3));
            }

            // Hovered, unpressed button
            (false, true, false) => {
                border_color.set_all(Color::srgb(1.0, 1.0, 1.0));
            }

            // Unhovered button (either pressed or not).
            (false, false, _) => {
                border_color.set_all(Color::srgb(0.0, 0.0, 0.0));
            }
        }
    }
}


fn handle_escape(
    mut commands: Commands,
    maybe_2d: Option<Single<(),With<TwoD>>>,
    maybe_3d: Option<Single<(),With<ThreeD>>>,
) {
    if maybe_2d.is_some() {
        commands.run_system_cached(multidim_switch_2d_to_menu);
    } else if maybe_3d.is_some() {
        commands.run_system_cached(multidim_switch_3d_to_menu);
    }
}


fn menu_bundle(asset_server: &AssetServer) -> impl Bundle {
    (
        Menu,
        Node {
            width: percent(100),
            height: percent(100),
            align_items: AlignItems::Center,
            justify_content: JustifyContent::Center,
            display: Display::Flex,
            flex_direction: FlexDirection::Column,
            row_gap: px(10),
            ..default()
        },
        children![
            ( Camera2d ),
            (
                menu_button(asset_server, px(160), px(64), "2D"),
                observe(multidim_switch_menu_to_2d),
            ),
            (
                menu_button(asset_server, px(160), px(64), "3D"),
                observe(multidim_switch_menu_to_3d),
            ),
        ],
    )
}

fn bundle_2d(asset_server: &AssetServer, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<ColorMaterial>>) -> impl Bundle {
    (
        TwoD,
        Transform::default(),
        children![
            ( Camera2d ),
            (
                Mesh2d(meshes.add(Rectangle::default())),
                MeshMaterial2d(materials.add(Color::from(WHITE))),
                Anchor::CENTER,
                Transform {
                    translation: (-1. * Vec3::Z),
                    scale: Vec3::splat(128.),
                    ..default()
                },
            ),
            (
                Text2d::new("Random text over random mesh".to_string()),
                TextFont {
                    font: asset_server.load("fonts/default.ttf"),
                    font_size: 24.0,
                    ..default()
                },
                Anchor::CENTER,
                TextBackgroundColor(Color::BLACK),
                Transform::from_translation(Vec3 { x: 0., y: 100., z: 1. }),
            ),
        ],
    )
}

fn bundle_3d(mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<StandardMaterial>>) -> impl Bundle {
    (
        ThreeD,
        Transform::default(),
        children![
            (
                Mesh3d(meshes.add(Circle::new(4.0))),
                MeshMaterial3d(materials.add(Color::WHITE)),
                Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
            ),
            (
                Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
                MeshMaterial3d(materials.add(Color::WHITE)),
                Transform::from_xyz(0., 0.5, 0.),
            ),
            (
                PointLight {
                    shadows_enabled: true,
                    ..default()
                },
                Transform::from_xyz(4.0, 8.0, 4.0),
            ),
            (
                Camera3d::default(),
                Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y),
            ),
        ],
    )
}


pub fn multidim_init_menu(
    mut commands: Commands,
    assets: Res<AssetServer>
) {
    commands.spawn(menu_bundle(&assets));
}

fn multidim_init_2d(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    meshes: ResMut<Assets<Mesh>>,
    materials: ResMut<Assets<ColorMaterial>>,
) {
    commands.spawn(bundle_2d(&asset_server, meshes, materials));
}

pub fn multidim_init_3d(
    mut commands: Commands,
    meshes: ResMut<Assets<Mesh>>,
    materials: ResMut<Assets<StandardMaterial>>,
) {
    commands.spawn(bundle_3d(meshes, materials));
}


fn uninit_menu_dimension(
    mut commands: Commands,
    menu: Single<Entity, With<Menu>>,
) {
    commands.entity(*menu).try_despawn();
}

fn uninit_2d(
    mut commands: Commands,
    two_d: Single<Entity, With<TwoD>>,
) {
    commands.entity(*two_d).try_despawn();
}

fn uninit_3d(
    mut commands: Commands,
    three_d: Single<Entity, With<ThreeD>>
) {
    commands.entity(*three_d).try_despawn();
}


fn multidim_switch_menu_to_2d(
    _e: On<Activate>,
    mut commands: Commands,
) {
    commands.run_system_cached(uninit_menu_dimension);
    commands.run_system_cached(multidim_init_2d);
}

fn multidim_switch_menu_to_3d(
    _e: On<Activate>,
    mut commands: Commands,
) {
    commands.run_system_cached(uninit_menu_dimension);
    commands.run_system_cached(multidim_init_3d);
}

fn multidim_switch_2d_to_menu(
    mut commands: Commands,
) {
    commands.run_system_cached(uninit_2d);
    commands.run_system_cached(multidim_init_menu);
}

fn multidim_switch_3d_to_menu(
    mut commands: Commands,
) {
    commands.run_system_cached(uninit_3d);
    commands.run_system_cached(multidim_init_menu);
}


pub struct MultidimPlugin;

impl Plugin for MultidimPlugin {
    fn build(&self, app: &mut App) {
        app
            .add_observer(button_on_interaction::<Add, Pressed>)
            .add_observer(button_on_interaction::<Remove, Pressed>)
            .add_observer(button_on_interaction::<Add, InteractionDisabled>)
            .add_observer(button_on_interaction::<Remove, InteractionDisabled>)
            .add_observer(button_on_interaction::<Insert, Hovered>)
            .add_systems(Startup, multidim_init_menu)
            .add_systems(Update, handle_escape.run_if(input_just_released(KeyCode::Escape)));
    }
}

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins,
            UiWidgetsPlugins,
            InputDispatchPlugin,
            MultidimPlugin,
        ))
        .run();
}
