//! GPU compute acceleration via wgpu (WebGPU).
//!
//! Provides GPU-accelerated versions of tree routing, power iteration PCA,
//! and hash computation. Compiles WGSL shaders to Metal (macOS), Vulkan (Linux),
//! or DX12 (Windows) — no CUDA dependency.
//!
//! Enabled via the `gpu` Cargo feature.

pub mod buffers;
pub mod tree_routing;
pub mod pca;
pub mod hash_scatter;

use std::sync::Arc;

/// GPU device and queue, shared across pipelines.
pub struct GpuContext {
    pub device: Arc<wgpu::Device>,
    pub queue: Arc<wgpu::Queue>,
    pub adapter_info: wgpu::AdapterInfo,
    pub limits: wgpu::Limits,
}

impl GpuContext {
    /// Initialize GPU context, selecting the best available adapter.
    pub fn new() -> Result<Self, GpuError> {
        pollster::block_on(Self::new_async())
    }

    /// The adapter's device type (Discrete, Integrated, etc.).
    pub fn device_type(&self) -> wgpu::DeviceType {
        self.adapter_info.device_type
    }

    async fn new_async() -> Result<Self, GpuError> {
        let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
            backends: wgpu::Backends::all(),
            ..Default::default()
        });

        let adapter = instance
            .request_adapter(&wgpu::RequestAdapterOptions {
                power_preference: wgpu::PowerPreference::HighPerformance,
                compatible_surface: None,
                force_fallback_adapter: false,
            })
            .await
            .map_err(|_| GpuError::NoAdapter)?;

        let adapter_info = adapter.get_info();
        let limits = adapter.limits();

        let (device, queue) = adapter
            .request_device(&wgpu::DeviceDescriptor {
                label: Some("dyf-gpu"),
                required_features: wgpu::Features::empty(),
                required_limits: wgpu::Limits::default(),
                experimental_features: Default::default(),
                memory_hints: wgpu::MemoryHints::Performance,
                trace: wgpu::Trace::Off,
            })
            .await
            .map_err(|e: wgpu::RequestDeviceError| GpuError::DeviceRequest(e.to_string()))?;

        Ok(GpuContext {
            device: Arc::new(device),
            queue: Arc::new(queue),
            adapter_info,
            limits,
        })
    }

    /// Human-readable GPU info string.
    pub fn info(&self) -> String {
        format!(
            "{} ({:?}, {} MiB max buffer)",
            self.adapter_info.name,
            self.adapter_info.backend,
            self.limits.max_storage_buffer_binding_size / (1024 * 1024),
        )
    }

    /// Maximum storage buffer size in bytes.
    pub fn max_buffer_size(&self) -> u64 {
        self.limits.max_storage_buffer_binding_size as u64
    }
}

/// GPU operation errors.
#[derive(Debug)]
pub enum GpuError {
    NoAdapter,
    DeviceRequest(String),
    BufferTooLarge { requested: u64, max: u64 },
    ShaderError(String),
    ValidationError(String),
}

impl std::fmt::Display for GpuError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            GpuError::NoAdapter => write!(f, "No GPU adapter found"),
            GpuError::DeviceRequest(e) => write!(f, "GPU device request failed: {e}"),
            GpuError::BufferTooLarge { requested, max } => {
                write!(f, "Buffer {requested} bytes exceeds max {max}")
            }
            GpuError::ShaderError(e) => write!(f, "Shader error: {e}"),
            GpuError::ValidationError(e) => write!(f, "Validation error: {e}"),
        }
    }
}

impl std::error::Error for GpuError {}
