//! 3D galaxy view for starting-base selection.

use bevy::prelude::*;

use super::{StartingBaseDraft, StartingBaseFocusRequest, StartingBaseSpawned};
use crate::gameplay::campaign::CampaignDraft;
use crate::gameplay::galaxy::{StartingBaseMapSystem, contents};
use crate::state::AppState;

const SELECTION_PIXEL_THRESHOLD: f32 = 18.0;
const SELECTED_SCALE: f32 = 2.4;
const CANDIDATE_SCALE: f32 = 1.25;
const FOGGED_SCALE: f32 = 0.58;
const SELECTION_LERP_SPEED: f32 = 10.0;
const STARTING_BASE_FOCUS_DISTANCE: f32 = 90.0;

#[derive(Component)]
struct StartingBaseSceneRoot;

#[derive(Component)]
pub(super) struct StartingBaseSystemVisual {
    candidate_index: Option<usize>,
}

/// Marker for POI entities spawned in starting base view
#[derive(Component)]
pub struct StartingBasePoi;

/// Resource tracking the currently spawned POI system entity
#[derive(Resource, Default)]
pub struct SpawnedPoiSystem {
    entity: Option<Entity>,
}

/// System to spawn POIs for the selected candidate
pub fn spawn_selected_pois(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    campaign: Res<CampaignDraft>,
    draft: Res<StartingBaseDraft>,
    mut spawned: ResMut<SpawnedPoiSystem>,
    systems: Query<(&GlobalTransform, &StartingBaseSystemVisual)>,
    existing_pois: Query<&GlobalTransform, With<StartingBasePoi>>,
) {
    let Some(candidate_index) = draft.selected_index else {
        // No selection, despawn any existing POIs
        if let Some(entity) = spawned.entity.take() {
            commands.entity(entity).despawn();
        }
        return;
    };

    let Some(candidate) = draft.candidates.get(candidate_index) else {
        return;
    };

    let Some(galaxy) = campaign.galaxy.as_ref() else {
        return;
    };

    // Find the system position for this candidate
    let system_position = systems.iter().find_map(|(transform, visual)| {
        if visual.candidate_index == Some(candidate_index) {
            Some(transform.translation())
        } else {
            None
        }
    });

    let target_position = match system_position {
        Some(pos) => pos,
        None => return,
    };

    // Check if we already spawned POIs for this system and if the position matches
    let should_respawn = if let Some(entity) = spawned.entity {
        // Check if the entity still exists by querying for it
        if let Ok(gt) = existing_pois.get(entity) {
            // If the entity still exists and position matches, keep it
            if gt.translation().distance(target_position) < 1.0 {
                false
            } else {
                // Position changed, need to respawn
                commands.entity(entity).despawn();
                true
            }
        } else {
            // Entity doesn't exist, need to spawn
            true
        }
    } else {
        // No existing POI system, need to spawn
        true
    };

    if !should_respawn {
        return;
    }

    // Find the system index in the galaxy
    let system_index = galaxy.systems.iter().position(|s| s.id == candidate.id);
    let Some(system_index) = system_index else {
        return;
    };

    let Some(system_contents) = galaxy.contents.get(system_index) else {
        return;
    };

    let system = &galaxy.systems[system_index];

    // Create content assets for spawning
    let content_assets = contents::ContentAssets::new(&mut meshes, &mut materials);

    // Spawn the POI system entity
    let ctx = contents::SystemContext {
        id: &system.id,
        name: &system.name,
        faction: system.faction,
        security: system.security,
        is_core: system.is_core,
    };

    // Spawn the POI system root
    let poi_root = commands
        .spawn((
            Transform::from_translation(target_position),
            Visibility::default(),
            InheritedVisibility::default(),
            StartingBasePoi,
            GlobalTransform::default(),
        ))
        .id();

    // Spawn a star entity for the POIs to orbit around
    let star_entity = commands
        .spawn((
            crate::gameplay::galaxy::Star,
            crate::gameplay::galaxy::Massive { mass: 5000.0 },
            crate::gameplay::galaxy::Luminosity { value: 50.0 },
            crate::gameplay::galaxy::MassLock { radius: 5.0 },
            crate::gameplay::galaxy::BoundingVolume { radius: 1.5 },
            crate::gameplay::galaxy::poi::Identifiable {
                id: format!("{}-star", system.id),
                display_name: system.name.clone(),
                classification: crate::gameplay::galaxy::poi::Classification::Celestial,
            },
            Transform::default(),
        ))
        .set_parent(poi_root)
        .id();

    // Spawn all POI children
    commands.entity(poi_root).with_children(|parent| {
        contents::spawn_system_contents(
            parent,
            &ctx,
            system_contents,
            star_entity,
            &content_assets,
            &mut materials,
        );
    });

    spawned.entity = Some(poi_root);
}

/// System to despawn POIs when exiting the starting base scene
pub fn despawn_pois_on_exit(
    mut commands: Commands,
    mut spawned: ResMut<SpawnedPoiSystem>,
    pois: Query<Entity, With<StartingBasePoi>>,
) {
    for entity in &pois {
        commands.entity(entity).despawn();
    }
    spawned.entity = None;
}

pub fn setup_starting_base_scene(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    campaign: Res<CampaignDraft>,
    mut draft: ResMut<StartingBaseDraft>,
    mut next_state: ResMut<NextState<AppState>>,
) {
    let Some(galaxy) = campaign.galaxy.as_ref() else {
        bevy::log::error!("Starting base selection entered without an accepted galaxy");
        next_state.set(AppState::Galaxy);
        return;
    };
    if galaxy.systems.len() != galaxy.contents.len() {
        bevy::log::error!(
            "Starting base selection entered with an invalid galaxy: systems={}, contents={}",
            galaxy.systems.len(),
            galaxy.contents.len()
        );
        next_state.set(AppState::Galaxy);
        return;
    }
    let map = galaxy.starting_base_map();
    draft.candidates = map.candidates;
    draft.selected_index = None;

    let star_mesh = meshes.add(Sphere::new(1.0).mesh().ico(3).unwrap());
    let fogged_material = materials.add(StandardMaterial {
        base_color: Color::srgb(0.10, 0.12, 0.16),
        emissive: LinearRgba::new(0.02, 0.025, 0.035, 1.0),
        unlit: true,
        ..default()
    });
    let connection_material = materials.add(StandardMaterial {
        base_color: Color::srgb(0.07, 0.09, 0.13),
        emissive: LinearRgba::new(0.025, 0.035, 0.055, 1.0),
        unlit: true,
        ..default()
    });

    let candidate_materials: Vec<Handle<StandardMaterial>> = map
        .systems
        .iter()
        .map(|system| {
            materials.add(StandardMaterial {
                base_color: Color::srgb(system.color[0], system.color[1], system.color[2]),
                emissive: LinearRgba::new(
                    system.color[0] * 1.6,
                    system.color[1] * 1.6,
                    system.color[2] * 1.6,
                    1.0,
                ),
                unlit: true,
                ..default()
            })
        })
        .collect();

    commands
        .spawn((
            Transform::default(),
            Visibility::default(),
            InheritedVisibility::default(),
            StartingBaseSceneRoot,
            StartingBaseSpawned,
        ))
        .with_children(|parent| {
            for (index, system) in map.systems.iter().enumerate() {
                spawn_system(
                    parent,
                    &star_mesh,
                    &candidate_materials[index],
                    &fogged_material,
                    system,
                );
            }

            for (a, b) in &map.connections {
                let Some(from) = map.systems.get(*a).map(|system| system.position) else {
                    continue;
                };
                let Some(to) = map.systems.get(*b).map(|system| system.position) else {
                    continue;
                };
                spawn_connection(parent, &mut meshes, &connection_material, from, to);
            }
        });
}

fn spawn_system(
    parent: &mut ChildSpawnerCommands,
    star_mesh: &Handle<Mesh>,
    candidate_material: &Handle<StandardMaterial>,
    fogged_material: &Handle<StandardMaterial>,
    system: &StartingBaseMapSystem,
) {
    let scale = if system.candidate_index.is_some() {
        CANDIDATE_SCALE
    } else {
        FOGGED_SCALE
    };
    let material = if system.candidate_index.is_some() {
        candidate_material.clone()
    } else {
        fogged_material.clone()
    };

    parent.spawn((
        Mesh3d(star_mesh.clone()),
        MeshMaterial3d(material),
        Transform::from_translation(system.position).with_scale(Vec3::splat(scale)),
        StartingBaseSystemVisual {
            candidate_index: system.candidate_index,
        },
    ));
}

fn spawn_connection(
    parent: &mut ChildSpawnerCommands,
    meshes: &mut Assets<Mesh>,
    material: &Handle<StandardMaterial>,
    from: Vec3,
    to: Vec3,
) {
    let delta = to - from;
    let length = delta.length();
    if length < 0.01 {
        return;
    }
    let midpoint = (from + to) * 0.5;
    let direction = delta / length;
    let rotation = Quat::from_rotation_arc(Vec3::Y, direction);
    parent.spawn((
        Mesh3d(meshes.add(Cylinder::new(0.08, length).mesh())),
        MeshMaterial3d(material.clone()),
        Transform::from_translation(midpoint).with_rotation(rotation),
    ));
}

pub fn focus_starting_base_camera(
    mut commands: Commands,
    focus: Option<Res<StartingBaseFocusRequest>>,
    systems: Query<(&GlobalTransform, &StartingBaseSystemVisual)>,
    mut goal: ResMut<crate::camera::OrbitFocusGoal>,
) {
    let Some(focus) = focus else {
        return;
    };
    let focus_index = focus.candidate_index;
    commands.remove_resource::<StartingBaseFocusRequest>();

    let Some(target) = systems.iter().find_map(|(transform, visual)| {
        if visual.candidate_index == Some(focus_index) {
            Some(transform.translation())
        } else {
            None
        }
    }) else {
        return;
    };

    // This is an inspection scene (no `track_camera_target`), so it owns the
    // orbit target directly: arm target + distance and let the shared
    // `orbit_camera_control` tween toward them. The angle is left untouched so
    // a user-chosen rotation persists across candidate switches. Re-clicking a
    // candidate overwrites the goal, smoothly retargeting an in-flight tween.
    goal.arm(Some(target), Some(STARTING_BASE_FOCUS_DISTANCE), None);
}

pub fn animate_starting_base_selection(
    draft: Res<StartingBaseDraft>,
    time: Res<Time>,
    mut systems: Query<(&StartingBaseSystemVisual, &mut Transform)>,
) {
    let dt = time.delta_secs().min(0.1);
    let alpha = (dt * SELECTION_LERP_SPEED).clamp(0.0, 1.0);

    for (visual, mut transform) in &mut systems {
        let target = match visual.candidate_index {
            Some(index) if Some(index) == draft.selected_index => SELECTED_SCALE,
            Some(_) => CANDIDATE_SCALE,
            None => FOGGED_SCALE,
        };
        let current = transform.scale.x;
        let next = current + (target - current) * alpha;
        if (next - current).abs() > 1e-4 {
            transform.scale = Vec3::splat(next);
        }
    }
}
