use bevy::input::mouse::MouseButton;
use bevy::prelude::*;
use bevy::window::PrimaryWindow;

use crate::ui::util::cursor_over_ui;

#[derive(Component)]
pub struct MainCamera;

/// Orbit-style camera: rotates around `target` at `distance`, controlled by mouse.
/// Used for inspection scenes like Galaxy where there is no player to follow.
///
/// Orientation is stored as a quaternion (`rotation`) to allow true 360° motion
/// with no gimbal lock. The base orientation is camera at `target + (0, 0, distance)`
/// looking toward `target` with up `+Y`. The quaternion rotates this base around
/// `target`. Mouse drag applies incremental rotations:
///   - horizontal delta → rotate around world `+Y` (yaw)
///   - vertical delta   → rotate around the camera's local `+X` (its right vector)
///
/// This yaw-around-world / pitch-around-local split is the standard free-look
/// construction; it never produces a degenerate "up" vector at the poles.
#[derive(Component, Debug, Clone, Copy)]
pub struct OrbitCamera {
    pub target: Vec3,
    pub distance: f32,
    pub rotation: Quat,
}

impl OrbitCamera {
    /// Reset to default orientation (camera at the canonical "starting" view).
    pub fn reset(&mut self) {
        *self = Self::default();
    }
}

impl Default for OrbitCamera {
    fn default() -> Self {
        // ~36° above horizontal, slightly rotated, roughly matching the docs
        // GalaxyScene opening shot. Built as yaw * pitch so the angles compose
        // the same way the incremental drag rotations do.
        let yaw = Quat::from_rotation_y(0.15);
        let pitch = Quat::from_rotation_x(0.625);
        Self {
            target: Vec3::ZERO,
            distance: 420.0,
            rotation: yaw * pitch,
        }
    }
}

/// Initial camera spawn. The same entity is reused across states; control systems
/// decide how it moves depending on which state is active.
pub fn spawn_camera(mut commands: Commands) {
    let orbit = OrbitCamera::default();
    let offset = orbit.rotation * Vec3::new(0.0, 0.0, orbit.distance);
    commands.spawn((
        Camera3d::default(),
        Transform::from_translation(orbit.target + offset)
            .with_rotation(orbit.rotation)
            .looking_at(orbit.target, Vec3::Y),
        MainCamera,
        orbit,
    ));
}

const ORBIT_ROTATE_SENSITIVITY: f32 = 0.005;
const ORBIT_ZOOM_SENSITIVITY: f32 = 0.1;
const ORBIT_MIN_DISTANCE: f32 = 40.0;
const ORBIT_MAX_DISTANCE: f32 = 1500.0;

/// Left-drag rotates around the orbit target; scroll wheel zooms.
/// No pitch clamping — the camera can tumble a full 360°.
///
/// Drag is suppressed when the cursor is over any UI node — otherwise clicking
/// buttons or panels would also rotate the camera.
pub fn orbit_camera_control(
    mouse_input: Res<ButtonInput<MouseButton>>,
    primary_window: Query<&Window, With<PrimaryWindow>>,
    mut mouse_motion: EventReader<bevy::input::mouse::MouseMotion>,
    mut scroll_events: EventReader<bevy::input::mouse::MouseWheel>,
    mut query: Query<(&mut Transform, &mut OrbitCamera), With<MainCamera>>,
    ui_nodes: Query<(&bevy::ui::ComputedNode, &GlobalTransform)>,
) {
    let Ok((mut transform, mut orbit)) = query.single_mut() else {
        // Drain pending input to avoid stale buildup when there's no camera.
        mouse_motion.clear();
        scroll_events.clear();
        return;
    };

    let cursor_over_ui = primary_window
        .single()
        .ok()
        .map(|w| cursor_over_ui(w, &ui_nodes))
        .unwrap_or(false);

    if mouse_input.pressed(MouseButton::Left) && !cursor_over_ui {
        for event in mouse_motion.read() {
            // iPhone-style: content follows the finger horizontally.
            let yaw = Quat::from_rotation_y(-event.delta.x * ORBIT_ROTATE_SENSITIVITY);
            // Vertical: drag down raises the camera so the scene appears to
            // shift down with the cursor.
            let pitch = Quat::from_rotation_x(event.delta.y * ORBIT_ROTATE_SENSITIVITY);
            // Pre-multiply yaw (world-axis rotation), post-multiply pitch
            // (local-axis rotation) — this preserves a stable horizon line as
            // long as `rotation` doesn't already pitch past 90°.
            orbit.rotation = yaw * orbit.rotation * pitch;
        }
    } else {
        mouse_motion.clear();
    }

    for event in scroll_events.read() {
        // Scroll up (positive y) → decrease distance (zoom in).
        orbit.distance *= 1.0 - event.y * ORBIT_ZOOM_SENSITIVITY;
    }

    orbit.distance = orbit.distance.clamp(ORBIT_MIN_DISTANCE, ORBIT_MAX_DISTANCE);

    // Position = target + rotation * (camera-forward * distance).
    // In the base orientation the camera sits on +Z looking at the origin, so
    // the offset is `rotation * +Z * distance`.
    let offset = orbit.rotation * Vec3::new(0.0, 0.0, orbit.distance);
    let position = orbit.target + offset;
    *transform = Transform::from_translation(position)
        .looking_at(orbit.target, Vec3::Y)
        // Re-apply rotation to recover the roll component that `looking_at`
        // would otherwise discard (matters when the camera is upside-down).
        .with_rotation(orbit.rotation);
}

/// Resource: set by UI buttons (e.g. "Center View") to request the orbit camera
/// be reset to its default orientation on the next frame. Consumed by
/// [`apply_orbit_reset`].
#[derive(Resource, Default, Debug)]
pub struct ResetOrbitCamera;

/// If [`ResetOrbitCamera`] is present, snap the orbit camera back to default
/// and consume the resource. Lives in `Update` so UI button presses (which
/// insert the resource via `Commands`) take effect on the following frame.
pub fn apply_orbit_reset(
    mut commands: Commands,
    flag: Option<Res<ResetOrbitCamera>>,
    mut query: Query<&mut OrbitCamera, With<MainCamera>>,
) {
    let Some(_flag) = flag else { return };
    let Ok(mut orbit) = query.single_mut() else {
        commands.remove_resource::<ResetOrbitCamera>();
        return;
    };
    orbit.reset();
    commands.remove_resource::<ResetOrbitCamera>();
}
