//! TaskLens desktop shell.
//!
//! The shell is deliberately thin: it forwards the frontend's vault commands
//! into `tasklens-core` (the same table the CLI uses), watches the vault for
//! external edits, and hosts the Pi agent session.

mod watcher;

use std::sync::{Arc, Mutex};

use serde_json::{json, Value};
use tauri::{AppHandle, Emitter, State};
use tasklens_core::agent::{self, AgentConfig, AgentSession};
use tasklens_core::dispatch;
use tasklens_core::settings::{self, Settings};

/// Single entry point for every vault command. `command` names an entry in
/// `dispatch::COMMANDS`; `args` uses the same shape the CLI accepts.
#[tauri::command]
fn vault_command(command: String, args: Option<Value>) -> Result<Value, String> {
    dispatch::run(&command, &args.unwrap_or(Value::Null), None).map_err(|error| error.0)
}

#[tauri::command]
fn watch_vault(
    app: AppHandle,
    state: State<'_, Arc<watcher::VaultWatcher>>,
    path: String,
) -> Result<(), String> {
    state.watch(app, &path)
}

/// Holds the live agent process between IPC calls.
#[derive(Default)]
struct AgentState {
    session: Mutex<Option<AgentSession>>,
}

#[tauri::command]
fn agent_start(
    app: AppHandle,
    state: State<'_, Arc<AgentState>>,
    config: Value,
) -> Result<Value, String> {
    let mut slot = state.session.lock().map_err(|_| "agent 状态不可用".to_string())?;
    if let Some(existing) = slot.take() {
        existing.stop();
    }

    let settings = Settings::load();
    let config = AgentConfig::resolve(&config, &settings, settings::resolve_root(None).ok().as_deref());

    // Agent events are streamed to the renderer as they arrive.
    let emitter = app.clone();
    let emit: Arc<dyn Fn(String) + Send + Sync> = Arc::new(move |line: String| {
        let data: Value = serde_json::from_str(&line).unwrap_or(Value::String(line));
        let _ = emitter.emit("agent-event", data);
    });

    let session = AgentSession::start(&config, emit).map_err(|error| error.0)?;
    let payload = json!({
        "command": session.command,
        "embedded": session.embedded,
        "vault": config.vault,
        "missingExtensions": config.missing_extensions(),
    });
    *slot = Some(session);
    Ok(payload)
}

#[tauri::command]
fn agent_send(state: State<'_, Arc<AgentState>>, line: String) -> Result<(), String> {
    let slot = state.session.lock().map_err(|_| "agent 状态不可用".to_string())?;
    match slot.as_ref() {
        Some(session) => session.send_line(&line).map_err(|error| error.0),
        None => Err("agent 尚未启动".to_string()),
    }
}

#[tauri::command]
fn agent_stop(state: State<'_, Arc<AgentState>>) -> Result<(), String> {
    let mut slot = state.session.lock().map_err(|_| "agent 状态不可用".to_string())?;
    if let Some(session) = slot.take() {
        session.stop();
    }
    Ok(())
}

/// Where the agent executable resolves to — used by the settings UI.
#[tauri::command]
fn agent_probe(command: String) -> Value {
    let settings = Settings::load();
    let requested = command.trim();
    let name = if requested.is_empty() {
        settings.ai.runtime.clone()
    } else {
        requested.to_string()
    };
    // `auto` has no single answer: report the runtime that would actually run.
    let name = if name == "auto" { "bun" } else { name.as_str() }.to_string();
    let (path, needs_shell) = agent::resolve_command(&name);
    let exists = path.is_file();
    // Fall back to node when bun is requested but missing.
    if !exists && name == "bun" {
        let (node_path, node_shell) = agent::resolve_command("node");
        if node_path.is_file() {
            return json!({
                "command": "node",
                "path": node_path.to_string_lossy(),
                "exists": true,
                "needsShell": node_shell,
            });
        }
        let _ = needs_shell;
    }
    json!({
        "command": name,
        "path": path.to_string_lossy(),
        "exists": exists,
        "needsShell": needs_shell,
    })
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_opener::init())
        .manage(Arc::new(watcher::VaultWatcher::new()))
        .manage(Arc::new(AgentState::default()))
        .invoke_handler(tauri::generate_handler![
            vault_command,
            watch_vault,
            agent_start,
            agent_send,
            agent_stop,
            agent_probe
        ])
        .run(tauri::generate_context!())
        .expect("TaskLens 启动失败");
}