portal-handler 0.1.0

System URI handler for portal.nvim — forwards nvim:// URIs to a running Neovim instance via RPC
//! Détection des sockets Neovim actifs sur le système

use std::path::PathBuf;

/// Retourne le meilleur socket Neovim disponible.
///
/// Ordre de priorité :
/// 1. `$NVIM`            — défini automatiquement dans les buffers :terminal
/// 2. `$XDG_RUNTIME_DIR/nvim.*/0` — Neovim >= 0.9 sur Linux moderne
/// 3. `/tmp/nvim*/`      — fallback universel
pub fn find_best() -> Option<PathBuf> {
    // 1. $NVIM (toujours correct si on est dans un terminal Neovim)
    if let Ok(path) = std::env::var("NVIM") {
        let p = PathBuf::from(&path);
        if is_socket(&p) {
            return Some(p);
        }
    }

    // 2. XDG_RUNTIME_DIR
    let runtime = get_runtime_dir();
    if let Some(s) = best_socket_in(&runtime) {
        return Some(s);
    }

    // 3. /tmp
    best_socket_in("/tmp")
}

/// Retourne tous les sockets Neovim trouvés (pour `list`)
pub fn find_all() -> Vec<PathBuf> {
    let mut result = Vec::new();

    let runtime = get_runtime_dir();
    collect_sockets(&runtime, &mut result);
    collect_sockets("/tmp", &mut result);

    sort_by_mtime(&mut result);
    result
}

// ─── helpers privés ────────────────────────────────────────────────────────

fn get_runtime_dir() -> String {
    std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| {
        // Lire l'UID via la commande `id` pour éviter unsafe/libc
        let output = std::process::Command::new("id")
            .arg("-u")
            .output()
            .ok()
            .and_then(|o| String::from_utf8(o.stdout).ok())
            .unwrap_or_else(|| "1000".to_string());
        format!("/run/user/{}", output.trim())
    })
}

/// Trouve le socket le plus récent dans `dir` dont le nom commence par "nvim"
fn best_socket_in(dir: &str) -> Option<PathBuf> {
    let mut sockets = Vec::new();
    collect_sockets(dir, &mut sockets);
    sort_by_mtime(&mut sockets);
    sockets.into_iter().last()
}

/// Collecte tous les sockets Neovim dans `dir` (récursif un niveau)
fn collect_sockets(dir: &str, out: &mut Vec<PathBuf>) {
    let entries = match std::fs::read_dir(dir) {
        Ok(e) => e,
        Err(_) => return,
    };

    for entry in entries.filter_map(|e| e.ok()) {
        let name = entry.file_name();
        if !name.to_string_lossy().starts_with("nvim") {
            continue;
        }

        let path = entry.path();

        if is_socket(&path) {
            out.push(path);
        } else if path.is_dir() {
            // Neovim >= 0.9 : nvim.PID/0
            if let Ok(sub) = std::fs::read_dir(&path) {
                for s in sub.filter_map(|x| x.ok()) {
                    let sp = s.path();
                    if is_socket(&sp) {
                        out.push(sp);
                    }
                }
            }
        }
    }
}

fn is_socket(p: &std::path::Path) -> bool {
    #[cfg(unix)]
    {
        use std::os::unix::fs::FileTypeExt;
        p.metadata()
            .map(|m| m.file_type().is_socket())
            .unwrap_or(false)
    }
    #[cfg(not(unix))]
    {
        // Sur Windows, Neovim utilise des named pipes — on vérifie juste l'existence
        p.exists()
    }
}

fn sort_by_mtime(paths: &mut Vec<PathBuf>) {
    paths.sort_by_key(|p| p.metadata().and_then(|m| m.modified()).ok());
}