// Naga-based shader compilation utilities.
// Provides consistent shader loading and validation.

/// Shader source type.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShaderLang {
    Wgsl,
}

/// Compiled shader info for diagnostics.
#[derive(Debug, Clone)]
pub struct ShaderInfo {
    pub label: String,
    pub lang: ShaderLang,
    pub warnings: Vec<String>,
}

/// Load a WGSL shader module from source string.
pub fn load_wgsl(device: &wgpu::Device, label: &str, source: &str) -> (wgpu::ShaderModule, ShaderInfo) {
    let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
        label: Some(label),
        source: wgpu::ShaderSource::Wgsl(source.into()),
    });

    let info = ShaderInfo {
        label: label.to_string(),
        lang: ShaderLang::Wgsl,
        warnings: Vec::new(),
    };

    (module, info)
}

/// Load a WGSL shader module from an embedded source (include_str!).
// G10: Shader compilation errors produce wgpu validation errors that panic by default.
// The device error callback should be configured at device creation to handle gracefully.
pub fn load_wgsl_embedded(device: &wgpu::Device, label: &str, source: &'static str) -> wgpu::ShaderModule {
    device.create_shader_module(wgpu::ShaderModuleDescriptor {
        label: Some(label),
        source: wgpu::ShaderSource::Wgsl(source.into()),
    })
}

/// Uniform buffer helper — creates and uploads a bytemuck-compatible struct.
pub fn create_uniform_buffer<T: bytemuck::Pod>(device: &wgpu::Device, label: &str, data: &T) -> wgpu::Buffer {
    use wgpu::util::DeviceExt;
    device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
        label: Some(label),
        contents: bytemuck::bytes_of(data),
        usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
    })
}

/// Update a uniform buffer with new data.
pub fn update_uniform_buffer<T: bytemuck::Pod>(queue: &wgpu::Queue, buffer: &wgpu::Buffer, data: &T) {
    queue.write_buffer(buffer, 0, bytemuck::bytes_of(data));
}

/// Create a uniform bind group layout with a single uniform buffer at binding 0.
pub fn uniform_bind_group_layout(
    device: &wgpu::Device,
    label: &str,
    visibility: wgpu::ShaderStages,
) -> wgpu::BindGroupLayout {
    device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
        label: Some(label),
        entries: &[wgpu::BindGroupLayoutEntry {
            binding: 0,
            visibility,
            ty: wgpu::BindingType::Buffer {
                ty: wgpu::BufferBindingType::Uniform,
                has_dynamic_offset: false,
                min_binding_size: None,
            },
            count: None,
        }],
    })
}

/// Create a storage bind group layout with N read-only storage buffers.
pub fn storage_bind_group_layout(
    device: &wgpu::Device,
    label: &str,
    count: u32,
    visibility: wgpu::ShaderStages,
) -> wgpu::BindGroupLayout {
    let entries: Vec<wgpu::BindGroupLayoutEntry> = (0..count)
        .map(|i| wgpu::BindGroupLayoutEntry {
            binding: i,
            visibility,
            ty: wgpu::BindingType::Buffer {
                ty: wgpu::BufferBindingType::Storage { read_only: true },
                has_dynamic_offset: false,
                min_binding_size: None,
            },
            count: None,
        })
        .collect();

    device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
        label: Some(label),
        entries: &entries,
    })
}

/// Create a bind group from a layout and buffer entries.
pub fn create_bind_group(
    device: &wgpu::Device,
    label: &str,
    layout: &wgpu::BindGroupLayout,
    buffers: &[&wgpu::Buffer],
) -> wgpu::BindGroup {
    let entries: Vec<wgpu::BindGroupEntry<'_>> = buffers
        .iter()
        .enumerate()
        .map(|(i, buf)| wgpu::BindGroupEntry {
            binding: i as u32,
            resource: buf.as_entire_binding(),
        })
        .collect();

    device.create_bind_group(&wgpu::BindGroupDescriptor {
        label: Some(label),
        layout,
        entries: &entries,
    })
}

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

    #[test]
    fn shader_info_defaults() {
        let info = ShaderInfo {
            label: "test".into(),
            lang: ShaderLang::Wgsl,
            warnings: Vec::new(),
        };
        assert_eq!(info.lang, ShaderLang::Wgsl);
        assert!(info.warnings.is_empty());
    }
}
