use std::collections::HashMap;
use std::path::{Path, PathBuf};

/// Icon search directories in priority order.
const ICON_THEME_DIRS: &[&str] = &[
    "/usr/share/icons/Adwaita",
    "/usr/share/icons/hicolor",
    "/usr/share/icons/gnome",
    "/usr/share/icons/breeze",
    "/var/lib/flatpak/exports/share/icons/hicolor",
];

/// Preferred subdirectories for scalable and sized icons.
const SCALABLE_SUBDIRS: &[&str] = &["scalable/status", "scalable/actions", "scalable/apps", "scalable/devices", "scalable/categories"];
const SIZED_SUBDIRS: &[&str] = &[
    "256x256/apps", "256x256/status", "256x256/actions", "256x256/devices", "256x256/categories",
    "128x128/apps", "128x128/status", "128x128/actions", "128x128/devices", "128x128/categories",
    "96x96/apps", "96x96/status", "96x96/actions", "96x96/devices", "96x96/categories",
    "64x64/apps", "64x64/status", "64x64/actions", "64x64/devices", "64x64/categories",
    "48x48/apps", "48x48/status", "48x48/actions", "48x48/devices", "48x48/categories",
    "32x32/apps", "32x32/status", "32x32/actions", "32x32/devices", "32x32/categories",
    "symbolic/status", "symbolic/actions", "symbolic/apps", "symbolic/devices",
    "symbolic/legacy", "symbolic/emblems", "symbolic/categories",
    "24x24/apps", "24x24/status", "24x24/actions", "24x24/devices", "24x24/categories",
];

/// Pixmaps fallback directory.
const PIXMAP_DIRS: &[&str] = &[
    "/usr/share/pixmaps",
    "/var/lib/flatpak/exports/share/icons/hicolor/128x128/apps",
    "/var/lib/flatpak/exports/share/icons/hicolor/64x64/apps",
];

pub struct IconCache {
    /// (icon_name, size) -> RGBA buffer + (width, height)
    cache: HashMap<(String, u32), Option<(Vec<u8>, u32, u32)>>,
}

impl IconCache {
    pub fn new() -> Self {
        Self { cache: HashMap::new() }
    }

    /// Look up and render an icon by name at the given size.
    /// Returns (RGBA buffer, width, height) or None.
    pub fn get(&mut self, name: &str, size: u32) -> Option<&(Vec<u8>, u32, u32)> {
        let key = (name.to_string(), size);
        if !self.cache.contains_key(&key) {
            let result = Self::load_icon(name, size);
            self.cache.insert(key.clone(), result);
        }
        self.cache.get(&key).and_then(|o| o.as_ref())
    }

    fn load_icon(name: &str, size: u32) -> Option<(Vec<u8>, u32, u32)> {
        // Try to find the icon file
        let path = lookup_icon_path(name);
        tracing::info!("Icon lookup '{name}': {:?}", path);
        let path = path?;
        let ext = path.extension()?.to_str()?;

        let result = match ext {
            "svg" => load_svg(&path, size),
            "png" => load_png(&path, size),
            _ => None,
        };
        if result.is_none() {
            tracing::warn!("Failed to render icon: {path:?}");
        }
        result
    }
}

/// Look up an icon path following freedesktop conventions.
pub fn lookup_icon_path(name: &str) -> Option<PathBuf> {
    // If the name is an absolute path, use it directly
    if name.starts_with('/') {
        let p = PathBuf::from(name);
        if p.exists() {
            return Some(p);
        }
        // Try adding extensions
        for ext in &["svg", "png"] {
            let p = PathBuf::from(format!("{name}.{ext}"));
            if p.exists() {
                return Some(p);
            }
        }
    }

    // Search icon theme directories
    for theme_dir in ICON_THEME_DIRS {
        let theme_path = Path::new(theme_dir);
        if !theme_path.exists() {
            continue;
        }

        // Try scalable SVGs first
        for subdir in SCALABLE_SUBDIRS {
            let svg_path = theme_path.join(subdir).join(format!("{name}.svg"));
            if svg_path.exists() {
                return Some(svg_path);
            }
            // Also try -symbolic variant
            let sym_path = theme_path.join(subdir).join(format!("{name}-symbolic.svg"));
            if sym_path.exists() {
                return Some(sym_path);
            }
        }

        // Try sized directories
        for subdir in SIZED_SUBDIRS {
            for ext in &["svg", "png"] {
                let p = theme_path.join(subdir).join(format!("{name}.{ext}"));
                if p.exists() {
                    return Some(p);
                }
            }
        }
    }

    // Try pixmaps directories
    for pixmap_dir in PIXMAP_DIRS {
        for ext in &["svg", "png", "xpm"] {
            let p = PathBuf::from(pixmap_dir).join(format!("{name}.{ext}"));
            if p.exists() && *ext != "xpm" {
                return Some(p);
            }
        }
    }

    // Try Flatpak app icon export directories with the name as app ID
    let flatpak_dirs = [
        "/var/lib/flatpak/exports/share/icons/hicolor",
        &format!("{}/.local/share/flatpak/exports/share/icons/hicolor", std::env::var("HOME").unwrap_or_default()),
    ];
    for base in &flatpak_dirs {
        for size_dir in &["128x128/apps", "64x64/apps", "48x48/apps", "scalable/apps"] {
            for ext in &["svg", "png"] {
                let p = PathBuf::from(base).join(size_dir).join(format!("{name}.{ext}"));
                if p.exists() {
                    return Some(p);
                }
            }
        }
    }

    None
}

fn load_svg(path: &Path, size: u32) -> Option<(Vec<u8>, u32, u32)> {
    let data = std::fs::read(path).ok()?;
    let tree = resvg::usvg::Tree::from_data(&data, &resvg::usvg::Options::default()).ok()?;

    let svg_size = tree.size();
    let (sw, sh) = (svg_size.width(), svg_size.height());

    let scale = if sw > sh {
        size as f32 / sw
    } else {
        size as f32 / sh
    };

    let w = (sw * scale).ceil() as u32;
    let h = (sh * scale).ceil() as u32;

    if w == 0 || h == 0 {
        return None;
    }

    let mut pixmap = resvg::tiny_skia::Pixmap::new(w, h)?;
    let transform = resvg::tiny_skia::Transform::from_scale(scale, scale);
    resvg::render(&tree, transform, &mut pixmap.as_mut());

    Some((pixmap.data().to_vec(), w, h))
}

fn load_png(path: &Path, size: u32) -> Option<(Vec<u8>, u32, u32)> {
    let img = image::open(path).ok()?.into_rgba8();

    let (w, h) = img.dimensions();
    if w == size && h == size {
        return Some((img.into_raw(), w, h));
    }

    // Aspect-preserving resize with Lanczos3 for sharp, high-quality icons
    let scale = size as f32 / w.max(h) as f32;
    let tw = (w as f32 * scale).round() as u32;
    let th = (h as f32 * scale).round() as u32;
    let resized = image::imageops::resize(&img, tw, th, image::imageops::FilterType::Lanczos3);
    Some((resized.into_raw(), tw, th))
}
