//! Dreamwell Physics GPU — compute simulation, rendering, and promotion.
//!
//! Orchestrates particle simulation across multiple GPU compute passes
//! and renders particles using configurable render modes.

pub mod buffers;
pub mod collision_gpu;
pub mod compaction;
pub mod debug;
pub mod emitter_gpu;
pub mod force_field_gpu;
pub mod indirect;
pub mod promotion_gpu;
pub mod render;
pub mod simulation;
pub mod spawn;

use crate::camera::Camera;

pub use buffers::GpuPhysicsBuffers;
pub use collision_gpu::CollisionPass;
pub use compaction::CompactionPass;
pub use debug::{PhysicsDebugOverlay, PhysicsFrameStats};
pub use emitter_gpu::{EmitterPool, GpuEmitter};
pub use force_field_gpu::ForceFieldPass;
pub use indirect::IndirectDrawPass;
pub use promotion_gpu::PromotionExtractionPass;
pub use render::ParticleRenderer;
pub use simulation::SimulationPass;
pub use spawn::SpawnPass;

/// Main orchestrator for the physics GPU system.
pub struct PhysicsGpu {
    pub buffers: GpuPhysicsBuffers,
    pub emitter_pool: EmitterPool,
    pub spawn_pass: SpawnPass,
    pub simulation_pass: SimulationPass,
    pub collision_pass: CollisionPass,
    pub force_field_pass: ForceFieldPass,
    pub compaction_pass: CompactionPass,
    pub promotion_pass: PromotionExtractionPass,
    pub indirect_pass: IndirectDrawPass,
    pub renderer: ParticleRenderer,
    pub debug: PhysicsDebugOverlay,
    pub max_particles: u32,
    pub frame_stats: PhysicsFrameStats,
}

impl PhysicsGpu {
    /// Create a new physics GPU system with the given maximum particle capacity.
    pub fn new(device: &wgpu::Device, max_particles: u32) -> Self {
        let buffers = GpuPhysicsBuffers::new(device, max_particles);
        let emitter_pool = EmitterPool::new();
        let spawn_pass = SpawnPass::new(device);
        let simulation_pass = SimulationPass::new(device);
        let collision_pass = CollisionPass::new(device);
        let force_field_pass = ForceFieldPass::new(device);
        let compaction_pass = CompactionPass::new(device);
        let promotion_pass = PromotionExtractionPass::new(device);
        let indirect_pass = IndirectDrawPass::new(device);
        let renderer = ParticleRenderer::new(device);
        let debug = PhysicsDebugOverlay::new();

        Self {
            buffers,
            emitter_pool,
            spawn_pass,
            simulation_pass,
            collision_pass,
            force_field_pass,
            compaction_pass,
            promotion_pass,
            indirect_pass,
            renderer,
            debug,
            max_particles,
            frame_stats: PhysicsFrameStats::default(),
        }
    }

    /// Add an emitter to the system and return its handle.
    pub fn spawn_emitter(
        &mut self,
        desc: &dreamwell_engine::physics::EmitterDescriptor,
        position: [f32; 3],
        seed: u64,
    ) -> u64 {
        self.emitter_pool.add(desc, position, seed)
    }

    /// Remove an emitter.
    pub fn remove_emitter(&mut self, id: u64) {
        self.emitter_pool.remove(id);
    }

    /// Ingest a physics event and map it to emitter/spawn commands.
    pub fn ingest_event(&mut self, event: &dreamwell_engine::physics::PhysicsEvent) {
        use dreamwell_engine::physics::PhysicsEvent;
        match event {
            PhysicsEvent::SpawnEmitter { preset_id, .. } => {
                // Caller should resolve preset_id to EmitterDescriptor.
                log::debug!("Physics: spawn emitter request for preset '{}'", preset_id);
            }
            PhysicsEvent::StopEmitter { emitter_id, .. } => {
                self.remove_emitter(*emitter_id);
            }
            PhysicsEvent::Impact { position, force, .. } => {
                log::debug!("Physics: impact at {:?} force={}", position, force);
            }
            PhysicsEvent::Fracture { position, .. } => {
                log::debug!("Physics: fracture at {:?}", position);
            }
            PhysicsEvent::Ignite { heat, .. } => {
                log::debug!("Physics: ignite heat={}", heat);
            }
            PhysicsEvent::Explode {
                position,
                radius,
                force,
                ..
            } => {
                log::debug!("Physics: explode at {:?} r={} f={}", position, radius, force);
            }
            PhysicsEvent::PromoteParticle { .. } => {
                log::debug!("Physics: promote particle");
            }
        }
    }

    /// Run the full simulation pipeline for one frame.
    pub fn simulate(
        &mut self,
        device: &wgpu::Device,
        queue: &wgpu::Queue,
        encoder: &mut wgpu::CommandEncoder,
        dt: f32,
        total_time: f32,
    ) {
        // 1. Spawn new particles from active emitters.
        self.spawn_pass
            .dispatch(device, queue, encoder, &self.buffers, &mut self.emitter_pool, dt);

        // 2. Simulate particles (forces, integration, aging).
        self.simulation_pass
            .dispatch(device, queue, encoder, &self.buffers, dt, total_time);

        // 3. Collision detection and response.
        self.collision_pass.dispatch(queue, encoder, &self.buffers);

        // 4. Compaction (remove dead particles) — optional, run periodically.
        if self.frame_stats.frame_count.is_multiple_of(60) {
            self.compaction_pass.dispatch(device, queue, encoder, &mut self.buffers);
        }

        // 5. Promotion candidate extraction.
        self.promotion_pass.dispatch(queue, encoder, &self.buffers);

        self.frame_stats.frame_count += 1;
    }

    /// Prepare rendering state (bind groups, uniforms). Call before render pass.
    pub fn prepare_render(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, camera: &Camera) {
        self.renderer.prepare(device, queue, &self.buffers, camera);
    }

    /// Render all alive particles. Call prepare_render first.
    pub fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
        self.renderer.render(render_pass, self.max_particles);
    }

    /// Get current frame statistics.
    pub fn stats(&self) -> &PhysicsFrameStats {
        &self.frame_stats
    }

    /// Get current alive particle estimate.
    pub fn alive_count(&self) -> u32 {
        self.emitter_pool.estimated_alive()
    }
}
