// Observer context for GPU — uploads per-frame observer state to a uniform buffer.
// Used by Quantum Culling and DreamMatter for per-observer visibility and simulation.

/// GPU-uploadable observer context (80 bytes).
/// Extended with prev_position and dirty_object_count for DreamSuperposition
/// temporal coherence in the Quantum Culling shader.
/// Quantum bridge: coherence/decohere_radius fields feed GPU quantum-aware LOD.
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GpuObserverContext {
    pub position: [f32; 3],
    pub fov_radius: f32,
    pub topology_layer: u32,
    pub frame_index: u32,
    /// Bitmask of visible topology layers (default: 0x3FF = all 10 layers visible).
    /// Used by quantum_cull.wgsl for per-layer visibility filtering.
    pub layer_visibility_mask: u32,
    /// Count of objects whose transforms changed this frame (DreamSpace dirty set).
    /// When 0 AND observer hasn't moved: all meshlet visibility is temporally coherent.
    pub dirty_object_count: u32,
    /// Previous frame's observer position. If distance(position, prev_position) < epsilon,
    /// observer hasn't moved and temporal coherence applies.
    pub prev_position: [f32; 3],
    /// Squared distance the observer moved since last frame (precomputed on CPU).
    /// Shader uses this for early-out: if < coherence_threshold², skip re-evaluation.
    pub observer_move_dist_sq: f32,
    /// Number of active (non-superposed) bodies. For GPU workload sizing.
    pub active_body_count: u32,
    /// Number of superposed bodies. Informational for Prometheus/debug.
    pub superposed_body_count: u32,
    /// Number of dormant bodies. Informational.
    pub dormant_body_count: u32,
    pub _pad2: u32,
    // ── Quantum bridge fields (16 bytes) ─────────────────────────────
    /// Observer quantum coherence (from density matrix purity). Range [0, 1].
    /// High coherence → interference visible → keep full LOD.
    pub coherence: f32,
    /// Decoherence ring outer boundary (world units).
    pub decohere_radius: f32,
    /// Decoherence tick rate (modulo scheduling hint for GPU).
    pub decohere_tick_rate: u32,
    pub _pad3: u32,
}

/// Manages a GPU uniform buffer for observer context.
/// Pre-allocated at init; updated via write_buffer each frame.
pub struct ObserverBuffer {
    pub buffer: wgpu::Buffer,
}

impl ObserverBuffer {
    /// Create a new observer buffer.
    pub fn new(device: &wgpu::Device) -> Self {
        let buffer = device.create_buffer(&wgpu::BufferDescriptor {
            label: Some("observer_context"),
            size: std::mem::size_of::<GpuObserverContext>() as u64,
            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });
        Self { buffer }
    }

    /// Upload observer context to GPU. Zero allocation.
    pub fn upload(&self, queue: &wgpu::Queue, ctx: &GpuObserverContext) {
        queue.write_buffer(&self.buffer, 0, bytemuck::bytes_of(ctx));
    }

    /// Explicitly destroy GPU resources.
    pub fn destroy(&mut self) {
        self.buffer.destroy();
    }
}

/// Configuration for quantum culling layer visibility.
pub struct QuantumCullProfile {
    /// Bitmask of visible topology layers (bit N = layer N visible).
    pub layer_mask: u32,
}

impl Default for QuantumCullProfile {
    fn default() -> Self {
        Self {
            layer_mask: 0x3FF, // all 10 layers visible
        }
    }
}

impl QuantumCullProfile {
    /// Create from a list of visible layer indices.
    pub fn from_visible_layers(layers: &[u8]) -> Self {
        let mut mask = 0u32;
        for &l in layers {
            if l < 10 {
                mask |= 1 << l;
            }
        }
        Self { layer_mask: mask }
    }

    /// Convert to the bitmask value for GpuObserverContext.
    pub fn to_bitmask(&self) -> u32 {
        self.layer_mask
    }

    /// Check if a specific layer is visible.
    pub fn is_layer_visible(&self, layer: u8) -> bool {
        layer < 10 && (self.layer_mask & (1 << layer)) != 0
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use bytemuck::Zeroable;

    #[test]
    fn gpu_observer_context_size() {
        assert_eq!(std::mem::size_of::<GpuObserverContext>(), 80);
    }

    #[test]
    fn gpu_observer_context_zeroed() {
        let ctx = GpuObserverContext::zeroed();
        assert_eq!(ctx.position, [0.0; 3]);
        assert_eq!(ctx.topology_layer, 0);
    }

    #[test]
    fn bitmask_culling() {
        let profile = QuantumCullProfile::from_visible_layers(&[0, 3, 6]);
        assert!(profile.is_layer_visible(0));
        assert!(profile.is_layer_visible(3));
        assert!(profile.is_layer_visible(6));
        assert!(!profile.is_layer_visible(1));
        assert!(!profile.is_layer_visible(9));
        assert_eq!(profile.to_bitmask(), (1 << 0) | (1 << 3) | (1 << 6));
    }

    #[test]
    fn default_mask_all_visible() {
        let profile = QuantumCullProfile::default();
        for layer in 0..10u8 {
            assert!(profile.is_layer_visible(layer), "layer {layer} should be visible");
        }
        assert!(!profile.is_layer_visible(10));
    }
}
