// SPDX-License-Identifier: GPL-3.0-only

use cosmic::widget::icon;
use mime_guess::Mime;
use rustc_hash::FxHashMap;
use std::fs;
use std::path::Path;
use std::sync::{LazyLock, Mutex};

pub const FALLBACK_MIME_ICON: &str = "text-x-generic";

#[derive(Debug, Eq, Hash, PartialEq)]
struct MimeIconKey {
    mime: Mime,
    size: u16,
}

struct MimeIconCache {
    cache: FxHashMap<MimeIconKey, Option<icon::Handle>>,
    #[cfg(unix)]
    shared_mime_info: xdg_mime::SharedMimeInfo,
}

impl MimeIconCache {
    pub fn new() -> Self {
        Self {
            cache: FxHashMap::default(),
            #[cfg(unix)]
            shared_mime_info: xdg_mime::SharedMimeInfo::new(),
        }
    }

    #[cfg(not(unix))]
    pub fn get(&mut self, _key: MimeIconKey) -> Option<icon::Handle> {
        None
    }

    #[cfg(unix)]
    pub fn get(&mut self, key: MimeIconKey) -> Option<icon::Handle> {
        self.cache
            .entry(key)
            .or_insert_with_key(|key| {
                let mut icon_names = self.shared_mime_info.lookup_icon_names(&key.mime);
                if icon_names.is_empty() {
                    return None;
                }
                let icon_name = icon_names.remove(0);
                let mut named = icon::from_name(icon_name).prefer_svg(true).size(key.size);
                if !icon_names.is_empty() {
                    let fallback_names =
                        icon_names.into_iter().map(std::borrow::Cow::from).collect();
                    named = named.fallback(Some(icon::IconFallback::Names(fallback_names)));
                }
                Some(named.handle())
            })
            .clone()
    }
}
static MIME_ICON_CACHE: LazyLock<Mutex<MimeIconCache>> =
    LazyLock::new(|| Mutex::new(MimeIconCache::new()));

#[cfg(not(unix))]
pub fn mime_for_path(
    path: impl AsRef<Path>,
    metadata_opt: Option<&fs::Metadata>,
    remote: bool,
) -> Mime {
    mime_guess::from_path(path).first_or_octet_stream()
}

#[cfg(unix)]
pub fn mime_for_path(
    path: impl AsRef<Path>,
    metadata_opt: Option<&fs::Metadata>,
    remote: bool,
) -> Mime {
    let path = path.as_ref();
    let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
    // Try the shared mime info cache first
    let mut gb = mime_icon_cache.shared_mime_info.guess_mime_type();
    if remote {
        if let Some(file_name) = path.file_name().and_then(std::ffi::OsStr::to_str) {
            gb.file_name(file_name);
        }
    } else {
        gb.path(path);
    }
    if let Some(metadata) = metadata_opt {
        gb.metadata(metadata.clone());
    }
    let guess = gb.guess();
    let guessed_mime = guess.mime_type();

    /// Checks if the `Mime` is a special variant returned by `xdg-mime`.
    /// This includes directories, symlinks and zerosize files, which are returned as uncertain.
    fn is_special_mime(mime: &Mime) -> bool {
        *mime == "inode/directory" || *mime == "inode/symlink" || *mime == "application/x-zerosize"
    }

    // `xdg-mime-rs` sets the guess to uncertain if it returns special mime types.
    // The guess could also be uncertain on platforms without shared-mime-info.
    // Try mime_guess, but only if it is not one of the special mime types.
    if guess.uncertain() && (remote || !is_special_mime(guessed_mime)) {
        // If uncertain, try mime_guess. This could happen on platforms without shared-mime-info
        mime_guess::from_path(path).first_or_octet_stream()
    } else {
        guessed_mime.clone()
    }
}

pub fn mime_icon(mime: Mime, size: u16) -> icon::Handle {
    let mut mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
    match mime_icon_cache.get(MimeIconKey { mime, size }) {
        Some(handle) => handle,
        None => icon::from_name(FALLBACK_MIME_ICON)
            .prefer_svg(true)
            .size(size)
            .handle(),
    }
}

#[cfg(not(unix))]
pub fn parent_mime_types(_mime: &Mime) -> Option<Vec<Mime>> {
    None
}

#[cfg(unix)]
pub fn parent_mime_types(mime: &Mime) -> Option<Vec<Mime>> {
    let mime_icon_cache = MIME_ICON_CACHE.lock().unwrap();
    mime_icon_cache.shared_mime_info.get_parents_aliased(mime)
}
