// Texture cache — reuse render-internal textures (depth buffers, shadow maps).
// Tracks last-used frame for garbage collection of stale textures.

use crate::pipeline_cache::fnv1a_hash;

/// A cached texture with its view and metadata.
pub struct CachedTexture {
    pub texture: wgpu::Texture,
    pub view: wgpu::TextureView,
    pub desc_hash: u64,
    pub last_used_frame: u32,
}

/// Texture reuse cache for render-internal textures.
/// Textures are matched by (width, height, format, usage) hash.
/// Stale textures are garbage-collected after `max_unused_frames`.
pub struct TextureCache {
    textures: Vec<CachedTexture>,
}

impl TextureCache {
    pub fn new() -> Self {
        Self { textures: Vec::new() }
    }

    /// Get or create a texture matching the given parameters.
    /// Returns a reference to the cached texture (reused or newly created).
    pub fn get_or_create(
        &mut self,
        device: &wgpu::Device,
        width: u32,
        height: u32,
        format: wgpu::TextureFormat,
        usage: wgpu::TextureUsages,
        frame: u32,
    ) -> &CachedTexture {
        let hash = texture_desc_hash(width, height, format, usage);

        // Try to find existing match.
        if let Some(idx) = self.textures.iter().position(|t| t.desc_hash == hash) {
            self.textures[idx].last_used_frame = frame;
            return &self.textures[idx];
        }

        // Create new texture.
        let texture = device.create_texture(&wgpu::TextureDescriptor {
            label: Some("cached_texture"),
            size: wgpu::Extent3d {
                width,
                height,
                depth_or_array_layers: 1,
            },
            mip_level_count: 1,
            sample_count: 1,
            dimension: wgpu::TextureDimension::D2,
            format,
            usage,
            view_formats: &[],
        });
        let view = texture.create_view(&wgpu::TextureViewDescriptor::default());

        self.textures.push(CachedTexture {
            texture,
            view,
            desc_hash: hash,
            last_used_frame: frame,
        });

        self.textures.last().unwrap()
    }

    /// Garbage-collect textures not used for `max_unused_frames` frames.
    pub fn gc(&mut self, current_frame: u32, max_unused_frames: u32) {
        self.textures
            .retain(|t| current_frame.wrapping_sub(t.last_used_frame) <= max_unused_frames);
    }

    /// Number of cached textures.
    pub fn len(&self) -> usize {
        self.textures.len()
    }

    /// Whether the cache is empty.
    pub fn is_empty(&self) -> bool {
        self.textures.is_empty()
    }

    /// Clear all cached textures.
    pub fn clear(&mut self) {
        self.textures.clear();
    }
}

impl Default for TextureCache {
    fn default() -> Self {
        Self::new()
    }
}

/// Compute hash for texture descriptor parameters.
fn texture_desc_hash(width: u32, height: u32, format: wgpu::TextureFormat, usage: wgpu::TextureUsages) -> u64 {
    let mut bytes = [0u8; 16];
    bytes[0..4].copy_from_slice(&width.to_le_bytes());
    bytes[4..8].copy_from_slice(&height.to_le_bytes());
    // Hash the debug representation of the format as a stable discriminant.
    let format_disc = {
        use std::hash::{Hash, Hasher};
        let mut hasher = std::collections::hash_map::DefaultHasher::new();
        format.hash(&mut hasher);
        hasher.finish() as u32
    };
    bytes[8..12].copy_from_slice(&format_disc.to_le_bytes());
    bytes[12..16].copy_from_slice(&usage.bits().to_le_bytes());
    fnv1a_hash(&bytes)
}

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

    #[test]
    fn texture_desc_hash_deterministic() {
        let a = texture_desc_hash(
            1920,
            1080,
            wgpu::TextureFormat::Depth32Float,
            wgpu::TextureUsages::RENDER_ATTACHMENT,
        );
        let b = texture_desc_hash(
            1920,
            1080,
            wgpu::TextureFormat::Depth32Float,
            wgpu::TextureUsages::RENDER_ATTACHMENT,
        );
        assert_eq!(a, b);
    }

    #[test]
    fn texture_desc_hash_varies_with_size() {
        let a = texture_desc_hash(
            1920,
            1080,
            wgpu::TextureFormat::Depth32Float,
            wgpu::TextureUsages::RENDER_ATTACHMENT,
        );
        let b = texture_desc_hash(
            1280,
            720,
            wgpu::TextureFormat::Depth32Float,
            wgpu::TextureUsages::RENDER_ATTACHMENT,
        );
        assert_ne!(a, b);
    }

    #[test]
    fn texture_desc_hash_varies_with_format() {
        let a = texture_desc_hash(
            1920,
            1080,
            wgpu::TextureFormat::Depth32Float,
            wgpu::TextureUsages::RENDER_ATTACHMENT,
        );
        let b = texture_desc_hash(
            1920,
            1080,
            wgpu::TextureFormat::Rgba8Unorm,
            wgpu::TextureUsages::RENDER_ATTACHMENT,
        );
        assert_ne!(a, b);
    }

    #[test]
    fn cache_empty_by_default() {
        let cache = TextureCache::new();
        assert!(cache.is_empty());
        assert_eq!(cache.len(), 0);
    }
}
