//! Authoritative resources for the galaxy creation scene.
//!
//! [`GalaxyParams`] is the input to procedural generation; the UI mutates it,
//! the regeneration system detects changes via the `generation` counter, and
//! the generator reads it. [`SelectedStar`] tracks the currently-clicked star.
//!
//! ## Structure
//!
//! A galaxy is composed of three structural layer types:
//! - [`CoreParams`] — Concord systems clustered near the origin.
//! - [`DiskParams`] (up to [`MAX_DISKS`]) — independent spiral disks, each
//!   with its own arm count, twist, tilt, rotation, and radial extent.
//! - [`BeamParams`] (×2, top and bottom) — vertical columns of systems along
//!   ±Y, the "polar beams". Each beam is its own population of selectable
//!   star systems, fully equal citizens to disk arms.

use bevy::prelude::*;

// ── Tunable defaults ────────────────────────────────────────────────────────
//
// These mirror the docs prototype (`apps/docs/src/prototypes/existing-demos/GalaxyDemo.tsx`)
// so the Bevy scene opens with the same look as the web demo.

const DEFAULT_SEED: u64 = 4242;
const DEFAULT_SIZE: f32 = 280.0;

// Core defaults: 7 Concord systems inside a 38-unit sphere (matches the old
// hardcoded `[8, 38]` core zone from the single-pass generator).
const DEFAULT_CORE_COUNT: usize = 7;
const DEFAULT_CORE_RADIUS: f32 = 38.0;

// Disk defaults: roughly the previous default (120 total) minus the core,
// keeping the same arms/twist as the docs prototype.
const DEFAULT_DISK_COUNT: usize = 100;
const DEFAULT_DISK_ARMS: u32 = 4;
const DEFAULT_DISK_TWIST: f32 = 12.0; // ~1.9 full turns

// Beam defaults: small population so the two polar columns read clearly
// without dominating the scene. Equal top/bottom for symmetry.
const DEFAULT_BEAM_ENABLED: bool = true;
const DEFAULT_BEAM_COUNT: usize = 30;
const DEFAULT_BEAM_THICKNESS: f32 = 16.0;
const DEFAULT_BEAM_LENGTH: f32 = 180.0;
const DEFAULT_BEAM_TAPER: f32 = 1.5;

// ── Limits ──────────────────────────────────────────────────────────────────

pub const SEED_STEP: u64 = 1;

pub const SIZE_MIN: f32 = 140.0;
pub const SIZE_MAX: f32 = 420.0;
pub const SIZE_STEP: f32 = 10.0;

pub const CORE_COUNT_MIN: usize = 0;
pub const CORE_COUNT_MAX: usize = 30;
pub const CORE_COUNT_STEP: usize = 1;
pub const CORE_RADIUS_MIN: f32 = 10.0;
pub const CORE_RADIUS_MAX: f32 = 120.0;
pub const CORE_RADIUS_STEP: f32 = 5.0;

/// Maximum number of independent disk layers.
pub const MAX_DISKS: usize = 4;

pub const DISK_COUNT_MIN: usize = 20;
pub const DISK_COUNT_MAX: usize = 220;
pub const DISK_COUNT_STEP: usize = 4;
pub const DISK_ARMS_MIN: u32 = 1;
pub const DISK_ARMS_MAX: u32 = 6;
pub const DISK_TWIST_MIN: f32 = 4.0; // ~0.6 turns — bare minimum curvature
pub const DISK_TWIST_MAX: f32 = 18.0; // ~2.9 turns — tight logarithmic spiral
pub const DISK_TWIST_STEP: f32 = 0.4;

/// Max tilt is 45° — disks should never be parallel to beams.
pub const DISK_TILT_MIN: f32 = 0.0;
pub const DISK_TILT_MAX: f32 = std::f32::consts::FRAC_PI_4;
/// ~5° steps (45° / 9).
pub const DISK_TILT_STEP: f32 = std::f32::consts::FRAC_PI_4 / 9.0;

pub const DISK_ROTATION_MIN: f32 = 0.0;
pub const DISK_ROTATION_MAX: f32 = std::f32::consts::TAU;
/// 30° steps.
pub const DISK_ROTATION_STEP: f32 = std::f32::consts::TAU / 12.0;

pub const BEAM_COUNT_MIN: usize = 0;
pub const BEAM_COUNT_MAX: usize = 120;
pub const BEAM_COUNT_STEP: usize = 4;
pub const BEAM_THICKNESS_MIN: f32 = 1.0;
pub const BEAM_THICKNESS_MAX: f32 = 80.0;
pub const BEAM_THICKNESS_STEP: f32 = 1.0;
pub const BEAM_LENGTH_MIN: f32 = 20.0;
pub const BEAM_LENGTH_MAX: f32 = 600.0;
pub const BEAM_LENGTH_STEP: f32 = 20.0;
pub const BEAM_TAPER_MIN: f32 = 0.3;
pub const BEAM_TAPER_MAX: f32 = 4.0;
pub const BEAM_TAPER_STEP: f32 = 0.1;

pub const BEAM_GRAVITY_MIN: f32 = 0.5;
pub const BEAM_GRAVITY_MAX: f32 = 4.0;
pub const BEAM_GRAVITY_STEP: f32 = 0.1;
const DEFAULT_BEAM_GRAVITY: f32 = 1.5;

/// Connect each system to its N nearest neighbors (deduplicated).
/// Docs prototype uses 2; kept constant until a UI control is added.
pub(crate) const NEAREST_NEIGHBOR_CONNECTIONS: usize = 2;

/// Max attempts to find a clear spot for each system (Poisson-style spacing).
pub(crate) const SPACING_ATTEMPTS: usize = 180;

/// Minimum distance between any two star systems, regardless of relaxation.
/// Should be at least 2× the star visual radius (~1.0) so stars never visually
/// overlap. Applied as a floor to the relaxed spacing in all layers.
pub(crate) const MIN_SYSTEM_SPACING: f32 = 2.0;

// ── Sub-structs ─────────────────────────────────────────────────────────────

/// Concord core — densely packed Concord-faction systems at the origin.
#[derive(Debug, Clone)]
pub struct CoreParams {
    pub count: usize,
    /// Maximum distance from the origin for core systems.
    pub radius: f32,
}

impl Default for CoreParams {
    fn default() -> Self {
        Self {
            count: DEFAULT_CORE_COUNT,
            radius: DEFAULT_CORE_RADIUS,
        }
    }
}

/// One independent disk layer of spiral arms.
///
/// Disks are generated in the XZ plane with systems sweeping continuously
/// from the origin to `galaxy_size` (no inner/outer radius gap), then
/// tilted by [`Self::tilt`] (rotation around X) and rotated by
/// [`Self::rotation_offset`] (around Y). The `powf(0.62)` density bias
/// packs systems near the origin so arms flow naturally from the dense
/// core cluster outward.
#[derive(Debug, Clone)]
pub struct DiskParams {
    pub count: usize,
    pub arms: u32,
    pub twist: f32,
    /// Tilt angle in radians (rotation around X axis).
    /// Capped at 45° so disks never become parallel to beams.
    /// 0 = flat in XZ plane, π/4 = maximum tilt.
    pub tilt: f32,
    /// Rotation offset around Y axis in radians.
    /// Rotates the disk in the horizontal plane, determining where the
    /// tilted disk's "high side" points.
    pub rotation_offset: f32,
}

impl Default for DiskParams {
    fn default() -> Self {
        Self {
            count: DEFAULT_DISK_COUNT,
            arms: DEFAULT_DISK_ARMS,
            twist: DEFAULT_DISK_TWIST,
            tilt: 0.0,
            rotation_offset: 0.0,
        }
    }
}

/// One of the two polar beams (top = +Y, bottom = −Y). Beam systems are
/// distributed in a tapered cone along the beam axis; they are fully
/// selectable star systems equal to disk arms.
///
/// The cross-section at any height `t` (0 = base, 1 = tip) has radius
/// `thickness × (1 − t)^taper`, producing a Vercidium-style diffraction
/// spike: bright and concentrated near the galactic core, thinning to
/// nothing at the tips.
#[derive(Debug, Clone)]
pub struct BeamParams {
    pub enabled: bool,
    /// Number of star systems to spawn in this beam. UI label: "Density".
    pub count: usize,
    /// Maximum cylinder radius at the base (t=0). The actual radius tapers
    /// toward the tip according to `taper`.
    pub thickness: f32,
    /// Extent along the beam axis (one-sided; total beam length is 2 × length
    /// because there are two beams).
    pub length: f32,
    /// Shape exponent controlling how the beam tapers.
    ///   < 1.0 → wide bulbous beam, barely narrows
    ///   = 1.0 → linear double-cone
    ///   = 1.5 → moderate spike (Vercidium default look)
    ///   > 2.0 → sharp needle
    /// The cross-section radius at height `t` is `thickness × (1-t)^taper`.
    pub taper: f32,
    /// Radial density exponent for the beam cross-section.
    /// Higher values concentrate systems toward the Y axis (tighter, brighter
    /// core); lower values spread them out (more diffuse beam).
    ///   1.0 = uniform disk sampling
    ///   1.5 = moderate concentration (default)
    ///   3.0+ = very tight, needle-like core
    pub gravity: f32,
}

impl Default for BeamParams {
    fn default() -> Self {
        Self {
            enabled: DEFAULT_BEAM_ENABLED,
            count: DEFAULT_BEAM_COUNT,
            thickness: DEFAULT_BEAM_THICKNESS,
            length: DEFAULT_BEAM_LENGTH,
            taper: DEFAULT_BEAM_TAPER,
            gravity: DEFAULT_BEAM_GRAVITY,
        }
    }
}

/// Procedural galaxy parameters. UI mutates fields, then bumps `generation`
/// to signal the regeneration system to rebuild the scene.
#[derive(Resource, Debug, Clone)]
pub struct GalaxyParams {
    pub seed: u64,
    /// Overall galaxy extent — drives the disk radius.
    pub size: f32,
    pub core: CoreParams,
    /// Independent disk layers. Always contains at least one entry.
    pub disks: Vec<DiskParams>,
    pub beam_top: BeamParams,
    pub beam_bottom: BeamParams,
    /// Monotonic counter — any mutation must bump this via [`Self::bump_generation`].
    /// The regeneration system triggers a rebuild whenever this differs from its
    /// last-seen value.
    pub generation: u64,
}

impl Default for GalaxyParams {
    fn default() -> Self {
        Self {
            seed: DEFAULT_SEED,
            size: DEFAULT_SIZE,
            core: CoreParams::default(),
            disks: vec![DiskParams::default()],
            beam_top: BeamParams::default(),
            beam_bottom: BeamParams::default(),
            generation: 0,
        }
    }
}

impl GalaxyParams {
    /// Bump the generation counter, signalling the regeneration system.
    /// Call after any field mutation that should trigger a scene rebuild.
    pub fn bump_generation(&mut self) {
        self.generation = self.generation.wrapping_add(1);
    }

    /// Convenience for the "Regenerate" button: bump the seed and mark dirty.
    pub fn reseed_and_bump(&mut self) {
        self.seed = self.seed.wrapping_add(1);
        self.bump_generation();
    }
}

/// Currently selected star system entity in the galaxy view.
/// `None` means no selection.
#[derive(Resource, Default, Debug, Clone, Copy)]
pub struct SelectedStar(pub Option<Entity>);
