use std::num::NonZeroUsize;
use std::path::PathBuf;
use std::sync::mpsc::Sender;

use ratatui_image::picker::Picker;
use ratatui_image::protocol::StatefulProtocol;

use crate::image_render::worker::{DecodedImage, ImageJob};
/// LRU-evicting cache of decoded images and their protocol renderers for one view.
pub struct ImageCache {
    map: lru::LruCache<PathBuf, ImageEntry>,
}

struct ImageEntry {
    /// Created lazily from decoded + picker; `None` while decode pending.
    proto: Option<StatefulProtocol>,
}

impl ImageCache {
    pub fn new(limit: usize) -> Self {
        Self {
            map: lru::LruCache::new(NonZeroUsize::new(limit.max(1)).expect("limit >= 1")),
        }
    }

    /// Request an image for display. If the key is absent, sends a decode job
    /// to the worker. Call `install_decoded` when the result arrives.
    pub fn request(&mut self, key: PathBuf, max_dim: u32, tx: &Sender<ImageJob>) {
        if self.map.contains(&key) {
            // Touch existing entry
            let _ = self.map.get_mut(&key);
            return;
        }
        let _ = tx.send(ImageJob {
            key: key.clone(),
            max_dim,
        });
        self.map.put(key, ImageEntry { proto: None });
    }

    /// Install a completed decode result and build the protocol renderer.
    pub fn install_decoded(&mut self, img: DecodedImage, picker: &Picker) {
        if let Some(entry) = self.map.get_mut(&img.key) {
            let proto = picker.new_resize_protocol(img.image);
            entry.proto = Some(proto);
        }
    }

    /// Get a mutable reference to the protocol for rendering, if ready.
    pub fn get_proto(&mut self, key: &PathBuf) -> Option<&mut StatefulProtocol> {
        self.map.get_mut(key).and_then(|entry| entry.proto.as_mut())
    }
}

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

    #[test]
    fn test_eviction() {
        let mut cache = ImageCache::new(2);

        let dir = std::env::temp_dir().join("clin_test_img_cache");
        let _ = std::fs::create_dir_all(&dir);
        let path_a = dir.join("a.png");
        let path_b = dir.join("b.png");
        let path_c = dir.join("c.png");

        let k1 = path_a;
        let k2 = path_b;
        let k3 = path_c;

        // Insert two
        let (tx, _) = std::sync::mpsc::channel();
        cache.request(k1.clone(), 100, &tx);
        cache.request(k2.clone(), 100, &tx);
        assert_eq!(cache.map.len(), 2);

        // Touch k1 so it's more recent
        cache.get_proto(&k1);

        // Insert third — should evict k2 (oldest)
        cache.request(k3.clone(), 100, &tx);
        assert_eq!(cache.map.len(), 2);
        assert!(cache.map.contains(&k1), "k1 should survive");
        assert!(!cache.map.contains(&k2), "k2 should be evicted as oldest");
        assert!(cache.map.contains(&k3), "k3 should survive");

        // Cleanup
        let _ = std::fs::remove_dir_all(&dir);
    }
}
