use std::{
    collections::HashMap,
    fs,
    path::{Path, PathBuf},
    process::Command,
    sync::Mutex,
};

// Suppress the console window that Windows shows when spawning child processes.
#[cfg(target_os = "windows")]
trait NoWindow {
    fn no_window(&mut self) -> &mut Self;
}
#[cfg(target_os = "windows")]
impl NoWindow for Command {
    fn no_window(&mut self) -> &mut Self {
        use std::os::windows::process::CommandExt;
        self.creation_flags(0x08000000) // CREATE_NO_WINDOW
    }
}

use serde::{Deserialize, Serialize};
use tauri::{
    menu::{MenuBuilder, MenuItemBuilder, PredefinedMenuItem},
    tray::{TrayIconBuilder, TrayIconEvent},
    AppHandle, Emitter, Manager, State, WebviewWindow,
};

// ─── Data types ────────────────────────────────────────────────────────────────

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
struct AppItem {
    id: String,
    name: String,
    target_path: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    shortcut_path: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    icon_key: Option<String>,
    category: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    description: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
struct Category {
    id: String,
    name: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
struct Config {
    apps: Vec<AppItem>,
    categories: Vec<Category>,
}

// Snapshot stored when a context menu is shown so the app-level event handler
// can act on it without any extra state passing.
#[derive(Clone)]
struct ContextMenuSnapshot {
    app_id: String,
    open_path: String,        // shortcut if exists, else target
    shortcut_path: Option<String>,
    target_path: String,
}

// ─── App state ─────────────────────────────────────────────────────────────────

struct AppState {
    config_path: PathBuf,
    shortcuts_dir: PathBuf,
    icons_dir: PathBuf,
    context_menu: Mutex<Option<ContextMenuSnapshot>>,
}

// ─── Helpers ───────────────────────────────────────────────────────────────────

fn sanitize_filename(input: &str) -> String {
    let bad = "<>:\"/\\|?*";
    let mut out = String::new();
    for ch in input.chars() {
        if (ch as u32) < 32 || bad.contains(ch) {
            out.push('_');
        } else {
            out.push(ch);
        }
    }
    let s: String = out.split_whitespace().collect::<Vec<_>>().join(" ");
    let trimmed = s.trim().to_string();
    if trimmed.is_empty() {
        "item".to_string()
    } else {
        trimmed.chars().take(80).collect()
    }
}

fn ensure_dir(p: &Path) {
    let _ = fs::create_dir_all(p);
}

/// Read a PNG file from the icon cache and return it as a base64 data URL.
fn png_to_data_url(path: &Path) -> Option<String> {
    use base64::{engine::general_purpose::STANDARD, Engine};
    fs::read(path)
        .ok()
        .map(|data| format!("data:image/png;base64,{}", STANDARD.encode(&data)))
}

/// Resolve a .lnk shortcut to its target path via PowerShell.
fn resolve_lnk_target(lnk: &str) -> Option<String> {
    if !lnk.to_lowercase().ends_with(".lnk") {
        return None;
    }
    let escaped = lnk.replace('\'', "''");
    let ps = format!(
        "(New-Object -ComObject WScript.Shell).CreateShortcut('{}').TargetPath",
        escaped
    );
    let out = Command::new("powershell")
        .args(["-NoProfile", "-Command", &format!("& {{ {} }}", ps)])
        .no_window()
        .output()
        .ok()?;
    let target = String::from_utf8_lossy(&out.stdout).trim().to_string();
    if !target.is_empty() && Path::new(&target).exists() {
        Some(target)
    } else {
        None
    }
}

/// Extract the file's associated icon to a PNG using .NET System.Drawing.
fn extract_icon_with_powershell(src: &str, dest: &str) -> bool {
    let src_esc = src.replace('\'', "''");
    let dst_esc = dest.replace('\'', "''");
    let script = format!(
        "Add-Type -AssemblyName System.Drawing\n\
         $inPath = '{}'\n\
         $outPath = '{}'\n\
         try {{\n\
           $icon = [System.Drawing.Icon]::ExtractAssociatedIcon($inPath)\n\
           if ($icon) {{\n\
             $bmp = $icon.ToBitmap()\n\
             $bmp.Save($outPath, [System.Drawing.Imaging.ImageFormat]::Png)\n\
             $bmp.Dispose()\n\
             $icon.Dispose()\n\
           }}\n\
         }} catch {{ exit 1 }}\n",
        src_esc, dst_esc
    );
    let script_path = std::env::temp_dir().join("sprout-extract-icon.ps1");
    if fs::write(&script_path, &script).is_err() {
        return false;
    }
    let ok = Command::new("powershell")
        .args([
            "-NoProfile",
            "-ExecutionPolicy",
            "Bypass",
            "-File",
            script_path.to_str().unwrap_or(""),
        ])
        .no_window()
        .output()
        .map(|o| o.status.success() && Path::new(dest).exists())
        .unwrap_or(false);
    let _ = fs::remove_file(&script_path);
    ok
}

/// Ensure an icon PNG exists in the cache.
/// Returns a base64 data URL (`data:image/png;base64,...`) on success.
fn ensure_icon_file(icon_key: &str, from_path: &str, icons_dir: &Path) -> Option<String> {
    ensure_dir(icons_dir);
    let out_path = icons_dir.join(format!("{}.png", sanitize_filename(icon_key)));

    if out_path.exists() {
        // Already cached — read and return data URL.
        return png_to_data_url(&out_path);
    }

    if !Path::new(from_path).exists() {
        return None;
    }

    let icon_src = if from_path.to_lowercase().ends_with(".lnk") {
        resolve_lnk_target(from_path).unwrap_or_else(|| from_path.to_string())
    } else {
        from_path.to_string()
    };

    #[cfg(target_os = "windows")]
    if extract_icon_with_powershell(&icon_src, out_path.to_str().unwrap_or("")) {
        return png_to_data_url(&out_path);
    }

    None
}

// ─── Commands ──────────────────────────────────────────────────────────────────

#[tauri::command]
fn launch_app(path: String) -> Result<(), String> {
    if !Path::new(&path).exists() {
        return Err("应用程序不存在".into());
    }
    #[cfg(target_os = "windows")]
    {
        Command::new("cmd")
            .args(["/C", "start", "", &path])
            .no_window()
            .spawn()
            .map(|_| ())
            .map_err(|e| format!("启动失败: {}", e))
    }
    #[cfg(not(target_os = "windows"))]
    {
        Command::new("open")
            .arg(&path)
            .spawn()
            .map(|_| ())
            .map_err(|e| format!("启动失败: {}", e))
    }
}

#[tauri::command]
fn select_target(app: AppHandle) -> Option<String> {
    use tauri_plugin_dialog::DialogExt;
    app.dialog()
        .file()
        .add_filter("可执行与脚本", &["exe", "bat", "cmd", "ps1", "vbs"])
        .add_filter("所有文件", &["*"])
        .blocking_pick_file()
        .map(|f| f.to_string())
}

#[tauri::command]
fn select_folder(app: AppHandle) -> Option<String> {
    use tauri_plugin_dialog::DialogExt;
    app.dialog()
        .file()
        .blocking_pick_folder()
        .map(|f| f.to_string())
}

#[tauri::command]
fn select_single_executable(app: AppHandle) -> Option<String> {
    use tauri_plugin_dialog::DialogExt;
    app.dialog()
        .file()
        .add_filter("可执行与脚本", &["exe", "bat", "cmd", "ps1", "vbs"])
        .add_filter("所有文件", &["*"])
        .blocking_pick_file()
        .map(|f| f.to_string())
}

#[tauri::command]
fn select_exe_file(app: AppHandle) -> Option<String> {
    use tauri_plugin_dialog::DialogExt;
    app.dialog()
        .file()
        .add_filter("可执行文件", &["exe"])
        .blocking_pick_file()
        .map(|f| f.to_string())
}

#[derive(Serialize)]
struct ExecutableEntry {
    path: String,
    name: String,
}

#[tauri::command]
fn select_executables(app: AppHandle) -> Vec<ExecutableEntry> {
    use tauri_plugin_dialog::DialogExt;
    let paths = app
        .dialog()
        .file()
        .add_filter("可执行与脚本", &["exe", "bat", "cmd", "ps1", "vbs"])
        .add_filter("所有文件", &["*"])
        .blocking_pick_files()
        .unwrap_or_default();

    let exe_exts = [".exe", ".bat", ".cmd", ".ps1", ".vbs"];
    paths
        .into_iter()
        .map(|fp| {
            let p = fp.to_string();
            let base = Path::new(&p)
                .file_name()
                .and_then(|f| f.to_str())
                .unwrap_or("")
                .to_string();
            let name = {
                let lower = base.to_lowercase();
                let stripped = exe_exts
                    .iter()
                    .find(|ext| lower.ends_with(*ext))
                    .map(|ext| &base[..base.len() - ext.len()])
                    .unwrap_or(&base);
                stripped.to_string()
            };
            ExecutableEntry { path: p, name }
        })
        .collect()
}

#[derive(Serialize)]
struct ShortcutResult {
    success: bool,
    #[serde(rename = "shortcutPath")]
    shortcut_path: String,
}

#[tauri::command]
fn create_shortcut(
    id: String,
    target_path: String,
    name: Option<String>,
    state: State<'_, AppState>,
) -> Result<ShortcutResult, String> {
    if !Path::new(&target_path).exists() {
        return Err("目标不存在".into());
    }
    ensure_dir(&state.shortcuts_dir);

    let lnk_path = state
        .shortcuts_dir
        .join(format!("{}.lnk", sanitize_filename(&id)));

    #[cfg(target_os = "windows")]
    {
        let is_dir = fs::metadata(&target_path)
            .map(|m| m.is_dir())
            .unwrap_or(false);
        let cwd = if is_dir {
            String::new()
        } else {
            Path::new(&target_path)
                .parent()
                .and_then(|p| p.to_str())
                .unwrap_or("")
                .to_string()
        };
        let desc = name.unwrap_or_else(|| {
            Path::new(&target_path)
                .file_name()
                .and_then(|f| f.to_str())
                .unwrap_or("")
                .to_string()
        });

        let lnk_esc = lnk_path.to_str().unwrap_or("").replace('\'', "''");
        let target_esc = target_path.replace('\'', "''");
        let cwd_esc = cwd.replace('\'', "''");
        let desc_esc = desc.replace('\'', "''");

        let ps = format!(
            "$ws = New-Object -ComObject WScript.Shell; \
             $s = $ws.CreateShortcut('{}'); \
             $s.TargetPath = '{}'; \
             $s.WorkingDirectory = '{}'; \
             $s.Description = '{}'; \
             $s.Save()",
            lnk_esc, target_esc, cwd_esc, desc_esc
        );
        let out = Command::new("powershell")
            .args(["-NoProfile", "-Command", &ps])
            .no_window()
            .output()
            .map_err(|e| format!("创建快捷方式失败: {}", e))?;

        if !out.status.success() || !lnk_path.exists() {
            return Err("创建快捷方式失败".into());
        }
        return Ok(ShortcutResult {
            success: true,
            shortcut_path: lnk_path.to_string_lossy().into_owned(),
        });
    }

    // Non-Windows fallback: return the target path as-is.
    #[allow(unreachable_code)]
    Ok(ShortcutResult { success: true, shortcut_path: target_path })
}

#[derive(Serialize)]
struct IconResult {
    success: bool,
    #[serde(rename = "iconUrl")]
    icon_url: Option<String>,
}

#[tauri::command]
fn ensure_icon(
    icon_key: String,
    from_path: String,
    state: State<'_, AppState>,
) -> IconResult {
    if icon_key.is_empty() || from_path.is_empty() {
        return IconResult { success: false, icon_url: None };
    }
    match ensure_icon_file(&icon_key, &from_path, &state.icons_dir) {
        Some(url) => IconResult { success: true, icon_url: Some(url) },
        None => IconResult { success: false, icon_url: None },
    }
}

/// Read all cached icon files at once and return them as base64 data URLs.
/// Keys that don't have a cached PNG are simply omitted from the result.
#[tauri::command]
fn batch_read_icons(
    icon_keys: Vec<String>,
    state: State<'_, AppState>,
) -> HashMap<String, String> {
    icon_keys
        .into_iter()
        .filter_map(|key| {
            let path = state.icons_dir.join(format!("{}.png", sanitize_filename(&key)));
            png_to_data_url(&path).map(|url| (key, url))
        })
        .collect()
}

#[tauri::command]
fn delete_app_artifacts(
    shortcut_path: Option<String>,
    icon_key: Option<String>,
    state: State<'_, AppState>,
) -> serde_json::Value {
    if let Some(p) = shortcut_path {
        let pb = PathBuf::from(&p);
        if pb.extension().and_then(|e| e.to_str()) == Some("lnk")
            && pb.starts_with(&state.shortcuts_dir)
        {
            let _ = fs::remove_file(&pb);
        }
    }
    if let Some(key) = icon_key {
        let f = state.icons_dir.join(format!("{}.png", sanitize_filename(&key)));
        if f.starts_with(&state.icons_dir) {
            let _ = fs::remove_file(&f);
        }
    }
    serde_json::json!({ "success": true })
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct ContextMenuPayload {
    id: String,
    name: String,
    shortcut_path: Option<String>,
    target_path: String,
}

#[tauri::command]
fn show_app_context_menu(
    app: AppHandle,
    window: WebviewWindow,
    payload: ContextMenuPayload,
    state: State<'_, AppState>,
) -> Result<(), String> {
    let open_path = payload
        .shortcut_path
        .as_deref()
        .filter(|p| Path::new(p).exists())
        .unwrap_or(&payload.target_path)
        .to_string();

    let has_shortcut = payload
        .shortcut_path
        .as_deref()
        .map(|p| Path::new(p).exists())
        .unwrap_or(false);

    // Store context so the app-level menu event handler can act on it.
    *state.context_menu.lock().unwrap() = Some(ContextMenuSnapshot {
        app_id: payload.id.clone(),
        open_path: open_path.clone(),
        shortcut_path: payload.shortcut_path.clone(),
        target_path: payload.target_path.clone(),
    });

    let open_item =
        MenuItemBuilder::with_id("ctx_open", format!("打开: {}", payload.name))
            .build(&app)
            .map_err(|e| e.to_string())?;
    let runas_item =
        MenuItemBuilder::with_id("ctx_runas", "以管理员身份运行")
            .build(&app)
            .map_err(|e| e.to_string())?;
    let show_shortcut =
        MenuItemBuilder::with_id("ctx_show_shortcut", "在资源管理器中显示快捷方式")
            .enabled(has_shortcut)
            .build(&app)
            .map_err(|e| e.to_string())?;
    let show_target =
        MenuItemBuilder::with_id("ctx_show_target", "在资源管理器中显示目标")
            .build(&app)
            .map_err(|e| e.to_string())?;
    let sep = PredefinedMenuItem::separator(&app).map_err(|e| e.to_string())?;
    let edit_item =
        MenuItemBuilder::with_id("ctx_edit", "编辑")
            .build(&app)
            .map_err(|e| e.to_string())?;
    let remove_item =
        MenuItemBuilder::with_id("ctx_remove", "从启动器移除")
            .build(&app)
            .map_err(|e| e.to_string())?;

    let menu = MenuBuilder::new(&app)
        .item(&open_item)
        .item(&runas_item)
        .item(&show_shortcut)
        .item(&show_target)
        .item(&sep)
        .item(&edit_item)
        .item(&remove_item)
        .build()
        .map_err(|e| e.to_string())?;

    window.popup_menu(&menu).map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn get_exe_icon(exe_path: String, state: State<'_, AppState>) -> Option<String> {
    if !Path::new(&exe_path).exists() {
        return None;
    }
    let key = format!("exe_{}", sanitize_filename(&exe_path));
    ensure_icon_file(&key, &exe_path, &state.icons_dir)
}

#[tauri::command]
fn load_config(state: State<'_, AppState>) -> Result<Config, String> {
    if !state.config_path.exists() {
        let default = Config {
            apps: vec![],
            categories: vec![
                Category { id: "default".into(), name: "默认分类".into() },
                Category { id: "productivity".into(), name: "办公软件".into() },
                Category { id: "entertainment".into(), name: "娱乐软件".into() },
                Category { id: "development".into(), name: "开发工具".into() },
            ],
        };
        let json = serde_json::to_string_pretty(&default).map_err(|e| e.to_string())?;
        fs::write(&state.config_path, &json).map_err(|e| e.to_string())?;
        return Ok(default);
    }
    let data = fs::read_to_string(&state.config_path).map_err(|e| e.to_string())?;
    serde_json::from_str(&data).map_err(|e| e.to_string())
}

#[tauri::command]
fn save_config(config: Config, state: State<'_, AppState>) -> Result<(), String> {
    let json = serde_json::to_string_pretty(&config).map_err(|e| e.to_string())?;
    fs::write(&state.config_path, &json).map_err(|e| e.to_string())
}

#[tauri::command]
fn export_config(app: AppHandle, state: State<'_, AppState>) -> Result<serde_json::Value, String> {
    use tauri_plugin_dialog::DialogExt;
    let dest = app
        .dialog()
        .file()
        .set_file_name("sprout-launcher-config.json")
        .add_filter("JSON文件", &["json"])
        .blocking_save_file();

    let Some(dest_path) = dest else {
        return Ok(serde_json::json!({ "success": false }));
    };
    if !state.config_path.exists() {
        return Ok(serde_json::json!({ "success": false }));
    }
    let data = fs::read_to_string(&state.config_path).map_err(|e| e.to_string())?;
    fs::write(dest_path.to_string(), &data).map_err(|e| e.to_string())?;
    Ok(serde_json::json!({ "success": true }))
}

#[tauri::command]
fn import_config(
    app: AppHandle,
    state: State<'_, AppState>,
) -> Result<serde_json::Value, String> {
    use tauri_plugin_dialog::DialogExt;
    let src = app
        .dialog()
        .file()
        .add_filter("JSON文件", &["json"])
        .blocking_pick_file();

    let Some(src_path) = src else {
        return Ok(serde_json::json!({ "success": false }));
    };
    let data = fs::read_to_string(src_path.to_string()).map_err(|e| e.to_string())?;
    let config: Config =
        serde_json::from_str(&data).map_err(|_| "无效的配置文件格式".to_string())?;
    fs::write(&state.config_path, &data).map_err(|e| e.to_string())?;
    Ok(serde_json::json!({ "success": true, "config": config }))
}

#[tauri::command]
fn get_auto_launch(app: AppHandle) -> bool {
    use tauri_plugin_autostart::ManagerExt;
    app.autolaunch().is_enabled().unwrap_or(false)
}

#[tauri::command]
fn set_auto_launch(app: AppHandle, enabled: bool) -> Result<(), String> {
    use tauri_plugin_autostart::ManagerExt;
    if enabled {
        app.autolaunch().enable().map_err(|e| e.to_string())
    } else {
        app.autolaunch().disable().map_err(|e| e.to_string())
    }
}

#[tauri::command]
fn minimize_window(window: WebviewWindow) {
    let _ = window.minimize();
}

#[tauri::command]
fn maximize_window(window: WebviewWindow) {
    if window.is_maximized().unwrap_or(false) {
        let _ = window.unmaximize();
    } else {
        let _ = window.maximize();
    }
}

#[tauri::command]
fn close_window(window: WebviewWindow) {
    let _ = window.hide();
}

#[derive(Deserialize)]
struct RefreshItem {
    id: String,
    #[serde(rename = "targetPath", default)]
    target_path: Option<String>,
    #[serde(rename = "shortcutPath", default)]
    shortcut_path: Option<String>,
    #[serde(rename = "iconKey", default)]
    icon_key: Option<String>,
}

#[tauri::command]
fn refresh_icons(
    apps: Vec<RefreshItem>,
    state: State<'_, AppState>,
) -> Vec<serde_json::Value> {
    apps.into_iter()
        .map(|item| {
            let id = item.id.clone();
            let from = item
                .shortcut_path
                .filter(|p| Path::new(p).exists())
                .or_else(|| item.target_path.filter(|p| Path::new(p).exists()));

            let Some(from_path) = from else {
                return serde_json::json!({ "id": id, "success": false, "reason": "文件不存在" });
            };

            let icon_key = item.icon_key.unwrap_or_else(|| id.clone());
            // Delete cached file to force re-extraction.
            let cached = state
                .icons_dir
                .join(format!("{}.png", sanitize_filename(&icon_key)));
            let _ = fs::remove_file(&cached);

            match ensure_icon_file(&icon_key, &from_path, &state.icons_dir) {
                Some(url) => {
                    serde_json::json!({ "id": id, "success": true, "iconUrl": url })
                }
                None => {
                    serde_json::json!({ "id": id, "success": false, "reason": "无法获取图标" })
                }
            }
        })
        .collect()
}

// ─── Entry point ───────────────────────────────────────────────────────────────

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        // ── Plugins ──────────────────────────────────────────────────────────
        .plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
            if let Some(w) = app.get_webview_window("main") {
                let _ = w.show();
                let _ = w.set_focus();
            }
        }))
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_autostart::init(
            tauri_plugin_autostart::MacosLauncher::LaunchAgent,
            None,
        ))
        .plugin(if cfg!(debug_assertions) {
            tauri_plugin_log::Builder::default()
                .level(log::LevelFilter::Info)
                .build()
        } else {
            tauri_plugin_log::Builder::default()
                .level(log::LevelFilter::Warn)
                .build()
        })
        // ── Setup ─────────────────────────────────────────────────────────────
        .setup(|app| {
            let app_data = app.path().app_data_dir()?;
            let config_path = app_data.join("launcher-config.json");
            let data_dir = app_data.join("data");
            let shortcuts_dir = data_dir.join("shortcuts");
            let icons_dir = data_dir.join("icons");

            ensure_dir(&shortcuts_dir);
            ensure_dir(&icons_dir);
            ensure_dir(&app_data);

            app.manage(AppState {
                config_path,
                shortcuts_dir,
                icons_dir,
                context_menu: Mutex::new(None),
            });

            // System tray — use public/logo.png embedded at compile time.
            let logo_bytes = include_bytes!("../../public/logo.png");
            let logo_icon = tauri::image::Image::from_bytes(logo_bytes)?;

            let tray_menu = MenuBuilder::new(app)
                .text("tray_show", "显示主窗口")
                .separator()
                .text("tray_quit", "退出 SproutLauncher")
                .build()?;

            TrayIconBuilder::new()
                .icon(logo_icon)
                .tooltip("SproutLauncher")
                .menu(&tray_menu)
                .on_tray_icon_event(|tray, event| {
                    // Only show the window on left-click.
                    // Right-click shows the context menu automatically (OS handles it).
                    if let TrayIconEvent::Click {
                        button: tauri::tray::MouseButton::Left,
                        button_state: tauri::tray::MouseButtonState::Up,
                        ..
                    } = event
                    {
                        let app = tray.app_handle();
                        if let Some(w) = app.get_webview_window("main") {
                            let _ = w.show();
                            let _ = w.set_focus();
                        }
                    }
                })
                .build(app)?;

            // Single app-level handler for ALL menu events (tray + context menu).
            let app_handle = app.handle().clone();
            app.on_menu_event(move |app, event| {
                match event.id().as_ref() {
                    // ── Tray menu ─────────────────────────────────────────────
                    "tray_show" => {
                        if let Some(w) = app.get_webview_window("main") {
                            let _ = w.show();
                            let _ = w.set_focus();
                        }
                    }
                    "tray_quit" => {
                        // Use process::exit to bypass CloseRequested handler.
                        std::process::exit(0);
                    }
                    // ── App context menu ──────────────────────────────────────
                    id => {
                        let state = app_handle.state::<AppState>();
                        let snapshot = state.context_menu.lock().unwrap().clone();
                        let Some(snap) = snapshot else { return };
                        match id {
                            "ctx_open" => {
                                #[cfg(target_os = "windows")]
                                let _ = Command::new("cmd")
                                    .args(["/C", "start", "", &snap.open_path])
                                    .no_window()
                                    .spawn();
                            }
                            "ctx_runas" => {
                                #[cfg(target_os = "windows")]
                                {
                                    let esc = snap.open_path.replace('\'', "''");
                                    let _ = Command::new("powershell")
                                        .args([
                                            "-NoProfile",
                                            "-Command",
                                            &format!(
                                                "Start-Process -FilePath '{}' -Verb RunAs",
                                                esc
                                            ),
                                        ])
                                        .no_window()
                                        .spawn();
                                }
                            }
                            "ctx_show_shortcut" => {
                                if let Some(ref sp) = snap.shortcut_path {
                                    #[cfg(target_os = "windows")]
                                    let _ = Command::new("explorer")
                                        .args(["/select,", sp])
                                        .no_window()
                                        .spawn();
                                }
                            }
                            "ctx_show_target" => {
                                #[cfg(target_os = "windows")]
                                let _ = Command::new("explorer")
                                    .args(["/select,", &snap.target_path])
                                    .no_window()
                                    .spawn();
                            }
                            "ctx_edit" => {
                                if let Some(w) = app_handle.get_webview_window("main") {
                                    let _ = w.emit(
                                        "app-context-action",
                                        serde_json::json!({
                                            "action": "edit",
                                            "id": snap.app_id
                                        }),
                                    );
                                }
                            }
                            "ctx_remove" => {
                                if let Some(w) = app_handle.get_webview_window("main") {
                                    let _ = w.emit(
                                        "app-context-action",
                                        serde_json::json!({
                                            "action": "remove",
                                            "id": snap.app_id
                                        }),
                                    );
                                }
                            }
                            _ => {}
                        }
                    }
                }
            });

            Ok(())
        })
        // ── Close → hide to tray ──────────────────────────────────────────────
        .on_window_event(|window, event| {
            if let tauri::WindowEvent::CloseRequested { api, .. } = event {
                api.prevent_close();
                let _ = window.hide();
            }
        })
        // ── Commands ──────────────────────────────────────────────────────────
        .invoke_handler(tauri::generate_handler![
            launch_app,
            select_target,
            select_folder,
            select_single_executable,
            select_exe_file,
            select_executables,
            create_shortcut,
            ensure_icon,
            batch_read_icons,
            delete_app_artifacts,
            show_app_context_menu,
            get_exe_icon,
            load_config,
            save_config,
            export_config,
            import_config,
            get_auto_launch,
            set_auto_launch,
            minimize_window,
            maximize_window,
            close_window,
            refresh_icons,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
