//! Tauri command surface. Every JS-callable function lives here.

use base64::{engine::general_purpose::STANDARD as B64, Engine as _};
use tauri::{AppHandle, Manager};

use crate::pty::{list_wsl_distros, PaneId, PtyManager};

const WORKSPACE_FILE: &str = "workspace.json";

#[tauri::command]
pub async fn list_distros() -> Result<Vec<String>, String> {
    list_wsl_distros().map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn spawn_pane(
    app: AppHandle,
    manager: tauri::State<'_, PtyManager>,
    distro: Option<String>,
    cwd: Option<String>,
    cols: u16,
    rows: u16,
) -> Result<PaneId, String> {
    manager
        .spawn_wsl(app, distro, cwd, cols, rows)
        .map_err(|e| e.to_string())
}

/// `data_b64` is base64-encoded UTF-8 bytes (xterm.js's `onData` emits
/// strings; the frontend encodes before sending).
#[tauri::command]
pub async fn write_to_pane(
    manager: tauri::State<'_, PtyManager>,
    id: PaneId,
    data_b64: String,
) -> Result<(), String> {
    let bytes = B64
        .decode(data_b64.as_bytes())
        .map_err(|e| format!("base64 decode: {e}"))?;
    manager.write(id, &bytes).map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn resize_pane(
    manager: tauri::State<'_, PtyManager>,
    id: PaneId,
    cols: u16,
    rows: u16,
) -> Result<(), String> {
    manager.resize(id, cols, rows).map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn kill_pane(
    manager: tauri::State<'_, PtyManager>,
    id: PaneId,
) -> Result<(), String> {
    manager.kill(id).map_err(|e| e.to_string())
}

/// Write the workspace JSON to `%APPDATA%\com.megaproxy.tiletopia\workspace.json`.
/// Writes to a `.tmp` and renames over the real file so a crash mid-write
/// can't leave a partial file readable.
#[tauri::command]
pub async fn save_workspace(app: AppHandle, json: String) -> Result<(), String> {
    let dir = app
        .path()
        .app_config_dir()
        .map_err(|e| format!("app_config_dir: {e}"))?;
    std::fs::create_dir_all(&dir).map_err(|e| format!("create_dir_all: {e}"))?;
    let path = dir.join(WORKSPACE_FILE);
    let tmp = dir.join(format!("{WORKSPACE_FILE}.tmp"));
    std::fs::write(&tmp, json.as_bytes()).map_err(|e| format!("write tmp: {e}"))?;
    // `std::fs::rename` is atomic on Unix and uses MoveFileEx with
    // REPLACE_EXISTING on Windows (>= Rust 1.50).
    std::fs::rename(&tmp, &path).map_err(|e| format!("rename: {e}"))?;
    Ok(())
}

/// Read the workspace JSON. Returns `None` if the file doesn't exist yet
/// (first launch).
#[tauri::command]
pub async fn load_workspace(app: AppHandle) -> Result<Option<String>, String> {
    let dir = app
        .path()
        .app_config_dir()
        .map_err(|e| format!("app_config_dir: {e}"))?;
    let path = dir.join(WORKSPACE_FILE);
    if !path.exists() {
        return Ok(None);
    }
    let s = std::fs::read_to_string(&path).map_err(|e| format!("read: {e}"))?;
    Ok(Some(s))
}
