//! Click-to-select star systems + selection visuals + info panel refresh.
//!
//! Picking is done in **screen space**: each star's world position is projected
//! to viewport coordinates, and we find the closest one within a pixel
//! threshold. This is cheaper and more intuitive than raycasting against
//! individual sphere meshes for an unbounded number of tiny stars.

use bevy::prelude::*;
use bevy::window::PrimaryWindow;

use super::StarSystem;
use crate::camera::MainCamera;
use crate::gameplay::galaxy::params::SelectedStar;
use crate::gameplay::galaxy::ui::GalaxyInfoPanel;
use crate::gameplay::galaxy::SystemContents;
use crate::ui::util::cursor_over_ui;

// ── Tunables ────────────────────────────────────────────────────────────────

/// Cursor-to-star screen distance (in logical pixels) below which a click is
/// treated as a hit. The docs demo uses similar ~16px tolerance.
const SELECTION_PIXEL_THRESHOLD: f32 = 18.0;

/// Target visual scale for the selected star; non-selected stars animate back to 1.0.
const SELECTED_SCALE: f32 = 2.2;
/// Lerp speed (per second) for the scale animation. Higher = snappier.
const SELECTION_LERP_SPEED: f32 = 10.0;

// ── Pick on click ───────────────────────────────────────────────────────────

pub fn select_star_on_click(
    mouse_input: Res<ButtonInput<MouseButton>>,
    primary_window: Query<&Window, With<PrimaryWindow>>,
    camera_query: Query<(&Camera, &GlobalTransform), With<MainCamera>>,
    stars: Query<(Entity, &Transform), With<StarSystem>>,
    ui_nodes: Query<(&bevy::ui::ComputedNode, &GlobalTransform)>,
    mut selected: ResMut<SelectedStar>,
) {
    if !mouse_input.just_pressed(MouseButton::Left) {
        return;
    }
    let Ok(window) = primary_window.single() else {
        return;
    };
    if cursor_over_ui(window, &ui_nodes) {
        return;
    }

    let Some(cursor) = window.cursor_position() else {
        return;
    };
    let Ok((camera, camera_gt)) = camera_query.single() else {
        return;
    };

    // Find the closest star (by screen-pixel distance) within threshold.
    let mut best: Option<(Entity, f32)> = None;
    for (entity, transform) in &stars {
        let Ok(viewport) = camera.world_to_viewport(camera_gt, transform.translation) else {
            continue;
        };
        let dist = viewport.distance(cursor);
        if dist >= SELECTION_PIXEL_THRESHOLD {
            continue;
        }
        if best.is_none_or(|(_, d)| dist < d) {
            best = Some((entity, dist));
        }
    }

    // Always assign so change detection fires even when clearing the selection.
    let new_selection = best.map(|(entity, _)| entity);
    if selected.0 != new_selection {
        selected.0 = new_selection;
    }
}

// ── Visual highlight ────────────────────────────────────────────────────────

/// Smoothly lerp every star's scale toward 1.0 (default) or [`SELECTED_SCALE`]
/// when it's the currently selected entity.
pub fn animate_selected_star(
    selected: Res<SelectedStar>,
    mut stars: Query<(Entity, &mut Transform), With<StarSystem>>,
    time: Res<Time>,
) {
    // Avoid mutating (and marking-changed) every Transform when nothing is selected
    // and all stars are already at scale 1.0.
    let all_default = selected.0.is_none() && stars.iter().all(|(_, t)| t.scale == Vec3::ONE);
    if all_default {
        return;
    }

    let dt = time.delta_secs().min(0.1);
    let alpha = (dt * SELECTION_LERP_SPEED).clamp(0.0, 1.0);

    for (entity, mut transform) in &mut stars {
        let target = if selected.0 == Some(entity) {
            SELECTED_SCALE
        } else {
            1.0
        };
        let current = transform.scale.x;
        let next = current + (target - current) * alpha;
        // Avoid spamming change detection when within float-tolerance of the target.
        if (next - current).abs() > 1e-4 {
            transform.scale = Vec3::splat(next);
        }
    }
}

// ── Info panel refresh ──────────────────────────────────────────────────────

/// Rebuild the contents of the right-side info panel whenever the selection
/// changes. The panel shell stays put; only its children are replaced via
/// `despawn_related::<Children>()`.
pub fn refresh_info_panel(
    mut commands: Commands,
    selected: Res<SelectedStar>,
    stars: Query<&StarSystem>,
    panel_roots: Query<Entity, With<GalaxyInfoPanel>>,
) {
    if !selected.is_changed() {
        return;
    }
    let Ok(panel_entity) = panel_roots.single() else {
        return;
    };

    // Despawn existing children but keep the panel root itself.
    commands.entity(panel_entity).despawn_related::<Children>();

    let selected_entity = selected.0;
    let sys = selected_entity.and_then(|e| stars.get(e).ok());

    commands.entity(panel_entity).with_children(|parent| {
        parent.spawn((
            Text::new("Selected System"),
            TextFont {
                font_size: 20.0,
                ..default()
            },
            TextColor(Color::srgb(0.82, 0.90, 1.0)),
            Node {
                margin: UiRect::bottom(Val::Px(8.0)),
                ..default()
            },
        ));

        match sys {
            Some(sys) => {
                let title = sys.name.clone();
                parent.spawn((
                    Text::new(title),
                    TextFont {
                        font_size: 17.0,
                        ..default()
                    },
                    TextColor(Color::srgb(0.20, 0.83, 0.93)),
                    Node {
                        margin: UiRect::bottom(Val::Px(8.0)),
                        ..default()
                    },
                ));
                spawn_info_line(parent, "Faction", sys.faction);
                spawn_info_line(parent, "Security", &format!("{:.2}", sys.security));
                spawn_info_line(parent, "POIs", &format!("{}", sys.poi_count));

                spawn_info_line(parent, "ID", &sys.id);
            }
            None => {
                parent.spawn((
                    Text::new("Click a star to inspect."),
                    TextFont {
                        font_size: 15.0,
                        ..default()
                    },
                    TextColor(Color::srgb(0.55, 0.65, 0.82)),
                ));
            }
        }
    });
}

fn spawn_info_line(parent: &mut ChildSpawnerCommands, label: &str, value: &str) {
    parent
        .spawn(Node {
            width: Val::Percent(100.0),
            flex_direction: FlexDirection::Row,
            column_gap: Val::Px(8.0),
            ..default()
        })
        .with_children(|row| {
            row.spawn((
                Text::new(label),
                TextFont {
                    font_size: 14.0,
                    ..default()
                },
                TextColor(Color::srgb(0.55, 0.65, 0.82)),
                Node {
                    width: Val::Px(78.0),
                    ..default()
                },
            ));
            row.spawn((
                Text::new(value),
                TextFont {
                    font_size: 14.0,
                    ..default()
                },
                TextColor(Color::srgb(0.82, 0.90, 1.0)),
            ));
        });
}
