portal-handler 0.1.0

System URI handler for portal.nvim — forwards nvim:// URIs to a running Neovim instance via RPC
//! Enregistrement du handler URI nvim:// selon l'OS

use anyhow::{anyhow, Result};

/// Enregistre le schéma nvim:// dans le système pour `binary_path`
pub fn install(binary_path: &str) -> Result<()> {
    #[cfg(target_os = "linux")]
    return linux::install(binary_path);

    #[cfg(target_os = "macos")]
    return macos::install(binary_path);

    #[cfg(target_os = "windows")]
    return windows::install(binary_path);

    #[allow(unreachable_code)]
    Err(anyhow!("OS non supporté pour l'installation automatique"))
}

/// Dé-enregistre le schéma nvim://
pub fn uninstall() -> Result<()> {
    #[cfg(target_os = "linux")]
    return linux::uninstall();

    #[cfg(target_os = "macos")]
    return macos::uninstall();

    #[cfg(target_os = "windows")]
    return windows::uninstall();

    #[allow(unreachable_code)]
    Err(anyhow!("OS non supporté"))
}

// ─── Linux ──────────────────────────────────────────────────────────────────

#[cfg(target_os = "linux")]
mod linux {
    use anyhow::{anyhow, Result};
    use std::path::PathBuf;

    fn apps_dir() -> PathBuf {
        dirs::data_dir()
            .unwrap_or_else(|| {
                PathBuf::from(std::env::var("HOME").unwrap_or_default()).join(".local/share")
            })
            .join("applications")
    }

    fn desktop_path() -> PathBuf {
        apps_dir().join("portal-nvim-handler.desktop")
    }

    pub fn install(binary: &str) -> Result<()> {
        let dir = apps_dir();
        std::fs::create_dir_all(&dir)?;

        let content = format!(
            "[Desktop Entry]\n\
             Name=Neovim URI Handler (portal.nvim)\n\
             Comment=Ouvre des fichiers dans Neovim via nvim:// URIs\n\
             Exec={} open %u\n\
             Type=Application\n\
             NoDisplay=true\n\
             MimeType=x-scheme-handler/nvim;\n",
            binary
        );

        std::fs::write(desktop_path(), &content)?;

        std::process::Command::new("xdg-mime")
            .args([
                "default",
                "portal-nvim-handler.desktop",
                "x-scheme-handler/nvim",
            ])
            .status()
            .map_err(|e| anyhow!("xdg-mime échoué : {}", e))?;

        std::process::Command::new("update-desktop-database")
            .arg(&dir)
            .status()
            .ok(); // non-fatal si la commande n'existe pas

        Ok(())
    }

    pub fn uninstall() -> Result<()> {
        let _ = std::fs::remove_file(desktop_path());
        std::process::Command::new("update-desktop-database")
            .arg(apps_dir())
            .status()
            .ok();
        Ok(())
    }
}

// ─── macOS ──────────────────────────────────────────────────────────────────

#[cfg(target_os = "macos")]
mod macos {
    use anyhow::{anyhow, Result};
    use std::path::PathBuf;

    fn app_path() -> PathBuf {
        PathBuf::from(std::env::var("HOME").unwrap_or_default())
            .join("Applications/PortalNvimHandler.app")
    }

    fn lsregister() -> &'static str {
        "/System/Library/Frameworks/CoreServices.framework\
         /Versions/A/Frameworks/LaunchServices.framework\
         /Versions/A/Support/lsregister"
    }

    pub fn install(binary: &str) -> Result<()> {
        let app = app_path();
        std::fs::create_dir_all(app.join("Contents/MacOS"))?;

        // Launcher
        let launcher = app.join("Contents/MacOS/PortalNvimHandler");
        std::fs::write(
            &launcher,
            format!("#!/bin/sh\nexec \"{}\" open \"$@\"\n", binary),
        )?;
        std::process::Command::new("chmod")
            .args(["+x", launcher.to_str().unwrap_or("")])
            .status()?;

        // Info.plist
        let plist = format!(
            r#"<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleIdentifier</key><string>com.portal-nvim-handler</string>
  <key>CFBundleName</key><string>PortalNvimHandler</string>
  <key>CFBundleExecutable</key><string>PortalNvimHandler</string>
  <key>CFBundlePackageType</key><string>APPL</string>
  <key>CFBundleURLTypes</key>
  <array><dict>
    <key>CFBundleURLName</key><string>Neovim URI</string>
    <key>CFBundleURLSchemes</key><array><string>nvim</string></array>
  </dict></array>
  <key>LSUIElement</key><true/>
</dict>
</plist>"#
        );
        std::fs::write(app.join("Contents/Info.plist"), plist)?;

        std::process::Command::new(lsregister())
            .args(["-f", app.to_str().unwrap_or("")])
            .status()
            .map_err(|e| anyhow!("lsregister échoué : {}", e))?;

        Ok(())
    }

    pub fn uninstall() -> Result<()> {
        let app = app_path();
        std::process::Command::new(lsregister())
            .args(["-u", app.to_str().unwrap_or("")])
            .status()
            .ok();
        let _ = std::fs::remove_dir_all(&app);
        Ok(())
    }
}

// ─── Windows ────────────────────────────────────────────────────────────────

#[cfg(target_os = "windows")]
mod windows {
    use anyhow::{anyhow, Result};

    pub fn install(binary: &str) -> Result<()> {
        let entries = [
            (
                "HKCU\\Software\\Classes\\nvim",
                vec!["/ve", "/d", "URL:nvim Protocol"],
            ),
            (
                "HKCU\\Software\\Classes\\nvim",
                vec!["/v", "URL Protocol", "/d", ""],
            ),
        ];

        for (key, args) in &entries {
            let mut cmd = std::process::Command::new("reg");
            cmd.args(["add", key, "/f"]);
            for a in args {
                cmd.arg(a);
            }
            let status = cmd.status().map_err(|e| anyhow!("reg.exe : {}", e))?;
            if !status.success() {
                return Err(anyhow!("reg.exe a échoué pour {}", key));
            }
        }

        // Commande d'ouverture avec le chemin du binaire
        let open_value = format!("\"{}\" open \"%1\"", binary);
        std::process::Command::new("reg")
            .args([
                "add",
                "HKCU\\Software\\Classes\\nvim\\shell\\open\\command",
                "/f",
                "/ve",
                "/d",
                &open_value,
            ])
            .status()
            .map_err(|e| anyhow!("reg.exe : {}", e))?;

        Ok(())
    }

    pub fn uninstall() -> Result<()> {
        std::process::Command::new("reg")
            .args(["delete", "HKCU\\Software\\Classes\\nvim", "/f"])
            .status()
            .ok();
        Ok(())
    }
}