use bevy::{light::NotShadowCaster, prelude::*};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(
            Update,
            (
                handle_click,
                move_to_target,
                fryer_cook,
                customer_serve,
                update_progress_bar,
                update_burger_indicators,
            ),
        )
        .run();
}

#[derive(PartialEq)]
enum Item {
    None,
    Burger,
}

#[derive(Component)]
struct Customer {
    order: Item,
    served: bool,
    range: f32,
}

#[derive(Component)]
struct Fryer {
    range: f32,
    speed: f32,
}

#[derive(Component)]
struct Unit {
    move_speed: f32,
    height_offset: f32,
}

#[derive(Component)]
struct Player {
    holding: Item,
    progress: f32,
}
#[derive(Component)]
struct ProgressBar {
    width: f32,
    player_height: f32,
}

#[derive(Component)]
struct PlayerBurgerIndicator;

#[derive(Component)]
struct MoveTarget {
    position: Vec3,
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    asset_server: Res<AssetServer>,
) {
    // Camera
    commands.spawn((
        Camera3d::default(),
        Transform::from_xyz(0.0, 25.0, -25.0).looking_at(Vec3::ZERO, Vec3::Y),
    ));

    // Light
    commands.insert_resource(AmbientLight {
        color: Color::WHITE,
        brightness: 200.0, // Subtle fill light so shadows aren't pitch black
        ..default()
    });

    commands.spawn((
        DirectionalLight {
            illuminance: 1000.0,
            shadows_enabled: true,
            ..default()
        },
        Transform::from_rotation(Quat::from_euler(
            EulerRot::XYZ,
            -0.8, // Pitch down (looking down at scene)
            0.5,  // Yaw (angle from side)
            0.0,  // Roll (no rotation)
        )),
    ));

    // Plane
    commands.spawn((
        Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(50.0)))),
        MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
    ));

    // Fryer
    commands.spawn((
        Mesh3d(meshes.add(Cuboid::new(2.0, 1.75, 2.0))),
        Transform::from_xyz(-10.0, 0.875, 0.0),
        MeshMaterial3d(materials.add(Color::srgb(0.1, 0.1, 0.1))),
        Fryer {
            range: 3.0,
            speed: 50.0,
        },
    ));

    // Front Counter
    commands.spawn((
        Mesh3d(meshes.add(Cuboid::new(2.0, 1.5, 10.0))),
        Transform::from_xyz(10.0, 0.75, 0.0),
        MeshMaterial3d(materials.add(Color::srgb(0.7, 0.7, 0.7))),
    ));

    // Player
    commands.spawn((
        SceneRoot(asset_server.load("models/employee.glb#Scene0")),
        Transform::from_xyz(0.0, 1.5, 0.0).with_scale(Vec3::splat(0.5)),
        Unit {
            move_speed: 10.0,
            height_offset: 1.5,
        },
        Player {
            holding: Item::None,
            progress: 0.0,
        },
        MoveTarget {
            position: Vec3::new(0.0, 0.0, 0.0),
        },
        // Burger indicator
        children![(
            SceneRoot(asset_server.load("models/burger.glb#Scene0")),
            Transform::from_xyz(0.0, 7.1, 0.0).with_scale(Vec3::splat(0.9)),
            Visibility::Hidden,
            PlayerBurgerIndicator,
        )],
    ));

    // ProgressBar
    commands.spawn((
        Mesh3d(meshes.add(Cuboid::new(4.0, 0.5, 0.1))),
        MeshMaterial3d(materials.add(Color::srgb(0.0, 1.0, 0.0))),
        Transform::from_xyz(0.0, 0.0, 0.0),
        ProgressBar {
            width: 1.0,
            player_height: 4.0,
        },
        NotShadowCaster,
    ));

    // Customer
    commands.spawn((
        SceneRoot(asset_server.load("models/customer.glb#Scene0")),
        Transform::from_xyz(20.0, 1.5, 0.0).with_scale(Vec3::splat(0.5)),
        Unit {
            move_speed: 5.0,
            height_offset: 1.5,
        },
        Customer {
            order: Item::Burger,
            served: false,
            range: 4.0,
        },
        MoveTarget {
            position: Vec3::new(12.0, 0.0, 0.0),
        },
    ));
}

fn handle_click(
    mouse_button: Res<ButtonInput<MouseButton>>,
    windows: Query<&Window>,
    camera_query: Query<(&Camera, &GlobalTransform)>,
    mut target_query: Query<&mut MoveTarget, With<Player>>,
) {
    // Guard clause
    if !mouse_button.just_pressed(MouseButton::Right) {
        return;
    }

    let Ok(window) = windows.single() else {
        return;
    };

    let Some(cursor_position) = window.cursor_position() else {
        return;
    };

    let Ok((camera, camera_transform)) = camera_query.single() else {
        return;
    };

    let Ok(ray) = camera.viewport_to_world(camera_transform, cursor_position) else {
        return;
    };

    // check for sky clicks
    if ray.direction.y > 0.0 {
        return;
    }

    // Calculate the distance that the intercept with the ground occurs
    let t = -ray.origin.y / ray.direction.y;

    // plug T into our formula
    let intersection = ray.origin + ray.direction * t;

    for mut target in target_query.iter_mut() {
        target.position = intersection;
    }
}

fn move_to_target(mut query: Query<(&mut Transform, &MoveTarget, &Unit)>, time: Res<Time>) {
    for (mut transform, target, unit) in query.iter_mut() {
        let adjusted_target = target.position + Vec3::Y * unit.height_offset;
        let direction = adjusted_target - transform.translation;
        let distance = direction.length();

        // stop divide by 0 errors
        if distance < 0.001 {
            continue;
        }

        let move_amount = unit.move_speed * time.delta_secs();

        // Snap
        if distance < move_amount {
            transform.translation = adjusted_target;
        } else {
            transform.translation += direction.normalize() * move_amount;
            transform.look_at(adjusted_target, Vec3::Y);
        }
    }
}

fn fryer_cook(
    mut player: Query<(&mut Player, &Transform)>,
    fryer: Query<(&Fryer, &Transform)>,
    time: Res<Time>,
) {
    let Ok((mut player_data, player_transform)) = player.single_mut() else {
        return;
    };
    if player_data.holding == Item::Burger {
        return;
    }
    for (fryer, fryer_transform) in fryer.iter() {
        let distance = player_transform.translation - fryer_transform.translation;
        if distance.length() < fryer.range {
            player_data.progress += fryer.speed * time.delta_secs();

            if player_data.progress > 100.0 {
                player_data.holding = Item::Burger;
                println!("YOU HAVE BURGER!!");
            }
        } else {
            player_data.progress = 0.0;
        }
    }
}

fn update_progress_bar(
    player: Query<(&Player, &Transform), Without<ProgressBar>>,
    mut progress_bar: Query<(&mut Transform, &mut Visibility, &ProgressBar)>,
    camera: Query<&Transform, (With<Camera3d>, Without<ProgressBar>, Without<Player>)>,
) {
    let Ok((player, player_transform)) = player.single() else {
        return;
    };
    let Ok((mut bar_transform, mut visibility, bar_data)) = progress_bar.single_mut() else {
        return;
    };
    let Ok(camera_transform) = camera.single() else {
        return;
    };

    let progress_percent = (player.progress / 100.0).clamp(0.0, 1.0);

    // Update above player
    bar_transform.translation = player_transform.translation + Vec3::Y * bar_data.player_height;

    // Billboard effect
    let to_camera = (camera_transform.translation - bar_transform.translation).normalize();
    let angle = to_camera.z.atan2(to_camera.x);
    bar_transform.rotation = Quat::from_rotation_y(angle + std::f32::consts::FRAC_PI_2);

    bar_transform.scale.x = bar_data.width * progress_percent;

    if progress_percent == 0.0 || progress_percent == 1.0 {
        *visibility = Visibility::Hidden;
    } else {
        *visibility = Visibility::Visible;
    }
}

fn update_burger_indicators(
    player: Query<&Player>,
    mut burger: Query<(&mut Visibility, &mut Transform), With<PlayerBurgerIndicator>>,
    time: Res<Time>,
) {
    let Ok(player_data) = player.single() else {
        return;
    };
    let Ok((mut burger_vis, mut burger_transform)) = burger.single_mut() else {
        return;
    };

    // Rotate burger
    burger_transform.rotate_y(time.delta_secs());

    // Show/hide based on inventory
    *burger_vis = if player_data.holding == Item::Burger {
        Visibility::Visible
    } else {
        Visibility::Hidden
    };
}

fn customer_serve(
    mut customer: Query<(&mut Customer, &mut MoveTarget, &Transform)>,
    mut player: Query<(&mut Player, &Transform)>,
) {
    let Ok((mut player_data, player_transform)) = player.single_mut() else {
        return;
    };
    if player_data.holding == Item::None {
        return;
    }
    for (mut customer_data, mut customer_target, customer_transform) in customer.iter_mut() {
        if customer_data.served {
            continue;
        }
        if player_data.holding != customer_data.order {
            continue;
        }

        let distance = player_transform.translation - customer_transform.translation;
        if distance.length() < customer_data.range {
            player_data.holding = Item::None;
            customer_data.served = true;
            customer_target.position = Vec3::new(100.0, 0.0, 0.0);
        }
    }
}
