// ABOUTME: Core library for the Seren Desktop Tauri application.
// ABOUTME: Contains Tauri commands and the application run function.

use log::info;
use tauri::{Emitter, Manager, RunEvent, WindowEvent};
use tauri_plugin_log::{Target, TargetKind};
use tauri_plugin_store::StoreExt;

pub mod commands {
    pub mod audio;
    pub mod auth;
    pub mod chat;
    pub mod claude_memory;
    pub mod cli_installer;
    pub mod context_intelligence;
    pub mod employees_archive;
    pub mod gateway_http;
    pub mod history_sync;
    pub mod indexing;
    pub mod memory;
    pub mod model_context_cache;
    pub mod orchestrator;
    pub mod provider_runtime;
    pub mod session;
    pub mod updater;
    pub mod web;
}

pub mod services {
    pub mod chunker;
    pub mod context_intelligence;
    pub mod database;
    pub mod history_sync;
    pub mod indexer;
    pub mod vector_store;
}

pub mod audio;
mod auth;
mod claude_memory;
mod claude_setup;
mod embedded_runtime;
mod files;
mod mcp;
pub mod messaging;
mod oauth;
mod oauth_callback_server;
mod orchestrator;
mod path_util;
mod pdf;
mod polymarket;
mod provider_runtime;
mod secret_broker;
mod shell;
mod skills;
mod support;
mod sync;
mod terminal;
mod tray;
mod wallet;

const AUTH_STORE: &str = "auth.json";
const TOKEN_KEY: &str = "token";
const REFRESH_TOKEN_KEY: &str = "refresh_token";
const PROVIDERS_STORE: &str = "providers.json";
const OAUTH_STORE: &str = "oauth.json";

#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}

/// Fetch a URL with Bearer auth and return the redirect Location header.
/// Used for OAuth authorize endpoints that return 302 redirects.
#[tauri::command]
async fn get_oauth_redirect_url(url: String, bearer_token: String) -> Result<String, String> {
    let client = reqwest::Client::builder()
        .redirect(reqwest::redirect::Policy::none())
        .build()
        .map_err(|e| format!("Failed to create HTTP client: {}", e))?;

    let response = client
        .get(&url)
        .header("Authorization", format!("Bearer {}", bearer_token))
        .send()
        .await
        .map_err(|e| format!("Request failed: {}", e))?;

    if response.status().is_redirection() {
        let location = response
            .headers()
            .get("location")
            .and_then(|v| v.to_str().ok())
            .ok_or("Redirect response missing Location header")?;
        return Ok(location.to_string());
    }

    let status = response.status();
    let body_text = response.text().await.unwrap_or_default();

    // If not a redirect, try to parse JSON with authorize_url
    if status.is_success() {
        if let Ok(body) = serde_json::from_str::<serde_json::Value>(&body_text) {
            if let Some(url) = body.get("authorize_url").or(body.get("url")) {
                if let Some(url_str) = url.as_str() {
                    return Ok(url_str.to_string());
                }
            }
        }
    }

    let truncated = if body_text.len() > 200 {
        format!("{}...[truncated]", &body_text[..200])
    } else {
        body_text.clone()
    };
    log::error!("[OAuth] {} response from Gateway: {}", status, truncated);
    Err(format!(
        "Unexpected response status: {} - {}",
        status, body_text
    ))
}

#[tauri::command]
fn store_token(app: tauri::AppHandle, token: String) -> Result<(), String> {
    let store = app.store(AUTH_STORE).map_err(|e| e.to_string())?;
    store.set(TOKEN_KEY, serde_json::json!(token));
    store.save().map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn get_token(app: tauri::AppHandle) -> Result<Option<String>, String> {
    let store = app.store(AUTH_STORE).map_err(|e| e.to_string())?;
    let token = store
        .get(TOKEN_KEY)
        .and_then(|v| v.as_str().map(|s| s.to_string()));
    Ok(token)
}

#[tauri::command]
fn clear_token(app: tauri::AppHandle) -> Result<(), String> {
    let store = app.store(AUTH_STORE).map_err(|e| e.to_string())?;
    store.delete(TOKEN_KEY);
    store.save().map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn store_refresh_token(app: tauri::AppHandle, token: String) -> Result<(), String> {
    let store = app.store(AUTH_STORE).map_err(|e| e.to_string())?;
    store.set(REFRESH_TOKEN_KEY, serde_json::json!(token));
    store.save().map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn get_refresh_token(app: tauri::AppHandle) -> Result<Option<String>, String> {
    let store = app.store(AUTH_STORE).map_err(|e| e.to_string())?;
    let token = store
        .get(REFRESH_TOKEN_KEY)
        .and_then(|v| v.as_str().map(|s| s.to_string()));
    Ok(token)
}

#[tauri::command]
fn clear_refresh_token(app: tauri::AppHandle) -> Result<(), String> {
    let store = app.store(AUTH_STORE).map_err(|e| e.to_string())?;
    store.delete(REFRESH_TOKEN_KEY);
    store.save().map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn get_setting(
    app: tauri::AppHandle,
    store: String,
    key: String,
) -> Result<Option<String>, String> {
    let store_handle = app.store(&store).map_err(|e| e.to_string())?;
    let value = store_handle
        .get(&key)
        .and_then(|v| v.as_str().map(|s| s.to_string()));
    Ok(value)
}

#[tauri::command]
fn set_setting(
    app: tauri::AppHandle,
    store: String,
    key: String,
    value: String,
) -> Result<(), String> {
    let store_handle = app.store(&store).map_err(|e| e.to_string())?;
    store_handle.set(&key, serde_json::json!(value));
    store_handle.save().map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn store_provider_key(
    app: tauri::AppHandle,
    provider: String,
    api_key: String,
) -> Result<(), String> {
    let store = app.store(PROVIDERS_STORE).map_err(|e| e.to_string())?;
    store.set(&provider, serde_json::json!(api_key));
    store.save().map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn get_provider_key(app: tauri::AppHandle, provider: String) -> Result<Option<String>, String> {
    let store = app.store(PROVIDERS_STORE).map_err(|e| e.to_string())?;
    let key = store
        .get(&provider)
        .and_then(|v| v.as_str().map(|s| s.to_string()));
    Ok(key)
}

#[tauri::command]
fn clear_provider_key(app: tauri::AppHandle, provider: String) -> Result<(), String> {
    let store = app.store(PROVIDERS_STORE).map_err(|e| e.to_string())?;
    store.delete(&provider);
    store.save().map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn get_configured_providers(app: tauri::AppHandle) -> Result<Vec<String>, String> {
    let store = app.store(PROVIDERS_STORE).map_err(|e| e.to_string())?;
    let providers: Vec<String> = store
        .keys()
        .into_iter()
        .filter(|k| store.get(k).map(|v| v.as_str().is_some()).unwrap_or(false))
        .collect();
    Ok(providers)
}

// OAuth credential storage commands
#[tauri::command]
fn store_oauth_credentials(
    app: tauri::AppHandle,
    provider: String,
    credentials: String,
) -> Result<(), String> {
    let store = app.store(OAUTH_STORE).map_err(|e| e.to_string())?;
    store.set(&provider, serde_json::json!(credentials));
    store.save().map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn get_oauth_credentials(
    app: tauri::AppHandle,
    provider: String,
) -> Result<Option<String>, String> {
    let store = app.store(OAUTH_STORE).map_err(|e| e.to_string())?;
    let creds = store
        .get(&provider)
        .and_then(|v| v.as_str().map(|s| s.to_string()));
    Ok(creds)
}

#[tauri::command]
fn clear_oauth_credentials(app: tauri::AppHandle, provider: String) -> Result<(), String> {
    let store = app.store(OAUTH_STORE).map_err(|e| e.to_string())?;
    store.delete(&provider);
    store.save().map_err(|e| e.to_string())?;
    Ok(())
}

#[tauri::command]
fn get_oauth_providers(app: tauri::AppHandle) -> Result<Vec<String>, String> {
    let store = app.store(OAUTH_STORE).map_err(|e| e.to_string())?;
    let providers: Vec<String> = store
        .keys()
        .into_iter()
        .filter(|k| store.get(k).map(|v| v.as_str().is_some()).unwrap_or(false))
        .collect();
    Ok(providers)
}

/// Redact sensitive query parameters from an OAuth URL for safe logging.
fn redact_auth_url(url: &str) -> String {
    let sensitive_params = [
        "code_challenge",
        "state",
        "nonce",
        "code_verifier",
        "client_id",
    ];
    let mut result = url.to_string();
    for param in &sensitive_params {
        let pattern = format!("{}=", param);
        if let Some(start) = result.find(&pattern) {
            let value_start = start + pattern.len();
            let value_end = result[value_start..]
                .find('&')
                .map(|i| value_start + i)
                .unwrap_or(result.len());
            result.replace_range(value_start..value_end, "[REDACTED]");
        }
    }
    result
}

/// Start OAuth flow with browser and loopback server.
/// Opens the auth URL in the default browser and starts a local server to receive the callback.
/// The auth_url must already contain a redirect_uri with the port to listen on.
/// Returns the authorization code and state from the callback.
#[tauri::command]
async fn start_oauth_browser_flow(
    app: tauri::AppHandle,
    auth_url: String,
    timeout_secs: Option<u64>,
) -> Result<oauth::OAuthCallbackResult, String> {
    use tauri_plugin_opener::OpenerExt;

    let timeout = timeout_secs.unwrap_or(300); // 5 minute default timeout

    // Extract the port from the redirect_uri in the auth URL
    // The frontend already registered the client with this redirect_uri, so we must use the same port
    let port = extract_port_from_redirect_uri(&auth_url)?;

    info!("[OAuth] Starting browser flow on port: {}", port);
    info!("[OAuth] Auth URL: {}", redact_auth_url(&auth_url));

    // Open the browser with the original auth URL (don't modify it)
    app.opener()
        .open_url(&auth_url, None::<&str>)
        .map_err(|e| format!("Failed to open browser: {}", e))?;

    // Wait for the callback on the specified port
    let result =
        tokio::task::spawn_blocking(move || oauth::wait_for_oauth_callback_on_port(port, timeout))
            .await
            .map_err(|e| format!("Task join error: {}", e))??;

    match result {
        Ok(callback) => Ok(callback),
        Err(oauth_error) => Err(format!(
            "OAuth error: {} - {}",
            oauth_error.error,
            oauth_error.error_description.unwrap_or_default()
        )),
    }
}

/// Extract the port number from the redirect_uri parameter in an OAuth URL.
fn extract_port_from_redirect_uri(auth_url: &str) -> Result<u16, String> {
    // Find redirect_uri parameter
    let re = regex::Regex::new(r"redirect_uri=([^&]+)").map_err(|e| e.to_string())?;

    let captures = re
        .captures(auth_url)
        .ok_or("No redirect_uri found in auth URL")?;

    let encoded_uri = captures.get(1).ok_or("No redirect_uri value")?.as_str();
    let decoded_uri = urlencoding::decode(encoded_uri).map_err(|e| e.to_string())?;

    // Extract port from URI like http://127.0.0.1:58688/oauth/callback
    let port_re =
        regex::Regex::new(r"127\.0\.0\.1:(\d+)").map_err(|e| format!("Regex error: {}", e))?;

    let port_captures = port_re
        .captures(&decoded_uri)
        .ok_or("No port found in redirect_uri")?;

    let port_str = port_captures.get(1).ok_or("No port value")?.as_str();
    port_str
        .parse::<u16>()
        .map_err(|e| format!("Invalid port: {}", e))
}

/// Get an available port for OAuth callback server.
#[tauri::command]
fn get_oauth_callback_port() -> Result<u16, String> {
    oauth::get_available_port()
}

#[derive(serde::Serialize)]
struct BuildInfo {
    app_version: String,
    release_tag: String,
    commit: String,
    build_date: String,
    build_type: String,
    tauri_version: String,
    webview: String,
    rust_version: String,
    os: String,
}

#[tauri::command]
fn get_build_info(app: tauri::AppHandle) -> BuildInfo {
    let version = app
        .config()
        .version
        .clone()
        .unwrap_or_else(|| "unknown".into());

    let webview = if cfg!(target_os = "macos") {
        "WebKit (macOS)".to_string()
    } else if cfg!(target_os = "windows") {
        "WebView2 (Windows)".to_string()
    } else {
        "WebKitGTK (Linux)".to_string()
    };

    let os = format!(
        "{} {} {}",
        std::env::consts::OS,
        std::env::consts::ARCH,
        os_version()
    );

    BuildInfo {
        app_version: version,
        release_tag: env!("BUILT_RELEASE_TAG").to_string(),
        commit: env!("BUILT_COMMIT").to_string(),
        build_date: env!("BUILT_DATE").to_string(),
        build_type: "Alpha".to_string(),
        tauri_version: tauri::VERSION.to_string(),
        webview,
        rust_version: env!("BUILT_RUST_VERSION").to_string(),
        os,
    }
}

fn os_version() -> String {
    #[cfg(target_os = "macos")]
    {
        std::process::Command::new("sw_vers")
            .arg("-productVersion")
            .output()
            .ok()
            .and_then(|o| String::from_utf8(o.stdout).ok())
            .map(|s| s.trim().to_string())
            .unwrap_or_default()
    }
    #[cfg(not(target_os = "macos"))]
    {
        String::new()
    }
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    #[allow(unused_mut)]
    let mut builder = tauri::Builder::default();

    // Single-instance plugin MUST be registered BEFORE every other plugin so
    // its callback fires as early as possible when a second launch is
    // attempted. Resolves serenorg/seren-desktop#1519 — without this, two
    // concurrent `pnpm tauri dev` invocations (or a fresh launch while a
    // previous instance is still alive) produce colliding `ProviderRuntimeState`
    // instances racing on port allocation and child process lifecycle, which
    // manifests as the provider runtime exiting with `signal: 9 (SIGKILL)` and
    // the chat window stuck in "Reconnecting Claude Code Thread".
    //
    // The callback is invoked inside the FIRST (already-running) instance when
    // a second instance attempts to launch. We focus the existing main window
    // and log the attempted argv for observability. The second instance exits
    // cleanly on its own after calling the plugin.
    //
    // Desktop-only: the plugin does not support mobile targets, matching the
    // Cargo.toml `cfg(any(macos, linux, windows))` gate.
    #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
    {
        builder = builder.plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
            log::info!(
                "[single-instance] Second launch blocked; focusing existing window. args={:?} cwd={}",
                args,
                cwd
            );
            if let Some(window) = app.get_webview_window("main") {
                let _ = window.unminimize();
                let _ = window.show();
                let _ = window.set_focus();
            }
        }));
    }

    builder = builder
        .plugin(
            tauri_plugin_log::Builder::new()
                .targets([
                    Target::new(TargetKind::Stdout),
                    Target::new(TargetKind::LogDir { file_name: None }),
                    Target::new(TargetKind::Webview),
                ])
                .max_file_size(5_000_000) // 5 MB per log file
                .rotation_strategy(tauri_plugin_log::RotationStrategy::KeepOne)
                .level(if cfg!(debug_assertions) {
                    log::LevelFilter::Debug
                } else {
                    log::LevelFilter::Info
                })
                .build(),
        )
        .plugin(tauri_plugin_opener::init())
        .plugin(tauri_plugin_store::Builder::default().build())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_updater::Builder::new().build())
        .plugin(tauri_plugin_process::init())
        .plugin(tauri_plugin_http::init())
        .plugin(tauri_plugin_shell::init());

    // Note: deep-link plugin disabled on Windows due to WiX bundler ICE03 registry errors
    // See: https://github.com/tauri-apps/tauri/issues/10453
    #[cfg(not(target_os = "windows"))]
    {
        builder = builder.plugin(tauri_plugin_deep_link::init());
    }

    builder = builder
        .manage(mcp::McpState::new())
        .manage(mcp::HttpMcpState::new())
        .manage(orchestrator::service::OrchestratorState::new())
        .manage(audio::pipeline::CaptureRegistry::default())
        .manage(orchestrator::eval::EvalState::new())
        .manage(orchestrator::tool_bridge::ToolResultBridge::new())
        .manage(provider_runtime::ProviderRuntimeState::new())
        .manage(std::sync::Arc::new(
            commands::updater::ShutdownGuard::default(),
        ))
        .manage(services::database::WalCheckpointTask::default())
        .manage(services::history_sync::HistorySyncLock::default())
        .manage(messaging::MessagingState::new())
        .manage(std::sync::Arc::new(tokio::sync::Mutex::new(None))
            as polymarket::commands::PolymarketWsState);

    builder
        .on_menu_event(|app, event| {
            if event.id().0 == "about" {
                let _ = app.emit("open-about", ());
            }
        })
        .setup(|app| {
            // Inject the Seren Desktop host marker into the process environment.
            // Every child process (provider runtime, Claude/Codex/Gemini CLI,
            // skills spawned by those agents) inherits these vars and can
            // detect that it's running under Seren Desktop.
            // Spec: serenorg/seren-desktop#1496
            //
            // SEREN_PLAYWRIGHT_MCP_COMMAND lets skill subprocesses
            // (prophet-arb-bot's automated browser flows) spawn the bundled
            // playwright-stealth MCP server without hardcoding the macOS
            // path. Resolved once here from the resource_dir so Windows and
            // Linux inherit a working absolute command too. #1945.
            //
            // SAFETY: set_var is unsafe in Rust 2024. Called exactly once
            // during app setup before any threads spawn child processes.
            unsafe {
                std::env::set_var("SEREN_HOST", "seren-desktop");
                std::env::set_var("SEREN_DESKTOP", "1");

                let resource_dir = app.path().resource_dir().ok();
                if let Some(script) =
                    mcp::resolve_playwright_mcp_script_path_from(resource_dir.as_deref())
                {
                    let node = mcp::resolve_command_in_embedded_path("node");
                    let cmd = mcp::format_playwright_mcp_command(&node, &script);
                    log::info!("[MCP] SEREN_PLAYWRIGHT_MCP_COMMAND={}", cmd);
                    std::env::set_var("SEREN_PLAYWRIGHT_MCP_COMMAND", cmd);
                } else {
                    log::warn!(
                        "[MCP] playwright-stealth script not found on disk; \
                         SEREN_PLAYWRIGHT_MCP_COMMAND not exported. Skill \
                         subprocesses that need it will fail with a clear \
                         setup error."
                    );
                }
            }

            // Build native menu bar for all platforms
            {
                use tauri::menu::{Menu, MenuItem, PredefinedMenuItem, Submenu};

                let about = MenuItem::with_id(app, "about", "About Seren", true, None::<&str>)?;
                let separator = PredefinedMenuItem::separator(app)?;
                let quit = PredefinedMenuItem::quit(app, Some("Quit Seren"))?;

                // macOS app menu includes Hide/Show items; Windows/Linux just About + Quit
                #[cfg(target_os = "macos")]
                let app_menu = {
                    let hide = PredefinedMenuItem::hide(app, Some("Hide Seren"))?;
                    let hide_others = PredefinedMenuItem::hide_others(app, None)?;
                    let show_all = PredefinedMenuItem::show_all(app, None)?;
                    Submenu::with_items(
                        app,
                        "Seren",
                        true,
                        &[
                            &about,
                            &separator,
                            &hide,
                            &hide_others,
                            &show_all,
                            &separator,
                            &quit,
                        ],
                    )?
                };

                #[cfg(not(target_os = "macos"))]
                let app_menu =
                    Submenu::with_items(app, "Seren", true, &[&about, &separator, &quit])?;

                let edit_menu = {
                    let undo = PredefinedMenuItem::undo(app, None)?;
                    let redo = PredefinedMenuItem::redo(app, None)?;
                    let cut = PredefinedMenuItem::cut(app, None)?;
                    let copy = PredefinedMenuItem::copy(app, None)?;
                    let paste = PredefinedMenuItem::paste(app, None)?;
                    let select_all = PredefinedMenuItem::select_all(app, None)?;
                    Submenu::with_items(
                        app,
                        "Edit",
                        true,
                        &[
                            &undo,
                            &redo,
                            &separator,
                            &cut,
                            &copy,
                            &paste,
                            &separator,
                            &select_all,
                        ],
                    )?
                };

                let window_menu = {
                    let minimize = PredefinedMenuItem::minimize(app, None)?;
                    let zoom = PredefinedMenuItem::maximize(app, Some("Zoom"))?;
                    let close = PredefinedMenuItem::close_window(app, None)?;
                    let fullscreen = PredefinedMenuItem::fullscreen(app, None)?;
                    Submenu::with_items(
                        app,
                        "Window",
                        true,
                        &[&minimize, &zoom, &fullscreen, &separator, &close],
                    )?
                };

                let menu = Menu::with_items(app, &[&app_menu, &edit_menu, &window_menu])?;
                app.set_menu(menu)?;
            }

            // System tray: live meeting-capture indicator + quick actions.
            if let Err(error) = tray::setup_tray(app.handle()) {
                log::warn!("[Tray] Failed to create system tray: {}", error);
            }

            // Configure embedded runtime early in startup
            // This prepends bundled Node.js and Git to PATH
            let paths = embedded_runtime::configure_embedded_runtime(app.handle());
            if paths.node_dir.is_some() || paths.git_dir.is_some() {
                info!(
                    "[Seren] Embedded runtime configured: node={:?}, git={:?}",
                    paths.node_dir.is_some(),
                    paths.git_dir.is_some()
                );
            }

            // Configure Claude Code environment (adds cargo to PATH if needed)
            claude_setup::configure_claude_code_environment();

            // Install panic sidecar capture for desktop support reports.
            support::init(app.handle());

            // Start OAuth callback server in dev mode
            // Provides localhost:8787 redirect for OAuth without deep links
            if let Some(handle) =
                oauth_callback_server::start_oauth_callback_server(app.handle().clone())
            {
                app.manage(handle);
            }

            // Register deep link handler for OAuth callbacks (production)
            // Note: Disabled on Windows due to WiX bundler ICE03 registry errors
            #[cfg(all(desktop, not(target_os = "windows")))]
            {
                use tauri_plugin_deep_link::DeepLinkExt;
                let handle = app.handle().clone();
                app.deep_link().on_open_url(move |event| {
                    let urls = event.urls();
                    log::info!(
                        "[Deep Link] Received open URL event with {} URLs: {:?}",
                        urls.len(),
                        urls
                    );
                    for url in urls {
                        log::debug!("[Deep Link] Processing URL: {}", url);
                        log::debug!("[Deep Link] - scheme: {}", url.scheme());
                        log::debug!("[Deep Link] - path: {}", url.path());
                        if url.scheme() == "seren" && url.path() == "/callback" {
                            log::info!("[Deep Link] Match! Emitting oauth-callback event");
                            // Emit event to frontend with OAuth callback data
                            if let Err(e) = handle.emit("oauth-callback", url.to_string()) {
                                log::error!(
                                    "[Deep Link] Failed to emit oauth-callback event: {}",
                                    e
                                );
                            } else {
                                log::info!("[Deep Link] Successfully emitted oauth-callback event");
                            }
                            // Focus the main window so user returns to the app
                            if let Some(window) = handle.get_webview_window("main") {
                                let _ = window.set_focus();
                                log::info!("[Deep Link] Focused main window after OAuth callback");
                            }
                        } else {
                            log::debug!(
                                "[Deep Link] No match - scheme: {}, path: {}",
                                url.scheme(),
                                url.path()
                            );
                        }
                    }
                });
            }

            // Initialize shared SQLite connection pool to prevent "database is locked" errors.
            {
                let pool = services::database::DbPool::new(&app.handle())
                    .expect("failed to initialize database pool");
                app.manage(pool);
                services::database::start_wal_checkpoint_task(&app.handle());
            }

            // Track Rust-bridged Gateway HTTP requests so the frontend can abort streams.
            app.manage(commands::gateway_http::GatewayHttpState::default());
            app.manage(terminal::TerminalState::default());

            // Initialize memory state for cloud + local cache operations.
            // Token is read fresh from the auth store on each request.
            {
                let data_dir = app
                    .path()
                    .app_data_dir()
                    .expect("failed to get app data dir");
                let cache_path = data_dir.join("memory_cache.db");

                app.manage(commands::memory::MemoryState::new(
                    "https://memory.serendb.com".to_string(),
                    cache_path,
                ));
            }

            Ok(())
        })
        .invoke_handler(tauri::generate_handler![
            greet,
            tray::set_tray_recording,
            get_oauth_redirect_url,
            store_token,
            get_token,
            clear_token,
            store_refresh_token,
            get_refresh_token,
            clear_refresh_token,
            get_setting,
            set_setting,
            store_provider_key,
            get_provider_key,
            clear_provider_key,
            get_configured_providers,
            files::read_file,
            files::read_file_base64,
            files::write_file,
            files::list_directory,
            files::path_exists,
            files::is_directory,
            files::create_file,
            files::create_directory,
            files::delete_path,
            files::rename_path,
            files::reveal_in_file_manager,
            files::open_path_with_default_app,
            // Shell command execution (requires frontend approval)
            shell::execute_shell_command,
            shell::execute_shell_command_streaming,
            shell::run_skill_script,
            shell::diagnose_shell_network,
            // Interactive terminal buffers
            terminal::terminal_create_buffer,
            terminal::terminal_list_buffers,
            terminal::terminal_write,
            terminal::terminal_resize,
            terminal::terminal_kill,
            terminal::terminal_grid_snapshot,
            terminal::terminal_grid_scrollback,
            terminal::terminal_signal,
            terminal::terminal_claude_version,
            // Web fetch command
            commands::web::web_fetch,
            // Rust-backed Gateway API bridge
            commands::gateway_http::gateway_http_start,
            commands::gateway_http::gateway_http_cancel,
            commands::history_sync::history_sync_run_now,
            commands::history_sync::history_sync_wipe_remote,
            // Meeting Mode persistence commands
            commands::audio::create_meeting,
            commands::audio::get_meeting,
            commands::audio::list_meetings,
            commands::audio::delete_meeting,
            commands::audio::update_meeting_status,
            commands::audio::update_meeting_notes,
            commands::audio::update_meeting_title,
            commands::audio::set_meeting_routed_skill,
            commands::audio::append_transcript_segment,
            commands::audio::get_transcript_segments,
            // Meeting Mode capture + intelligence commands
            commands::audio::start_meeting_capture,
            commands::audio::is_meeting_capture_active,
            commands::audio::stop_meeting_capture,
            commands::audio::reconcile_meeting_speakers,
            commands::audio::generate_meeting_notes,
            commands::audio::republish_meeting_to_seren_notes,
            commands::audio::get_meeting_transcript_text,
            commands::audio::select_meeting_skills,
            commands::audio::list_meeting_templates,
            commands::audio::meeting_autodetect,
            // Dictation commands
            commands::audio::transcribe_pcm,
            commands::audio::cleanup_dictation_text,
            commands::audio::transform_selection,
            // Conversation commands
            commands::chat::create_conversation,
            commands::chat::list_conversations,
            commands::chat::get_conversation,
            commands::chat::update_conversation,
            commands::chat::archive_conversation,
            commands::chat::delete_conversation,
            commands::chat::delete_conversations_by_employee,
            commands::employees_archive::archive_employee,
            commands::employees_archive::list_archived_employees,
            commands::employees_archive::delete_archived_employee,
            // Agent conversation commands
            commands::chat::create_agent_conversation,
            commands::chat::get_agent_conversation,
            commands::chat::set_agent_conversation_session_id,
            commands::chat::set_agent_conversation_title,
            commands::chat::set_agent_conversation_model_id,
            commands::chat::set_agent_conversation_permission_mode,
            commands::chat::set_agent_conversation_metadata,
            commands::chat::archive_agent_conversation,
            commands::chat::get_thread_draft,
            commands::chat::set_thread_draft,
            // Input history commands
            commands::chat::append_input_history,
            commands::chat::get_input_history,
            // Message commands
            commands::chat::save_message,
            commands::chat::get_messages,
            commands::chat::clear_conversation_history,
            commands::chat::clear_all_history,
            // Model context-window cache
            commands::model_context_cache::get_model_context_window,
            commands::model_context_cache::record_model_context_window,
            // Per-thread provider runtime binding
            commands::provider_runtime::get_provider_session_runtime,
            commands::provider_runtime::switch_thread_provider,
            sync::start_watching,
            sync::stop_watching,
            sync::get_sync_status,
            mcp::mcp_connect,
            mcp::mcp_disconnect,
            mcp::mcp_list_tools,
            mcp::mcp_list_resources,
            mcp::mcp_call_tool,
            mcp::mcp_read_resource,
            mcp::mcp_is_connected,
            mcp::mcp_list_connected,
            mcp::resolve_playwright_mcp_script_path,
            // HTTP MCP commands (for mcp.serendb.com)
            mcp::mcp_connect_http,
            mcp::mcp_disconnect_http,
            mcp::mcp_list_tools_http,
            mcp::mcp_call_tool_http,
            mcp::mcp_is_connected_http,
            mcp::mcp_list_connected_http,
            // Polymarket CLOB API authentication commands
            polymarket::commands::store_polymarket_credentials,
            polymarket::commands::get_polymarket_address,
            polymarket::commands::clear_polymarket_credentials,
            polymarket::commands::sign_polymarket_request,
            // Skill Keys host-side secret broker
            secret_broker::list_skill_secret_bindings,
            secret_broker::upsert_skill_secret_binding,
            secret_broker::request_skill_secret_env,
            secret_broker::delete_skill_secret_binding,
            secret_broker::scan_skill_env_migrations,
            secret_broker::list_secret_access_audit,
            secret_broker::grant_skill_secret_session,
            secret_broker::end_skill_secret_session,
            // Polymarket WebSocket commands
            polymarket::commands::connect_polymarket_websocket,
            polymarket::commands::subscribe_polymarket_market,
            polymarket::commands::subscribe_polymarket_user,
            embedded_runtime::get_embedded_runtime_info,
            provider_runtime::provider_runtime_get_config,
            provider_runtime::provider_runtime_stop,
            provider_runtime::provider_force_kill_session,
            commands::updater::updater_install_preflight,
            commands::updater::updater_pre_install,
            commands::updater::updater_pre_install_release,
            // CLI installer commands
            commands::cli_installer::check_cli_installed,
            commands::cli_installer::install_cli_tool,
            store_oauth_credentials,
            get_oauth_credentials,
            clear_oauth_credentials,
            get_oauth_providers,
            // OAuth browser flow commands
            commands::auth::start_social_login,
            start_oauth_browser_flow,
            get_oauth_callback_port,
            // Build info
            get_build_info,
            // Desktop support reporting
            support::get_support_report_ids,
            support::submit_support_report,
            support::sweep_support_crash_reports,
            // Semantic indexing commands
            commands::indexing::init_project_index,
            commands::indexing::get_index_status,
            commands::indexing::has_project_index,
            commands::indexing::index_chunk,
            commands::indexing::index_chunks,
            commands::indexing::delete_file_index,
            commands::indexing::file_needs_reindex,
            commands::indexing::search_codebase,
            commands::indexing::get_embedding_dimension,
            commands::indexing::discover_project_files,
            commands::indexing::chunk_file,
            commands::indexing::estimate_indexing,
            commands::indexing::compute_file_hash,
            // Local context-intelligence commands for agent-owned code inspection.
            commands::context_intelligence::seren_index_source,
            commands::context_intelligence::seren_index_file,
            commands::context_intelligence::seren_batch_index_files,
            // Skills commands
            skills::get_default_project_dir,
            skills::get_seren_skill_authoring_dir,
            skills::get_seren_skills_dir,
            skills::get_claude_skills_dir,
            skills::get_project_skills_dir,
            skills::create_skills_symlink,
            skills::read_project_config,
            skills::write_project_config,
            skills::clear_project_config,
            skills::get_thread_skills,
            skills::set_thread_skills,
            skills::clear_thread_skills,
            skills::list_skill_dirs,
            skills::install_skill,
            skills::validate_skill_payload,
            skills::log_skill_install_failure,
            skills::rename_skill_dir,
            skills::remove_skill,
            skills::read_skill_content,
            skills::read_skill_file,
            skills::read_skill_file_b64,
            skills::list_skill_payload_files,
            skills::read_skill_sync_state,
            skills::write_skill_sync_state,
            skills::resolve_skill_path,
            skills::create_skill_folder,
            // Messaging transport commands
            messaging::commands::messaging_start,
            messaging::commands::messaging_stop,
            messaging::commands::messaging_status,
            messaging::commands::messaging_status_all,
            messaging::commands::messaging_whatsapp_qr,
            // Orchestrator commands
            commands::orchestrator::orchestrate,
            commands::orchestrator::cancel_orchestration,
            commands::orchestrator::submit_tool_result,
            commands::orchestrator::submit_eval_signal,
            // Memory commands
            commands::memory::memory_bootstrap,
            commands::memory::memory_session_bootstrap,
            commands::memory::memory_remember,
            commands::memory::memory_create_memory,
            commands::memory::memory_recall,
            commands::memory::memory_process_conversation,
            commands::memory::memory_learn_from_error,
            commands::memory::memory_list_memories,
            commands::memory::memory_get_memory,
            commands::memory::memory_update_memory,
            commands::memory::memory_forget,
            commands::memory::memory_delete_memory,
            commands::memory::memory_get_memory_graph,
            commands::memory::memory_consolidate,
            commands::memory::memory_configure_publishers,
            commands::memory::memory_sync,
            // Claude Code auto-memory interceptor commands
            commands::claude_memory::claude_memory_start,
            commands::claude_memory::claude_memory_stop,
            commands::claude_memory::claude_memory_status,
            commands::claude_memory::claude_memory_migrate_existing,
            commands::claude_memory::claude_memory_get_project_identity,
            commands::claude_memory::claude_memory_render_memory_md,
            commands::claude_memory::claude_memory_run_sql,
            commands::claude_memory::claude_session_exists,
            // Runtime session commands
            commands::session::create_runtime_session,
            commands::session::get_runtime_session,
            commands::session::list_runtime_sessions,
            commands::session::update_runtime_session,
            commands::session::resume_runtime_session,
            commands::session::delete_runtime_session,
            commands::session::add_session_event,
            commands::session::get_session_events,
            commands::session::update_session_event_status,
        ])
        .build(tauri::generate_context!())
        .expect("error while building tauri application")
        .run(|app, event| match event {
            RunEvent::WindowEvent {
                event: WindowEvent::Focused(false),
                ..
            } => {
                services::database::checkpoint_managed_db(app, "window blur");
            }
            RunEvent::Exit => {
                if let Some(task) = app.try_state::<services::database::WalCheckpointTask>() {
                    task.abort();
                }
                services::database::checkpoint_managed_db(app, "app exit");
                log::info!("[App] Exit event — cleaning up child processes");
                // Kill all MCP stdio server processes to prevent orphaned zombies
                if let Some(mcp_state) = app.try_state::<mcp::McpState>() {
                    mcp_state.kill_all();
                }
                // Kill terminal PTY children, especially for Windows ConPTY
                // where dropping the master may not reliably terminate shells.
                if let Some(terminal_state) = app.try_state::<terminal::TerminalState>() {
                    terminal_state.kill_all();
                }
                // Stop the provider runtime node process
                if let Some(rt_state) = app.try_state::<provider_runtime::ProviderRuntimeState>() {
                    rt_state.kill_sync();
                }
            }
            _ => {}
        });
}
