//! Built-in visual-effect presets.
//!
//! [`spawn_vfx`] composes the engine's particle, line, and mesh renderers into
//! ready-made effects and returns a [`VfxHandle`] that [`despawn_vfx`] tears
//! down. Each preset is a tuned composition of emitters, beams, animated
//! meshes, and lights.

use crate::ecs::generational_registry::registry_entry_by_name_mut;
use crate::ecs::light::components::{Light, LightType};
use crate::ecs::material::components::AlphaMode;
use crate::ecs::mesh::commands::spawn_mesh;
use crate::ecs::particles::components::{
    ColorGradient, EmitterShape, EmitterType, ParticleEmitter,
};
use crate::ecs::transform::commands::mark_local_transform_dirty;
use crate::ecs::vfx::components::{Beam, LightningBolt, Trail, VfxAnimator, VfxHandle};
use crate::ecs::vfx::textures::{FLARE_SLOT, GLOW_SLOT, SMOKE_SLOT};
use crate::ecs::world::commands::{
    EcsCommand, queue_ecs_command, setup_entity_transforms, spawn_entities, spawn_light_entity,
};
use crate::ecs::world::components::{Lines, LocalTransform, Visibility};
use crate::ecs::world::{
    BEAM, GLOBAL_TRANSFORM, LIGHTNING_BOLT, LINES, LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY,
    PARTICLE_EMITTER, Quat, TRAIL, VFX_ANIMATOR, VISIBILITY, World,
};
use freecs::Entity;
use nalgebra_glm::{Vec3, vec3, vec4};
use std::f32::consts::{PI, TAU};

/// The family a [`VfxPreset`] belongs to, used to group effects in tooling.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VfxFamily {
    Particles,
    TrailsAndBeams,
    MeshEffects,
    Showpieces,
}

impl VfxFamily {
    pub fn label(self) -> &'static str {
        match self {
            VfxFamily::Particles => "Particles",
            VfxFamily::TrailsAndBeams => "Trails & Beams",
            VfxFamily::MeshEffects => "Mesh Effects",
            VfxFamily::Showpieces => "Showpieces",
        }
    }
}

/// A named, ready-to-spawn visual effect.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VfxPreset {
    Bonfire,
    Explosion,
    MagicAura,
    Fireworks,
    Blizzard,
    Flamethrower,
    Fireflies,
    Waterfall,
    Laser,
    EnergyBeam,
    Lightning,
    ChainLightning,
    Comet,
    Shockwave,
    ForceField,
    Portal,
    TeslaCoil,
    RuneCircle,
    EnergyCore,
    Supernova,
    LightningStorm,
    Singularity,
    Orrery,
}

impl VfxPreset {
    /// Every built-in preset, in display order.
    pub const ALL: [VfxPreset; 23] = [
        VfxPreset::Bonfire,
        VfxPreset::Explosion,
        VfxPreset::MagicAura,
        VfxPreset::Fireworks,
        VfxPreset::Blizzard,
        VfxPreset::Flamethrower,
        VfxPreset::Fireflies,
        VfxPreset::Waterfall,
        VfxPreset::Laser,
        VfxPreset::EnergyBeam,
        VfxPreset::Lightning,
        VfxPreset::ChainLightning,
        VfxPreset::Comet,
        VfxPreset::Shockwave,
        VfxPreset::ForceField,
        VfxPreset::Portal,
        VfxPreset::TeslaCoil,
        VfxPreset::RuneCircle,
        VfxPreset::EnergyCore,
        VfxPreset::Supernova,
        VfxPreset::LightningStorm,
        VfxPreset::Singularity,
        VfxPreset::Orrery,
    ];

    pub fn label(self) -> &'static str {
        match self {
            VfxPreset::Bonfire => "Bonfire",
            VfxPreset::Explosion => "Explosion",
            VfxPreset::MagicAura => "Magic Aura",
            VfxPreset::Fireworks => "Fireworks",
            VfxPreset::Blizzard => "Blizzard",
            VfxPreset::Flamethrower => "Flamethrower",
            VfxPreset::Fireflies => "Fireflies",
            VfxPreset::Waterfall => "Waterfall",
            VfxPreset::Laser => "Laser",
            VfxPreset::EnergyBeam => "Energy Beam",
            VfxPreset::Lightning => "Lightning",
            VfxPreset::ChainLightning => "Chain Lightning",
            VfxPreset::Comet => "Comet",
            VfxPreset::Shockwave => "Shockwave",
            VfxPreset::ForceField => "Force Field",
            VfxPreset::Portal => "Portal",
            VfxPreset::TeslaCoil => "Tesla Coil",
            VfxPreset::RuneCircle => "Rune Circle",
            VfxPreset::EnergyCore => "Energy Core",
            VfxPreset::Supernova => "Supernova",
            VfxPreset::LightningStorm => "Lightning Storm",
            VfxPreset::Singularity => "Singularity",
            VfxPreset::Orrery => "Orrery",
        }
    }

    pub fn family(self) -> VfxFamily {
        match self {
            VfxPreset::Bonfire
            | VfxPreset::Explosion
            | VfxPreset::MagicAura
            | VfxPreset::Fireworks
            | VfxPreset::Blizzard
            | VfxPreset::Flamethrower
            | VfxPreset::Fireflies
            | VfxPreset::Waterfall => VfxFamily::Particles,
            VfxPreset::Laser
            | VfxPreset::EnergyBeam
            | VfxPreset::Lightning
            | VfxPreset::ChainLightning
            | VfxPreset::Comet => VfxFamily::TrailsAndBeams,
            VfxPreset::Shockwave | VfxPreset::ForceField | VfxPreset::Portal => {
                VfxFamily::MeshEffects
            }
            VfxPreset::TeslaCoil
            | VfxPreset::RuneCircle
            | VfxPreset::EnergyCore
            | VfxPreset::Supernova
            | VfxPreset::LightningStorm
            | VfxPreset::Singularity
            | VfxPreset::Orrery => VfxFamily::Showpieces,
        }
    }
}

/// Spawns a built-in effect at `position`, returning the entities it created.
pub fn spawn_vfx(world: &mut World, preset: VfxPreset, position: Vec3) -> VfxHandle {
    match preset {
        VfxPreset::Bonfire => bonfire(world, position),
        VfxPreset::Explosion => explosion(world, position),
        VfxPreset::MagicAura => magic_aura(world, position),
        VfxPreset::Fireworks => fireworks(world, position),
        VfxPreset::Blizzard => single(spawn_emitter(
            world,
            textured(blizzard_snow(position), GLOW_SLOT),
        )),
        VfxPreset::Flamethrower => flamethrower(world, position),
        VfxPreset::Fireflies => single(spawn_emitter(
            world,
            textured(fireflies(position), GLOW_SLOT),
        )),
        VfxPreset::Waterfall => waterfall(world, position),
        VfxPreset::Laser => laser(world, position),
        VfxPreset::EnergyBeam => single(spawn_beam(world, energy_beam(position))),
        VfxPreset::Lightning => strike(world, position, false),
        VfxPreset::ChainLightning => strike(world, position, true),
        VfxPreset::Comet => single(spawn_trail(world, comet_trail(position))),
        VfxPreset::Shockwave => shockwave(world, position),
        VfxPreset::ForceField => force_field(world, position),
        VfxPreset::Portal => portal(world, position),
        VfxPreset::TeslaCoil => tesla_coil(world, position),
        VfxPreset::RuneCircle => rune_circle(world, position),
        VfxPreset::EnergyCore => energy_core(world, position),
        VfxPreset::Supernova => supernova(world, position),
        VfxPreset::LightningStorm => lightning_storm(world, position),
        VfxPreset::Singularity => singularity(world, position),
        VfxPreset::Orrery => orrery(world, position),
    }
}

/// Queues recursive despawn of every entity in `handle`.
pub fn despawn_vfx(world: &mut World, handle: &VfxHandle) {
    for &entity in &handle.entities {
        queue_ecs_command(world, EcsCommand::DespawnRecursive { entity });
    }
}

fn single(entity: Entity) -> VfxHandle {
    VfxHandle {
        entities: vec![entity],
    }
}

fn handle(entities: Vec<Entity>) -> VfxHandle {
    VfxHandle { entities }
}

// --- composite presets -----------------------------------------------------

fn bonfire(world: &mut World, position: Vec3) -> VfxHandle {
    handle(vec![
        spawn_emitter(world, textured(bonfire_flames(position), GLOW_SLOT)),
        spawn_emitter(world, textured(embers(position), FLARE_SLOT)),
        spawn_emitter(
            world,
            textured(
                ParticleEmitter {
                    spawn_rate: 26.0,
                    ..ParticleEmitter::smoke(position + vec3(0.0, 0.6, 0.0))
                },
                SMOKE_SLOT,
            ),
        ),
        spawn_point_light(
            world,
            position + vec3(0.0, 1.0, 0.0),
            vec3(1.0, 0.5, 0.2),
            55.0,
            14.0,
        ),
    ])
}

fn explosion(world: &mut World, position: Vec3) -> VfxHandle {
    let center = position + vec3(0.0, 1.3, 0.0);
    handle(vec![
        spawn_emitter(
            world,
            textured(
                ParticleEmitter::firework_explosion(center, vec3(1.0, 0.45, 0.12), 500),
                GLOW_SLOT,
            ),
        ),
        spawn_emitter(
            world,
            textured(ParticleEmitter::flash_burst(center), GLOW_SLOT),
        ),
        spawn_emitter(world, textured(debris_burst(center), FLARE_SLOT)),
        spawn_shockwave_ring(world, position),
        spawn_emitter(world, textured(ground_dust(position), SMOKE_SLOT)),
    ])
}

fn magic_aura(world: &mut World, position: Vec3) -> VfxHandle {
    let center = position + vec3(0.0, 1.1, 0.0);
    handle(vec![
        spawn_emitter(world, textured(magic_motes(center), FLARE_SLOT)),
        spawn_mesh_effect(
            world,
            "Sphere",
            center,
            Quat::identity(),
            true,
            0.5,
            VfxAnimator {
                lifetime: 0.0,
                start_scale: vec3(0.35, 0.35, 0.35),
                end_scale: vec3(0.35, 0.35, 0.35),
                peak_color: vec3(0.8, 0.45, 1.7),
                emissive_strength: 3.0,
                base_alpha: 0.85,
                pulse_amplitude: 0.3,
                pulse_frequency: 1.6,
                ..Default::default()
            },
        ),
        spawn_point_light(world, center, vec3(0.6, 0.3, 1.2), 35.0, 11.0),
    ])
}

fn fireworks(world: &mut World, position: Vec3) -> VfxHandle {
    handle(vec![
        spawn_emitter(
            world,
            textured(
                ParticleEmitter::firework_chrysanthemum(
                    position + vec3(-2.0, 4.5, 0.0),
                    vec3(1.0, 0.3, 0.3),
                    360,
                ),
                GLOW_SLOT,
            ),
        ),
        spawn_emitter(
            world,
            textured(
                ParticleEmitter::firework_willow(
                    position + vec3(2.2, 3.6, 1.0),
                    vec3(0.4, 0.7, 1.0),
                    320,
                ),
                GLOW_SLOT,
            ),
        ),
        spawn_emitter(
            world,
            textured(
                ParticleEmitter::firework_glitter(position + vec3(0.2, 5.2, -1.0), 260),
                FLARE_SLOT,
            ),
        ),
    ])
}

fn laser(world: &mut World, position: Vec3) -> VfxHandle {
    handle(vec![
        spawn_beam(world, laser_beam(position)),
        spawn_emitter(
            world,
            textured(impact_sparks(position + vec3(3.5, 1.2, 0.0)), FLARE_SLOT),
        ),
    ])
}

fn strike(world: &mut World, position: Vec3, chain: bool) -> VfxHandle {
    handle(vec![
        spawn_lightning(world, lightning_bolt(position, chain)),
        spawn_emitter(
            world,
            textured(impact_sparks(position + vec3(0.0, 0.05, 0.0)), FLARE_SLOT),
        ),
    ])
}

fn shockwave(world: &mut World, position: Vec3) -> VfxHandle {
    handle(vec![
        spawn_shockwave_ring(world, position),
        spawn_emitter(world, textured(ground_dust(position), SMOKE_SLOT)),
        spawn_emitter(
            world,
            textured(debris_burst(position + vec3(0.0, 0.3, 0.0)), FLARE_SLOT),
        ),
    ])
}

fn force_field(world: &mut World, position: Vec3) -> VfxHandle {
    let center = position + vec3(0.0, 1.3, 0.0);
    handle(vec![
        spawn_mesh_effect(
            world,
            "Sphere",
            center,
            Quat::identity(),
            false,
            0.08,
            VfxAnimator {
                lifetime: 0.0,
                start_scale: vec3(1.7, 1.7, 1.7),
                end_scale: vec3(1.7, 1.7, 1.7),
                peak_color: vec3(0.3, 0.75, 1.3),
                emissive_strength: 3.2,
                base_alpha: 0.34,
                pulse_amplitude: 0.35,
                pulse_frequency: 1.2,
                ..Default::default()
            },
        ),
        spawn_emitter(world, textured(shield_sparks(center), FLARE_SLOT)),
    ])
}

fn portal(world: &mut World, position: Vec3) -> VfxHandle {
    let center = position + vec3(0.0, 1.7, 0.0);
    handle(vec![
        spawn_mesh_effect(
            world,
            "Torus",
            center,
            nalgebra_glm::quat_angle_axis(PI / 2.0, &vec3(1.0, 0.0, 0.0)),
            true,
            0.5,
            VfxAnimator {
                lifetime: 0.0,
                start_scale: vec3(1.7, 1.7, 1.7),
                end_scale: vec3(1.7, 1.7, 1.7),
                peak_color: vec3(1.4, 0.4, 1.8),
                emissive_strength: 2.4,
                base_alpha: 0.95,
                pulse_amplitude: 0.15,
                pulse_frequency: 2.0,
                ..Default::default()
            },
        ),
        spawn_emitter(world, textured(portal_swirl(center), FLARE_SLOT)),
        spawn_point_light(world, center, vec3(1.0, 0.3, 1.4), 38.0, 11.0),
    ])
}

// --- spawn helpers ---------------------------------------------------------

fn identity_transform() -> LocalTransform {
    LocalTransform {
        translation: Vec3::zeros(),
        scale: Vec3::new(1.0, 1.0, 1.0),
        rotation: Quat::identity(),
    }
}

fn textured(mut emitter: ParticleEmitter, slot: u32) -> ParticleEmitter {
    emitter.texture_index = slot;
    emitter
}

fn spawn_emitter(world: &mut World, emitter: ParticleEmitter) -> Entity {
    let entity = spawn_entities(
        world,
        PARTICLE_EMITTER | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY,
        1,
    )[0];
    setup_entity_transforms(world, entity, identity_transform());
    world.core.set_particle_emitter(entity, emitter);
    entity
}

fn spawn_point_light(
    world: &mut World,
    position: Vec3,
    color: Vec3,
    intensity: f32,
    range: f32,
) -> Entity {
    let entity = spawn_light_entity(world, position, "vfx_light");
    world.core.set_light(
        entity,
        Light {
            light_type: LightType::Point,
            color,
            intensity,
            range,
            ..Default::default()
        },
    );
    entity
}

fn spawn_line_entity(world: &mut World, effect_mask: u64) -> Entity {
    let entity = spawn_entities(
        world,
        effect_mask
            | LINES
            | LOCAL_TRANSFORM
            | GLOBAL_TRANSFORM
            | LOCAL_TRANSFORM_DIRTY
            | VISIBILITY,
        1,
    )[0];
    setup_entity_transforms(world, entity, identity_transform());
    world.core.set_lines(entity, Lines::default());
    world.core.set_visibility(entity, Visibility::default());
    entity
}

fn spawn_beam(world: &mut World, beam: Beam) -> Entity {
    let entity = spawn_line_entity(world, BEAM);
    world.core.set_beam(entity, beam);
    entity
}

fn spawn_lightning(world: &mut World, bolt: LightningBolt) -> Entity {
    let entity = spawn_line_entity(world, LIGHTNING_BOLT);
    world.core.set_lightning_bolt(entity, bolt);
    entity
}

fn spawn_trail(world: &mut World, trail: Trail) -> Entity {
    let entity = spawn_line_entity(world, TRAIL);
    world.core.set_trail(entity, trail);
    entity
}

fn spawn_mesh_effect(
    world: &mut World,
    mesh: &str,
    position: Vec3,
    rotation: Quat,
    unlit: bool,
    roughness: f32,
    animator: VfxAnimator,
) -> Entity {
    let entity = spawn_mesh(world, mesh, position, animator.start_scale);
    if let Some(transform) = world.core.get_local_transform_mut(entity) {
        transform.rotation = rotation;
    }
    mark_local_transform_dirty(world, entity);
    world.core.add_components(entity, VFX_ANIMATOR);
    world.core.set_vfx_animator(entity, animator);

    let material_name = world
        .core
        .get_material_ref(entity)
        .map(|material_ref| material_ref.name.clone());
    if let Some(name) = material_name {
        if let Some(material) = registry_entry_by_name_mut(
            &mut world.resources.assets.material_registry.registry,
            &name,
        ) {
            let color = animator.peak_color;
            material.unlit = unlit;
            material.double_sided = true;
            material.alpha_mode = AlphaMode::Blend;
            material.roughness = roughness;
            material.metallic = 0.0;
            material.base_color = [color.x, color.y, color.z, animator.base_alpha];
            material.emissive_factor = [color.x, color.y, color.z];
            material.emissive_strength = animator.emissive_strength;
        }
        world
            .resources
            .mesh_render_state
            .mark_material_dirty(entity);
    }
    entity
}

fn spawn_shockwave_ring(world: &mut World, position: Vec3) -> Entity {
    spawn_mesh_effect(
        world,
        "Torus",
        position + vec3(0.0, 0.25, 0.0),
        Quat::identity(),
        true,
        0.5,
        VfxAnimator {
            lifetime: 0.9,
            despawn_on_end: true,
            start_scale: vec3(0.4, 0.18, 0.4),
            end_scale: vec3(5.5, 0.18, 5.5),
            peak_color: vec3(1.6, 1.1, 0.5),
            emissive_strength: 2.2,
            base_alpha: 1.0,
            fade_alpha: true,
            ..Default::default()
        },
    )
}

// --- emitter recipes -------------------------------------------------------

fn bonfire_flames(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        spawn_rate: 380.0,
        size_start: 0.5,
        size_end: 0.14,
        particle_lifetime_min: 0.5,
        particle_lifetime_max: 1.5,
        initial_velocity_min: 2.2,
        initial_velocity_max: 5.0,
        emissive_strength: 4.0,
        ..ParticleEmitter::fire(position)
    }
}

fn embers(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Sparks,
        shape: EmitterShape::Sphere { radius: 0.4 },
        position,
        direction: vec3(0.0, 1.0, 0.0),
        spawn_rate: 60.0,
        particle_lifetime_min: 1.0,
        particle_lifetime_max: 2.4,
        initial_velocity_min: 0.6,
        initial_velocity_max: 1.8,
        velocity_spread: 0.8,
        gravity: vec3(0.0, 1.2, 0.0),
        drag: 0.2,
        size_start: 0.07,
        size_end: 0.01,
        color_gradient: ColorGradient::sparks(),
        emissive_strength: 7.0,
        turbulence_strength: 1.0,
        turbulence_frequency: 1.5,
        ..Default::default()
    }
}

fn magic_motes(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Fire,
        shape: EmitterShape::Sphere { radius: 0.7 },
        position,
        direction: vec3(0.0, 1.0, 0.0),
        spawn_rate: 170.0,
        particle_lifetime_min: 1.4,
        particle_lifetime_max: 2.8,
        initial_velocity_min: 0.2,
        initial_velocity_max: 0.8,
        velocity_spread: 1.4,
        gravity: vec3(0.0, 0.3, 0.0),
        drag: 0.4,
        size_start: 0.14,
        size_end: 0.02,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(0.9, 0.6, 1.0, 1.0)),
                (0.4, vec4(0.55, 0.35, 1.0, 0.9)),
                (0.75, vec4(0.3, 0.7, 1.0, 0.6)),
                (1.0, vec4(0.2, 0.3, 0.7, 0.0)),
            ],
        },
        emissive_strength: 6.0,
        turbulence_strength: 2.8,
        turbulence_frequency: 1.8,
        ..Default::default()
    }
}

fn debris_burst(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Sparks,
        shape: EmitterShape::Sphere { radius: 0.2 },
        position,
        direction: vec3(0.0, 1.0, 0.0),
        spawn_rate: 0.0,
        burst_count: 160,
        particle_lifetime_min: 0.4,
        particle_lifetime_max: 1.0,
        initial_velocity_min: 8.0,
        initial_velocity_max: 18.0,
        velocity_spread: PI,
        gravity: vec3(0.0, -14.0, 0.0),
        drag: 0.06,
        size_start: 0.08,
        size_end: 0.01,
        color_gradient: ColorGradient::sparks(),
        emissive_strength: 12.0,
        one_shot: true,
        turbulence_strength: 0.6,
        turbulence_frequency: 3.0,
        ..Default::default()
    }
}

fn impact_sparks(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Sparks,
        shape: EmitterShape::Point,
        position,
        direction: vec3(0.0, 1.0, 0.0),
        spawn_rate: 120.0,
        particle_lifetime_min: 0.2,
        particle_lifetime_max: 0.6,
        initial_velocity_min: 2.0,
        initial_velocity_max: 6.0,
        velocity_spread: PI,
        gravity: vec3(0.0, -10.0, 0.0),
        drag: 0.05,
        size_start: 0.05,
        size_end: 0.01,
        color_gradient: ColorGradient::sparks(),
        emissive_strength: 10.0,
        ..Default::default()
    }
}

fn ground_dust(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Smoke,
        shape: EmitterShape::Sphere { radius: 0.3 },
        position: position + vec3(0.0, 0.1, 0.0),
        direction: vec3(0.0, 1.0, 0.0),
        spawn_rate: 0.0,
        burst_count: 60,
        particle_lifetime_min: 0.6,
        particle_lifetime_max: 1.2,
        initial_velocity_min: 3.0,
        initial_velocity_max: 6.0,
        velocity_spread: 1.4,
        gravity: vec3(0.0, -1.0, 0.0),
        drag: 0.5,
        size_start: 0.3,
        size_end: 1.0,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(0.6, 0.55, 0.5, 0.0)),
                (0.2, vec4(0.65, 0.6, 0.55, 0.5)),
                (1.0, vec4(0.7, 0.65, 0.6, 0.0)),
            ],
        },
        emissive_strength: 0.0,
        one_shot: true,
        ..Default::default()
    }
}

fn blizzard_snow(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Smoke,
        shape: EmitterShape::Box {
            half_extents: vec3(5.0, 0.1, 5.0),
        },
        position: position + vec3(0.0, 6.0, 0.0),
        direction: vec3(0.3, -1.0, 0.1),
        spawn_rate: 360.0,
        particle_lifetime_min: 2.5,
        particle_lifetime_max: 4.0,
        initial_velocity_min: 1.5,
        initial_velocity_max: 3.0,
        velocity_spread: 0.3,
        gravity: vec3(0.6, -1.2, 0.2),
        drag: 0.05,
        size_start: 0.06,
        size_end: 0.06,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(1.0, 1.0, 1.0, 0.0)),
                (0.1, vec4(1.0, 1.0, 1.0, 0.95)),
                (0.9, vec4(0.95, 0.97, 1.0, 0.95)),
                (1.0, vec4(0.9, 0.95, 1.0, 0.0)),
            ],
        },
        emissive_strength: 0.3,
        turbulence_strength: 0.8,
        turbulence_frequency: 0.5,
        ..Default::default()
    }
}

fn shield_sparks(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Sparks,
        shape: EmitterShape::Sphere { radius: 1.7 },
        position,
        direction: vec3(0.0, 1.0, 0.0),
        spawn_rate: 50.0,
        particle_lifetime_min: 0.4,
        particle_lifetime_max: 1.0,
        initial_velocity_min: 0.1,
        initial_velocity_max: 0.5,
        velocity_spread: PI,
        gravity: vec3(0.0, 0.0, 0.0),
        drag: 0.4,
        size_start: 0.05,
        size_end: 0.01,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(0.5, 0.9, 1.0, 1.0)),
                (0.5, vec4(0.3, 0.7, 1.0, 0.8)),
                (1.0, vec4(0.2, 0.5, 0.9, 0.0)),
            ],
        },
        emissive_strength: 8.0,
        ..Default::default()
    }
}

fn portal_swirl(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Fire,
        shape: EmitterShape::Sphere { radius: 1.2 },
        position,
        direction: vec3(0.0, 0.0, 1.0),
        spawn_rate: 150.0,
        particle_lifetime_min: 0.8,
        particle_lifetime_max: 1.6,
        initial_velocity_min: 0.1,
        initial_velocity_max: 0.6,
        velocity_spread: PI,
        gravity: vec3(0.0, 0.0, 0.0),
        drag: 0.5,
        size_start: 0.12,
        size_end: 0.02,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(1.0, 0.5, 1.0, 1.0)),
                (0.5, vec4(0.7, 0.3, 1.0, 0.8)),
                (1.0, vec4(0.4, 0.2, 0.8, 0.0)),
            ],
        },
        emissive_strength: 7.0,
        turbulence_strength: 3.0,
        turbulence_frequency: 2.5,
        ..Default::default()
    }
}

// --- line and trail recipes ------------------------------------------------

fn laser_beam(position: Vec3) -> Beam {
    Beam {
        start: position + vec3(-3.5, 1.2, 0.0),
        end: position + vec3(3.5, 1.2, 0.0),
        color: vec3(3.4, 0.3, 0.3),
        intensity: 4.0,
        alpha: 1.0,
        width: 0.05,
        strands: 3,
        flicker: 0.1,
        flicker_speed: 45.0,
        age: 0.0,
    }
}

fn energy_beam(position: Vec3) -> Beam {
    Beam {
        start: position + vec3(0.0, 0.1, 0.0),
        end: position + vec3(0.0, 5.5, 0.0),
        color: vec3(0.5, 1.3, 2.8),
        intensity: 3.2,
        alpha: 0.9,
        width: 0.22,
        strands: 8,
        flicker: 0.3,
        flicker_speed: 16.0,
        age: 0.0,
    }
}

fn lightning_bolt(position: Vec3, chain: bool) -> LightningBolt {
    LightningBolt {
        start: position + vec3(0.0, 5.5, 0.0),
        end: position + vec3(0.0, 0.05, 0.0),
        color: vec3(1.6, 1.9, 3.4),
        intensity: 5.5,
        alpha: 1.0,
        segments: 24,
        jaggedness: if chain { 0.7 } else { 0.45 },
        branch_count: if chain { 10 } else { 3 },
        branch_spread: 0.9,
        regen_interval: 0.045,
        timer: 0.0,
        seed: 0x9E37_79B9,
    }
}

fn comet_trail(position: Vec3) -> Trail {
    Trail {
        center: position + vec3(0.0, 1.8, 0.0),
        radius: 2.6,
        axis: vec3(0.25, 1.0, 0.0),
        speed: 2.8,
        color: vec3(2.8, 1.5, 0.5),
        intensity: 1.6,
        alpha: 1.0,
        max_points: 80,
        points: Vec::new(),
        age: 0.0,
    }
}

// --- additional composite presets ------------------------------------------

fn flamethrower(world: &mut World, position: Vec3) -> VfxHandle {
    let nozzle = position + vec3(-1.8, 1.2, 0.0);
    handle(vec![
        spawn_emitter(world, textured(flame_jet(nozzle), GLOW_SLOT)),
        spawn_emitter(
            world,
            textured(
                ParticleEmitter {
                    emitter_type: EmitterType::Smoke,
                    shape: EmitterShape::Cone {
                        angle: 0.3,
                        height: 0.2,
                    },
                    position: nozzle,
                    direction: vec3(1.0, 0.4, 0.0),
                    spawn_rate: 55.0,
                    particle_lifetime_min: 1.0,
                    particle_lifetime_max: 2.0,
                    initial_velocity_min: 3.0,
                    initial_velocity_max: 6.0,
                    velocity_spread: 0.4,
                    gravity: vec3(0.0, 1.0, 0.0),
                    drag: 0.5,
                    size_start: 0.4,
                    size_end: 1.3,
                    color_gradient: ColorGradient::smoke(),
                    emissive_strength: 0.0,
                    turbulence_strength: 1.0,
                    turbulence_frequency: 0.6,
                    ..Default::default()
                },
                SMOKE_SLOT,
            ),
        ),
        spawn_point_light(
            world,
            position + vec3(0.5, 1.2, 0.0),
            vec3(1.0, 0.5, 0.2),
            45.0,
            12.0,
        ),
    ])
}

fn waterfall(world: &mut World, position: Vec3) -> VfxHandle {
    handle(vec![
        spawn_emitter(
            world,
            textured(waterfall_water(position + vec3(0.0, 5.0, -0.5)), GLOW_SLOT),
        ),
        spawn_emitter(
            world,
            textured(waterfall_mist(position + vec3(0.0, 0.3, -0.5)), SMOKE_SLOT),
        ),
    ])
}

fn tesla_coil(world: &mut World, position: Vec3) -> VfxHandle {
    let center = position + vec3(0.0, 1.8, 0.0);
    let mut entities = vec![
        spawn_mesh_effect(
            world,
            "Sphere",
            center,
            Quat::identity(),
            true,
            0.5,
            VfxAnimator {
                lifetime: 0.0,
                start_scale: vec3(0.3, 0.3, 0.3),
                end_scale: vec3(0.3, 0.3, 0.3),
                peak_color: vec3(0.6, 0.8, 2.0),
                emissive_strength: 4.0,
                base_alpha: 0.95,
                pulse_amplitude: 0.3,
                pulse_frequency: 6.0,
                ..Default::default()
            },
        ),
        spawn_emitter(world, textured(core_sparks(center), FLARE_SLOT)),
        spawn_point_light(world, center, vec3(0.5, 0.7, 1.6), 45.0, 13.0),
    ];
    let arcs = 8u32;
    for index in 0..arcs {
        let angle = index as f32 / arcs as f32 * TAU;
        let elevation = (index as f32 * 0.9).sin() * 1.4;
        let end = center + vec3(angle.cos() * 2.8, elevation, angle.sin() * 2.8);
        let seed = 0x9E37_79B9u32.wrapping_add(index.wrapping_mul(2_246_822_519));
        entities.push(spawn_bolt(
            world,
            center,
            end,
            vec3(0.7, 0.85, 2.2),
            5.0,
            seed,
        ));
    }
    handle(entities)
}

fn rune_circle(world: &mut World, position: Vec3) -> VfxHandle {
    handle(vec![
        spawn_mesh_effect(
            world,
            "Torus",
            position + vec3(0.0, 0.15, 0.0),
            Quat::identity(),
            true,
            0.5,
            VfxAnimator {
                lifetime: 0.0,
                start_scale: vec3(2.4, 0.1, 2.4),
                end_scale: vec3(2.4, 0.1, 2.4),
                peak_color: vec3(0.7, 0.4, 1.6),
                emissive_strength: 2.6,
                base_alpha: 0.9,
                pulse_amplitude: 0.2,
                pulse_frequency: 1.0,
                ..Default::default()
            },
        ),
        spawn_mesh_effect(
            world,
            "Torus",
            position + vec3(0.0, 0.15, 0.0),
            Quat::identity(),
            true,
            0.5,
            VfxAnimator {
                lifetime: 0.0,
                start_scale: vec3(1.3, 0.08, 1.3),
                end_scale: vec3(1.3, 0.08, 1.3),
                peak_color: vec3(0.4, 0.6, 1.6),
                emissive_strength: 2.6,
                base_alpha: 0.9,
                ..Default::default()
            },
        ),
        spawn_beam(
            world,
            Beam {
                start: position + vec3(0.0, 0.1, 0.0),
                end: position + vec3(0.0, 5.0, 0.0),
                color: vec3(0.6, 0.4, 1.8),
                intensity: 2.6,
                alpha: 0.7,
                width: 0.5,
                strands: 9,
                flicker: 0.2,
                flicker_speed: 10.0,
                age: 0.0,
            },
        ),
        spawn_emitter(world, textured(rune_motes(position), FLARE_SLOT)),
        spawn_point_light(
            world,
            position + vec3(0.0, 1.5, 0.0),
            vec3(0.7, 0.4, 1.6),
            40.0,
            12.0,
        ),
    ])
}

fn energy_core(world: &mut World, position: Vec3) -> VfxHandle {
    let center = position + vec3(0.0, 1.6, 0.0);
    handle(vec![
        spawn_mesh_effect(
            world,
            "Sphere",
            center,
            Quat::identity(),
            true,
            0.3,
            VfxAnimator {
                lifetime: 0.0,
                start_scale: vec3(0.6, 0.6, 0.6),
                end_scale: vec3(0.6, 0.6, 0.6),
                peak_color: vec3(0.4, 1.2, 2.4),
                emissive_strength: 4.0,
                base_alpha: 0.95,
                pulse_amplitude: 0.3,
                pulse_frequency: 2.5,
                ..Default::default()
            },
        ),
        spawn_spinning_ring(world, center, vec3(1.0, 0.0, 0.0), vec3(0.4, 1.0, 2.2), 3.0),
        spawn_spinning_ring(
            world,
            center,
            vec3(0.0, 0.0, 1.0),
            vec3(0.5, 0.9, 2.0),
            -2.4,
        ),
        spawn_emitter(world, textured(core_sparks(center), FLARE_SLOT)),
        spawn_point_light(world, center, vec3(0.4, 0.9, 2.0), 45.0, 13.0),
    ])
}

fn supernova(world: &mut World, position: Vec3) -> VfxHandle {
    let center = position + vec3(0.0, 1.8, 0.0);
    handle(vec![
        spawn_emitter(
            world,
            textured(ParticleEmitter::flash_burst(center), GLOW_SLOT),
        ),
        spawn_emitter(
            world,
            textured(
                ParticleEmitter::firework_explosion(center, vec3(1.0, 0.7, 0.35), 700),
                GLOW_SLOT,
            ),
        ),
        spawn_emitter(world, textured(supernova_debris(center), FLARE_SLOT)),
        spawn_mesh_effect(
            world,
            "Torus",
            position + vec3(0.0, 0.3, 0.0),
            Quat::identity(),
            true,
            0.5,
            VfxAnimator {
                lifetime: 1.4,
                despawn_on_end: true,
                start_scale: vec3(0.5, 0.2, 0.5),
                end_scale: vec3(9.0, 0.2, 9.0),
                peak_color: vec3(1.6, 1.2, 0.6),
                emissive_strength: 2.6,
                base_alpha: 1.0,
                fade_alpha: true,
                ..Default::default()
            },
        ),
        spawn_mesh_effect(
            world,
            "Sphere",
            center,
            Quat::identity(),
            true,
            0.5,
            VfxAnimator {
                lifetime: 0.9,
                despawn_on_end: true,
                start_scale: vec3(0.3, 0.3, 0.3),
                end_scale: vec3(4.5, 4.5, 4.5),
                peak_color: vec3(1.4, 0.9, 0.5),
                emissive_strength: 3.0,
                base_alpha: 0.85,
                fade_alpha: true,
                ..Default::default()
            },
        ),
        spawn_emitter(world, textured(ground_dust(position), SMOKE_SLOT)),
        spawn_point_light(world, center, vec3(1.6, 1.1, 0.6), 130.0, 24.0),
    ])
}

fn lightning_storm(world: &mut World, position: Vec3) -> VfxHandle {
    let mut entities = vec![
        spawn_emitter(world, textured(storm_rain(position), GLOW_SLOT)),
        spawn_point_light(
            world,
            position + vec3(0.0, 6.0, 0.0),
            vec3(0.6, 0.7, 1.4),
            45.0,
            26.0,
        ),
    ];
    let strikes = 9u32;
    for index in 0..strikes {
        let angle = index as f32 / strikes as f32 * TAU;
        let radius = 2.5 + (index as f32 * 1.7).sin().abs() * 4.0;
        let strike = position + vec3(angle.cos() * radius, 0.05, angle.sin() * radius);
        let top = strike + vec3(0.0, 8.5, 0.0);
        let seed = 0x51ED_2719u32.wrapping_add(index.wrapping_mul(2_654_435_761));
        entities.push(spawn_bolt(
            world,
            top,
            strike,
            vec3(1.4, 1.7, 3.4),
            5.0,
            seed,
        ));
    }
    handle(entities)
}

fn singularity(world: &mut World, position: Vec3) -> VfxHandle {
    let center = position + vec3(0.0, 2.0, 0.0);

    let effects = &mut world.resources.renderer_state.effects;
    effects.uniforms = Default::default();
    effects.uniforms.chromatic_aberration = 0.5;
    effects.uniforms.wave_distortion = 0.35;
    effects.enabled = true;

    let mut entities = vec![
        spawn_mesh_effect(
            world,
            "Sphere",
            center,
            Quat::identity(),
            true,
            0.5,
            VfxAnimator {
                lifetime: 0.0,
                start_scale: vec3(0.7, 0.7, 0.7),
                end_scale: vec3(0.7, 0.7, 0.7),
                peak_color: vec3(0.02, 0.0, 0.05),
                emissive_strength: 0.0,
                base_alpha: 1.0,
                ..Default::default()
            },
        ),
        spawn_emitter(world, textured(singularity_disk(center), GLOW_SLOT)),
        spawn_mesh_effect(
            world,
            "Torus",
            center,
            nalgebra_glm::quat_angle_axis(1.2, &vec3(1.0, 0.0, 0.0)),
            true,
            0.4,
            VfxAnimator {
                lifetime: 0.0,
                start_scale: vec3(1.4, 1.4, 1.4),
                end_scale: vec3(1.4, 1.4, 1.4),
                spin_axis: vec3(1.0, 0.0, 0.0),
                spin_speed: 1.6,
                peak_color: vec3(1.2, 0.5, 2.0),
                emissive_strength: 3.0,
                base_alpha: 0.9,
                ..Default::default()
            },
        ),
        spawn_point_light(world, center, vec3(0.8, 0.4, 1.6), 50.0, 16.0),
    ];
    let beams = 10u32;
    for index in 0..beams {
        let angle = index as f32 / beams as f32 * TAU;
        let elevation = (index as f32 * 0.8).sin() * 1.6;
        let outer = center + vec3(angle.cos() * 4.2, elevation, angle.sin() * 4.2);
        entities.push(spawn_beam(
            world,
            Beam {
                start: outer,
                end: center,
                color: vec3(0.7, 0.3, 1.7),
                intensity: 2.6,
                alpha: 0.7,
                width: 0.06,
                strands: 3,
                flicker: 0.4,
                flicker_speed: 22.0,
                age: 0.0,
            },
        ));
    }
    handle(entities)
}

fn orrery(world: &mut World, position: Vec3) -> VfxHandle {
    let center = position + vec3(0.0, 2.0, 0.0);
    let mut entities = vec![
        spawn_mesh_effect(
            world,
            "Sphere",
            center,
            Quat::identity(),
            true,
            0.4,
            VfxAnimator {
                lifetime: 0.0,
                start_scale: vec3(0.6, 0.6, 0.6),
                end_scale: vec3(0.6, 0.6, 0.6),
                peak_color: vec3(2.0, 1.6, 0.8),
                emissive_strength: 4.5,
                base_alpha: 1.0,
                pulse_amplitude: 0.12,
                pulse_frequency: 1.5,
                ..Default::default()
            },
        ),
        spawn_point_light(world, center, vec3(1.4, 1.1, 0.6), 48.0, 18.0),
    ];
    let orbits = [
        (1.8, 2.2, vec3(0.0, 1.0, 0.12), vec3(0.4, 0.8, 2.2)),
        (2.8, -1.5, vec3(0.32, 1.0, 0.0), vec3(2.2, 0.5, 0.6)),
        (3.8, 1.05, vec3(0.12, 1.0, 0.4), vec3(0.5, 2.0, 0.7)),
    ];
    for (radius, speed, axis, color) in orbits {
        entities.push(spawn_mesh_effect(
            world,
            "Sphere",
            center,
            Quat::identity(),
            true,
            0.5,
            VfxAnimator {
                lifetime: 0.0,
                start_scale: vec3(0.22, 0.22, 0.22),
                end_scale: vec3(0.22, 0.22, 0.22),
                peak_color: color,
                emissive_strength: 3.5,
                base_alpha: 1.0,
                orbit_center: center,
                orbit_radius: radius,
                orbit_axis: axis,
                orbit_speed: speed,
                ..Default::default()
            },
        ));
        entities.push(spawn_trail(
            world,
            Trail {
                center,
                radius,
                axis,
                speed,
                color,
                intensity: 1.4,
                alpha: 1.0,
                max_points: 96,
                points: Vec::new(),
                age: 0.0,
            },
        ));
    }
    handle(entities)
}

fn supernova_debris(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Sparks,
        shape: EmitterShape::Sphere { radius: 0.3 },
        position,
        direction: vec3(0.0, 1.0, 0.0),
        spawn_rate: 0.0,
        burst_count: 600,
        particle_lifetime_min: 0.6,
        particle_lifetime_max: 1.6,
        initial_velocity_min: 14.0,
        initial_velocity_max: 32.0,
        velocity_spread: PI,
        gravity: vec3(0.0, -6.0, 0.0),
        drag: 0.05,
        size_start: 0.1,
        size_end: 0.01,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(1.0, 0.95, 0.8, 1.0)),
                (0.4, vec4(1.0, 0.6, 0.2, 0.9)),
                (0.8, vec4(1.0, 0.3, 0.1, 0.5)),
                (1.0, vec4(0.4, 0.1, 0.0, 0.0)),
            ],
        },
        emissive_strength: 12.0,
        one_shot: true,
        turbulence_strength: 0.5,
        turbulence_frequency: 3.0,
        ..Default::default()
    }
}

fn storm_rain(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Sparks,
        shape: EmitterShape::Box {
            half_extents: vec3(7.0, 0.1, 7.0),
        },
        position: position + vec3(0.0, 8.0, 0.0),
        direction: vec3(0.0, -1.0, 0.0),
        spawn_rate: 600.0,
        particle_lifetime_min: 0.8,
        particle_lifetime_max: 1.4,
        initial_velocity_min: 6.0,
        initial_velocity_max: 10.0,
        velocity_spread: 0.05,
        gravity: vec3(0.0, -18.0, 0.0),
        drag: 0.0,
        size_start: 0.05,
        size_end: 0.04,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(0.6, 0.7, 1.0, 0.0)),
                (0.3, vec4(0.65, 0.75, 1.0, 0.7)),
                (1.0, vec4(0.6, 0.7, 1.0, 0.4)),
            ],
        },
        emissive_strength: 1.0,
        ..Default::default()
    }
}

fn singularity_disk(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Fire,
        shape: EmitterShape::Box {
            half_extents: vec3(3.6, 0.1, 3.6),
        },
        position,
        direction: vec3(0.0, 1.0, 0.0),
        spawn_rate: 420.0,
        particle_lifetime_min: 0.8,
        particle_lifetime_max: 1.8,
        initial_velocity_min: 0.2,
        initial_velocity_max: 0.6,
        velocity_spread: PI,
        gravity: vec3(0.0, 0.0, 0.0),
        drag: 0.4,
        size_start: 0.12,
        size_end: 0.03,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(1.0, 0.8, 1.0, 1.0)),
                (0.4, vec4(0.8, 0.3, 1.0, 0.9)),
                (0.8, vec4(0.5, 0.2, 1.0, 0.6)),
                (1.0, vec4(0.2, 0.1, 0.5, 0.0)),
            ],
        },
        emissive_strength: 7.0,
        turbulence_strength: 3.5,
        turbulence_frequency: 2.0,
        ..Default::default()
    }
}

fn spawn_bolt(
    world: &mut World,
    start: Vec3,
    end: Vec3,
    color: Vec3,
    intensity: f32,
    seed: u32,
) -> Entity {
    spawn_lightning(
        world,
        LightningBolt {
            start,
            end,
            color,
            intensity,
            alpha: 1.0,
            segments: 14,
            jaggedness: 0.25,
            branch_count: 2,
            branch_spread: 0.6,
            regen_interval: 0.05,
            timer: 0.0,
            seed,
        },
    )
}

fn spawn_spinning_ring(
    world: &mut World,
    center: Vec3,
    axis: Vec3,
    color: Vec3,
    speed: f32,
) -> Entity {
    spawn_mesh_effect(
        world,
        "Torus",
        center,
        Quat::identity(),
        true,
        0.5,
        VfxAnimator {
            lifetime: 0.0,
            start_scale: vec3(1.3, 1.3, 1.3),
            end_scale: vec3(1.3, 1.3, 1.3),
            spin_axis: axis,
            spin_speed: speed,
            peak_color: color,
            emissive_strength: 2.5,
            base_alpha: 0.85,
            ..Default::default()
        },
    )
}

fn flame_jet(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Fire,
        shape: EmitterShape::Cone {
            angle: 0.22,
            height: 0.2,
        },
        position,
        direction: vec3(1.0, 0.15, 0.0),
        spawn_rate: 420.0,
        particle_lifetime_min: 0.4,
        particle_lifetime_max: 0.9,
        initial_velocity_min: 9.0,
        initial_velocity_max: 16.0,
        velocity_spread: 0.25,
        gravity: vec3(0.0, 1.0, 0.0),
        drag: 0.7,
        size_start: 0.28,
        size_end: 0.7,
        color_gradient: ColorGradient::fire(),
        emissive_strength: 5.0,
        turbulence_strength: 1.2,
        turbulence_frequency: 2.0,
        ..Default::default()
    }
}

fn fireflies(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Fire,
        shape: EmitterShape::Box {
            half_extents: vec3(4.0, 1.6, 4.0),
        },
        position: position + vec3(0.0, 1.4, 0.0),
        direction: vec3(0.0, 1.0, 0.0),
        spawn_rate: 45.0,
        particle_lifetime_min: 3.0,
        particle_lifetime_max: 6.0,
        initial_velocity_min: 0.1,
        initial_velocity_max: 0.4,
        velocity_spread: PI,
        gravity: vec3(0.0, 0.05, 0.0),
        drag: 0.6,
        size_start: 0.07,
        size_end: 0.05,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(1.0, 0.9, 0.4, 0.0)),
                (0.2, vec4(1.0, 0.95, 0.5, 1.0)),
                (0.5, vec4(0.7, 1.0, 0.4, 0.9)),
                (0.8, vec4(1.0, 0.9, 0.4, 0.8)),
                (1.0, vec4(0.8, 0.8, 0.3, 0.0)),
            ],
        },
        emissive_strength: 9.0,
        turbulence_strength: 1.5,
        turbulence_frequency: 0.4,
        ..Default::default()
    }
}

fn waterfall_water(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Sparks,
        shape: EmitterShape::Box {
            half_extents: vec3(1.5, 0.1, 0.2),
        },
        position,
        direction: vec3(0.0, -1.0, 0.0),
        spawn_rate: 520.0,
        particle_lifetime_min: 1.0,
        particle_lifetime_max: 1.6,
        initial_velocity_min: 2.0,
        initial_velocity_max: 4.0,
        velocity_spread: 0.1,
        gravity: vec3(0.0, -12.0, 0.0),
        drag: 0.02,
        size_start: 0.1,
        size_end: 0.06,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(0.7, 0.85, 1.0, 0.9)),
                (0.6, vec4(0.5, 0.7, 1.0, 0.8)),
                (1.0, vec4(0.6, 0.8, 1.0, 0.3)),
            ],
        },
        emissive_strength: 1.2,
        ..Default::default()
    }
}

fn waterfall_mist(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Smoke,
        shape: EmitterShape::Box {
            half_extents: vec3(1.6, 0.1, 0.6),
        },
        position,
        direction: vec3(0.0, 1.0, 0.0),
        spawn_rate: 80.0,
        particle_lifetime_min: 1.0,
        particle_lifetime_max: 2.2,
        initial_velocity_min: 0.5,
        initial_velocity_max: 1.5,
        velocity_spread: 1.2,
        gravity: vec3(0.0, 0.5, 0.0),
        drag: 0.4,
        size_start: 0.4,
        size_end: 1.2,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(0.8, 0.88, 1.0, 0.0)),
                (0.3, vec4(0.85, 0.9, 1.0, 0.5)),
                (1.0, vec4(0.9, 0.93, 1.0, 0.0)),
            ],
        },
        emissive_strength: 0.2,
        turbulence_strength: 0.6,
        turbulence_frequency: 0.5,
        ..Default::default()
    }
}

fn core_sparks(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Sparks,
        shape: EmitterShape::Sphere { radius: 0.4 },
        position,
        direction: vec3(0.0, 1.0, 0.0),
        spawn_rate: 90.0,
        particle_lifetime_min: 0.2,
        particle_lifetime_max: 0.5,
        initial_velocity_min: 1.0,
        initial_velocity_max: 4.0,
        velocity_spread: PI,
        gravity: vec3(0.0, 0.0, 0.0),
        drag: 0.3,
        size_start: 0.05,
        size_end: 0.01,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(0.8, 0.95, 1.0, 1.0)),
                (0.5, vec4(0.5, 0.8, 1.0, 0.8)),
                (1.0, vec4(0.3, 0.6, 1.0, 0.0)),
            ],
        },
        emissive_strength: 10.0,
        ..Default::default()
    }
}

fn rune_motes(position: Vec3) -> ParticleEmitter {
    ParticleEmitter {
        emitter_type: EmitterType::Fire,
        shape: EmitterShape::Box {
            half_extents: vec3(2.2, 0.1, 2.2),
        },
        position: position + vec3(0.0, 0.2, 0.0),
        direction: vec3(0.0, 1.0, 0.0),
        spawn_rate: 120.0,
        particle_lifetime_min: 1.5,
        particle_lifetime_max: 3.0,
        initial_velocity_min: 1.0,
        initial_velocity_max: 2.5,
        velocity_spread: 0.4,
        gravity: vec3(0.0, 0.5, 0.0),
        drag: 0.3,
        size_start: 0.1,
        size_end: 0.02,
        color_gradient: ColorGradient {
            colors: vec![
                (0.0, vec4(0.8, 0.5, 1.0, 1.0)),
                (0.5, vec4(0.5, 0.4, 1.0, 0.9)),
                (1.0, vec4(0.3, 0.3, 0.8, 0.0)),
            ],
        },
        emissive_strength: 6.0,
        turbulence_strength: 1.5,
        turbulence_frequency: 1.0,
        ..Default::default()
    }
}
