//! In-system scene setup and spawning.
//!
//! Handles spawning the star, POIs, and player ship docked at a station.

use bevy::ecs::system::SystemParam;
use bevy::prelude::*;

use crate::camera::{MainCamera, OrbitCamera};
use crate::gameplay::campaign::CampaignDraft;
use crate::gameplay::character_creation::STARTING_SHIPS;
use crate::gameplay::galaxy::{
    contents, Massive, Luminosity, MassLock, BoundingVolume, Identifiable,
    SystemContents,
};
use crate::gameplay::in_system::DockedState;
use crate::gameplay::physics::{BodyMass, ProximitySensor};

/// Tracks the currently active system for gameplay.
#[derive(Resource, Debug, Clone, Default)]
pub struct ActiveSystem {
    pub system_id: String,
    pub system_name: String,
    pub star_entity: Option<Entity>,
}

/// Marker for entities spawned in the in-system view.
#[derive(Component)]
pub struct InSystemSpawned;

/// Marker for the player ship entity.
#[derive(Component)]
pub struct PlayerShip;

/// Marker for docked state on the player.
#[derive(Component)]
pub struct Docked {
    pub station_entity: Entity,
}

/// Station marker component for tracking which station player is at.
#[derive(Component)]
pub struct DockingTarget {
    pub station_name: String,
}

/// Offset from station where player ship spawns when docked.
const DOCKED_OFFSET: Vec3 = Vec3::new(1.5, 0.0, 0.0);

/// World-space collision radius for the player ship. Matches its scaled
/// visual extent (a ~0.12-scale cuboid) so it cannot overlap stations,
/// planets, or the star.
const PLAYER_COLLIDER_RADIUS: f32 = 0.15;

/// World-space radius of the player ship's proximity sensor — the range at
/// which nearby bodies (stations, belts, the star) register as in-range via
/// `ProximityEvent`s.
const PLAYER_SCAN_RADIUS: f32 = 8.0;

/// Represents a docking target (either a station or a habitable planet).
#[derive(Debug, Clone)]
struct DockingTargetInfo {
    name: String,
    orbit: f32,
    phase: f32,
    population: u32,
    is_station: bool,
}

/// Bundles the in-system runtime state so [`setup_in_system_view`] stays
/// under clippy's argument-count threshold while also taking a camera query.
#[derive(SystemParam)]
pub(crate) struct InSystemRuntimeState<'w> {
    docked: ResMut<'w, DockedState>,
    active: ResMut<'w, ActiveSystem>,
}

pub fn setup_in_system_view(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    campaign: Res<CampaignDraft>,
    mut runtime: InSystemRuntimeState,
    mut camera_state: ResMut<crate::camera::CameraState>,
    mut camera_query: Query<&mut OrbitCamera, With<MainCamera>>,
) {
    // Get the selected starting base
    let Some(starting_base) = &campaign.starting_base else {
        bevy::log::error!("No starting base selected in CampaignDraft");
        return;
    };

    // Get the galaxy data
    let Some(galaxy) = &campaign.galaxy else {
        bevy::log::error!("No galaxy data in CampaignDraft");
        return;
    };

    // Find the selected system
    let system_index = match galaxy.systems.iter().position(|s| s.id == starting_base.system_id) {
        Some(idx) => idx,
        None => {
            bevy::log::error!("Selected system {} not found in galaxy", starting_base.system_id);
            return;
        }
    };

    let system = &galaxy.systems[system_index];
    let system_contents = &galaxy.contents[system_index];

    bevy::log::info!(
        "Setting up in-system view for: {} ({})",
        system.name,
        system.id
    );

    // Select docking target (station or habitable planet with highest population)
    let docking_target = select_docking_target(system_contents);

    let Some(docking_target) = docking_target else {
        bevy::log::warn!("No dockable targets found in system {}, spawning without docking", system.id);
        // Spawn scene without docking
        spawn_system_scene(
            &mut commands,
            &mut meshes,
            &mut materials,
            system,
            system_contents,
            None,
        );
        return;
    };

    bevy::log::info!(
        "Selected docking target: {} (population: {})",
        docking_target.name,
        docking_target.population
    );

    // Spawn the full scene with docking
    let (star_entity, station_entity) = spawn_system_scene(
        &mut commands,
        &mut meshes,
        &mut materials,
        system,
        system_contents,
        Some(&docking_target),
    );

    // Update resources
    runtime.docked.system_id = system.id.clone();
    runtime.docked.is_docked = true;
    runtime.docked.station_entity = Some(station_entity);

    runtime.active.system_id = system.id.clone();
    runtime.active.system_name = system.name.clone();
    runtime.active.star_entity = Some(star_entity);

    // Reconfigure the persistent camera for a tactical framing of the docked
    // station. Scenes must never spawn a new camera (see `camera` module docs);
    // a hard set is correct here because the coordinate scale changes
    // discontinuously (galaxy → in-system).
    let station_pos = orbital_position(docking_target.orbit, docking_target.phase);
    crate::camera::retarget_main_camera(
        &mut camera_query,
        station_pos,
        crate::camera::DOCKED_FRAMING_DISTANCE,
        crate::camera::tactical_rotation(),
    );

    // Orbit framing focused on the station; `track_camera_target` keeps the
    // orbit target locked to the station each frame.
    camera_state.mode = crate::camera::CameraMode::Orbit;
    camera_state.target_entity = Some(station_entity);
}

/// Select the docking target with highest population (station or habitable planet).
fn select_docking_target(contents: &SystemContents) -> Option<DockingTargetInfo> {
    // First try stations (highest population)
    let best_station = contents
        .stations
        .iter()
        .max_by_key(|s| s.population);

    if let Some(station) = best_station {
        return Some(DockingTargetInfo {
            name: station.name.clone(),
            orbit: station.orbit,
            phase: station.phase,
            population: station.population,
            is_station: true,
        });
    }

    // Fall back to habitable planets
    let best_planet = contents
        .planets
        .iter()
        .filter(|p| p.habitable)
        .max_by_key(|p| p.population);

    best_planet.map(|planet| DockingTargetInfo {
        name: planet.name.clone(),
        orbit: planet.orbit,
        phase: planet.phase,
        population: planet.population,
        is_station: false,
    })
}

/// Calculate orbital position from orbit radius and phase.
/// Matches the logic in galaxy/contents.rs but defined here since that's private.
fn orbital_position(orbit: f32, phase: f32) -> Vec3 {
    Vec3::new(phase.cos() * orbit, 0.0, phase.sin() * orbit)
}

/// Spawn the system scene including star, POIs, and player ship.
/// Returns (star_entity, docking_target_entity).
fn spawn_system_scene(
    commands: &mut Commands,
    meshes: &mut Assets<Mesh>,
    materials: &mut Assets<StandardMaterial>,
    system: &crate::gameplay::campaign::GeneratedGalaxySystem,
    contents: &SystemContents,
    docking_target: Option<&DockingTargetInfo>,
) -> (Entity, Entity) {
    // Create content assets for spawning
    let content_assets = contents::ContentAssets::new(meshes, materials);

    // Spawn the system root
    let system_root = commands
        .spawn((
            Transform::default(),
            Visibility::default(),
            InheritedVisibility::default(),
            InSystemSpawned,
            GlobalTransform::default(),
        ))
        .id();

    // Spawn the star
    let star_entity = commands
        .spawn((
            crate::gameplay::galaxy::Star,
            Massive { mass: 5000.0 },
            Luminosity { value: 50.0 },
            MassLock { radius: 5.0 },
            BoundingVolume { radius: 1.5 },
            Identifiable {
                id: format!("{}-star", system.id),
                display_name: system.name.clone(),
                classification: crate::gameplay::galaxy::poi::Classification::Celestial,
            },
            Transform::from_scale(Vec3::splat(1.5)),
        ))
        .set_parent(system_root)
        .id();

    // Spawn all POI children (planets, stations, etc.)
    commands.entity(system_root).with_children(|parent| {
        let ctx = contents::SystemContext {
            id: &system.id,
            name: &system.name,
            faction: system.faction,
            security: system.security,
            is_core: system.is_core,
        };

        contents::spawn_system_contents(
            parent,
            &ctx,
            contents,
            star_entity,
            &content_assets,
            materials,
        );
    });

    // Spawn tactical grid helper for spatial reference
    // Matches the movement demo styling: subtle dark grid
    const GRID_SIZE: f32 = 200.0;
    const GRID_DIVISIONS: usize = 20;
    const GRID_COLOR: Color = Color::srgba(0.05, 0.08, 0.13, 0.5); // #0d1520 with transparency

    // Create grid lines
    let step = GRID_SIZE / GRID_DIVISIONS as f32;
    let half_size = GRID_SIZE * 0.5;

    for i in 0..=GRID_DIVISIONS {
        let offset = (i as f32 * step) - half_size;

        // X-axis line
        commands.spawn((
            Mesh3d(meshes.add(Cuboid::new(GRID_SIZE, 0.02, 0.02))),
            MeshMaterial3d(materials.add(StandardMaterial {
                base_color: GRID_COLOR,
                unlit: true,
                ..default()
            })),
            Transform::from_translation(Vec3::new(0.0, -2.0, offset)),
            InSystemSpawned,
        ));

        // Z-axis line
        commands.spawn((
            Mesh3d(meshes.add(Cuboid::new(0.02, 0.02, GRID_SIZE))),
            MeshMaterial3d(materials.add(StandardMaterial {
                base_color: GRID_COLOR,
                unlit: true,
                ..default()
            })),
            Transform::from_translation(Vec3::new(offset, -2.0, 0.0)),
            InSystemSpawned,
        ));
    }

    // If we have a docking target, spawn player ship docked at it
    let station_entity = if let Some(target) = docking_target {
        // Calculate target position
        let target_position = orbital_position(target.orbit, target.phase);

        // Spawn player ship at docked offset
        spawn_player_ship_docked(
            commands,
            meshes,
            materials,
            &target.name,
            target_position,
            system_root,
        )
    } else {
        Entity::PLACEHOLDER
    };

    (star_entity, station_entity)
}

/// Spawn the player ship docked at a station.
/// Returns the player ship entity.
fn spawn_player_ship_docked(
    commands: &mut Commands,
    meshes: &mut Assets<Mesh>,
    materials: &mut Assets<StandardMaterial>,
    station_name: &str,
    station_position: Vec3,
    system_root: Entity,
) -> Entity {
    // Ship position: docked offset from station
    let ship_position = station_position + DOCKED_OFFSET;

    // Get the first starting ship type (for now, always use the frigate)
    let ship_data = STARTING_SHIPS.first().unwrap_or(&STARTING_SHIPS[0]);

    bevy::log::info!(
        "Spawning player ship: {} docked at {}",
        ship_data.name,
        station_name
    );

    // Create a simple ship visual (using a scaled, colored cube for now)
    let ship_entity = commands
        .spawn((
            PlayerShip,
            Docked {
                station_entity: Entity::PLACEHOLDER, // Will be set when we find the actual station
            },
            // Physical presence: a collider for the collision pipeline and a
            // proximity sensor for spatial queries. `BodyMass` weights
            // ship-to-ship separation; the ship has no `Velocity` while docked
            // so the resolver treats it as immovable until undock.
            BoundingVolume {
                radius: PLAYER_COLLIDER_RADIUS,
            },
            BodyMass(1.0),
            ProximitySensor::new(PLAYER_SCAN_RADIUS),
            Transform::from_translation(ship_position).with_scale(Vec3::splat(0.12)),
            Visibility::default(),
            InheritedVisibility::default(),
            InSystemSpawned,
        ))
        .set_parent(system_root)
        .id();

    // Add mesh and material
    commands.entity(ship_entity).insert((
        Mesh3d(meshes.add(Cuboid::new(1.0, 0.4, 1.5))),
        MeshMaterial3d(materials.add(StandardMaterial {
            base_color: Color::srgb(0.3, 0.5, 0.7),
            emissive: LinearRgba::new(0.1, 0.15, 0.2, 1.0),
            metallic: 0.8,
            perceptual_roughness: 0.3,
            ..default()
        })),
    ));

    ship_entity
}

/// Despawn all entities spawned for the in-system view.
///
/// The [`MainCamera`] is persistent across states (spawned once at [`Startup`])
/// and is intentionally **not** despawned here — scenes reconfigure it.
/// Despawning it would leave the session with no camera, since [`Startup`] does
/// not re-run.
pub fn despawn_in_system_scene(
    mut commands: Commands,
    query: Query<Entity, With<InSystemSpawned>>,
) {
    for entity in &query {
        commands.entity(entity).despawn();
    }
}
