// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
use std::env;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use tauri::{Emitter, Manager, Runtime, WebviewUrl, WebviewWindow, WebviewWindowBuilder};
use tauri_plugin_deep_link::DeepLinkExt;
use tauri_plugin_log::{Target, TargetKind};

// Store pending URLs that need to be handled when the window is ready
static PENDING_URLS: Mutex<Vec<String>> = Mutex::new(Vec::new());

// Set to true once the user has confirmed they want to quit, so the close
// interceptor lets the next CloseRequested through instead of looping back
// to the confirmation dialog.
static QUIT_CONFIRMED: AtomicBool = AtomicBool::new(false);

mod api_client;
mod api_server;
mod app_auto_updater;
pub mod app_dirs;
mod auto_updater;
mod browser;
mod browser_runner;
mod browser_version_manager;
mod default_browser;
pub mod dns_blocklist;
mod downloaded_browsers_registry;
mod downloader;
mod ephemeral_dirs;
mod extension_manager;
mod extraction;
mod geoip_downloader;
mod geolocation;
mod group_manager;
mod human_typing;
mod ip_utils;
mod platform_browser;
mod profile;
mod profile_importer;
mod proxy_manager;
pub mod proxy_runner;
pub mod proxy_server;
pub mod proxy_storage;
mod settings_manager;
pub mod socks5_local;
pub mod sync;
mod synchronizer;
pub mod traffic_stats;
mod wayfern_manager;
mod wayfern_terms;
// mod theme_detector; // removed: theme detection handled in webview via CSS prefers-color-scheme
pub mod cloud_auth;
mod commercial_license;
mod cookie_manager;
pub mod events;
mod mcp_integrations;
mod mcp_server;
mod tag_manager;
mod team_lock;
mod version_updater;
pub mod vpn;
pub mod vpn_worker_runner;
pub mod vpn_worker_storage;

use browser_runner::{
  check_browser_exists, kill_browser_profile, launch_browser_profile, open_url_with_profile,
};

use profile::manager::{
  check_browser_status, clone_profile, create_browser_profile_new, delete_profile,
  list_browser_profiles, rename_profile, update_profile_dns_blocklist, update_profile_launch_hook,
  update_profile_note, update_profile_proxy, update_profile_proxy_bypass_rules,
  update_profile_tags, update_profile_vpn, update_profile_window_color, update_wayfern_config,
};

use profile::password::{
  change_profile_password, is_profile_locked, lock_profile, remove_profile_password,
  set_profile_password, unlock_profile, verify_profile_password,
};

use browser_version_manager::{
  fetch_browser_versions_cached_first, fetch_browser_versions_with_count,
  fetch_browser_versions_with_count_cached_first, get_supported_browsers,
  is_browser_supported_on_platform,
};

use downloaded_browsers_registry::{
  check_missing_binaries, ensure_active_browsers_downloaded, ensure_all_binaries_exist,
  get_downloaded_browser_versions,
};

use downloader::{cancel_download, download_browser};

use settings_manager::{
  complete_onboarding, dismiss_window_resize_warning, get_app_settings, get_onboarding_completed,
  get_sync_settings, get_system_info, get_system_language, get_table_sorting_settings,
  get_window_resize_warning_dismissed, open_log_directory, read_log_files, save_app_settings,
  save_sync_settings, save_table_sorting_settings,
};

use sync::{
  cancel_profile_sync, check_has_e2e_password, delete_e2e_password, enable_sync_for_all_entities,
  get_unsynced_entity_counts, is_group_in_use_by_synced_profile, is_proxy_in_use_by_synced_profile,
  is_vpn_in_use_by_synced_profile, request_profile_sync, rollover_encryption_for_all_entities,
  set_e2e_password, set_extension_group_sync_enabled, set_extension_sync_enabled,
  set_group_sync_enabled, set_profile_sync_mode, set_proxy_sync_enabled, set_vpn_sync_enabled,
  verify_e2e_password,
};

use tag_manager::get_all_tags;

use default_browser::{is_default_browser, set_as_default_browser};

use version_updater::{
  clear_all_version_cache_and_refetch, get_version_update_status, get_version_updater,
  trigger_manual_version_update,
};

use auto_updater::{
  check_for_browser_updates, complete_browser_update_with_auto_update, dismiss_update_notification,
};

use app_auto_updater::{
  check_for_app_updates, check_for_app_updates_manual, download_and_prepare_app_update,
  restart_application,
};

use profile_importer::{detect_existing_profiles, import_browser_profile};

use extension_manager::{
  add_extension, add_extension_to_group, assign_extension_group_to_profile, create_extension_group,
  delete_extension, delete_extension_group, get_extension_group_for_profile, get_extension_icon,
  list_extension_groups, list_extensions, remove_extension_from_group, update_extension,
  update_extension_group,
};

use group_manager::{
  assign_profiles_to_group, create_profile_group, delete_profile_group, delete_selected_profiles,
  get_groups_with_profile_counts, get_profile_groups, update_profile_group,
};

use geoip_downloader::{check_missing_geoip_database, GeoIPDownloader};

use browser_version_manager::get_browser_release_types;

use api_server::{get_api_server_status, start_api_server, stop_api_server};

// Trait to extend WebviewWindow with transparent titlebar functionality
pub trait WindowExt {
  #[cfg(target_os = "macos")]
  fn set_transparent_titlebar(&self, transparent: bool) -> Result<(), String>;
  #[cfg(target_os = "macos")]
  fn disable_native_fullscreen(&self) -> Result<(), String>;
}

impl<R: Runtime> WindowExt for WebviewWindow<R> {
  #[cfg(target_os = "macos")]
  fn set_transparent_titlebar(&self, transparent: bool) -> Result<(), String> {
    use objc2::rc::Retained;
    use objc2_app_kit::{NSWindow, NSWindowStyleMask, NSWindowTitleVisibility};

    unsafe {
      let ns_window: Retained<NSWindow> =
        Retained::retain(self.ns_window().unwrap().cast()).unwrap();

      if transparent {
        // Hide the title text
        ns_window.setTitleVisibility(NSWindowTitleVisibility(1)); // NSWindowTitleHidden

        // Make titlebar transparent
        ns_window.setTitlebarAppearsTransparent(true);

        // Set full size content view
        let current_mask = ns_window.styleMask();
        let new_mask = NSWindowStyleMask(current_mask.0 | (1 << 15)); // NSFullSizeContentViewWindowMask
        ns_window.setStyleMask(new_mask);
      } else {
        // Show the title text
        ns_window.setTitleVisibility(NSWindowTitleVisibility(0)); // NSWindowTitleVisible

        // Make titlebar opaque
        ns_window.setTitlebarAppearsTransparent(false);

        // Remove full size content view
        let current_mask = ns_window.styleMask();
        let new_mask = NSWindowStyleMask(current_mask.0 & !(1 << 15));
        ns_window.setStyleMask(new_mask);
      }
    }

    Ok(())
  }

  #[cfg(target_os = "macos")]
  fn disable_native_fullscreen(&self) -> Result<(), String> {
    use objc2::rc::Retained;
    use objc2_app_kit::{NSWindow, NSWindowCollectionBehavior};

    unsafe {
      let ns_window: Retained<NSWindow> =
        Retained::retain(self.ns_window().unwrap().cast()).unwrap();

      // Make the green title-bar button (and titlebar double-click) "zoom"
      // the window to fill the screen as an ordinary window instead of
      // entering immersive native fullscreen that hides the menu bar and
      // moves to its own Space. Mirrors Electron's `fullscreenable: false`:
      // clear FullScreenPrimary and set FullScreenNone. AppKit then maps the
      // green button to the standard zoom, expanding to the visible screen
      // frame while keeping the window chrome and the current Space.
      const FULL_SCREEN_PRIMARY: usize = 1 << 7;
      const FULL_SCREEN_NONE: usize = 1 << 9;
      let current = ns_window.collectionBehavior();
      let updated =
        NSWindowCollectionBehavior((current.0 & !FULL_SCREEN_PRIMARY) | FULL_SCREEN_NONE);
      ns_window.setCollectionBehavior(updated);
    }

    Ok(())
  }
}

// Called internally for deep-link / startup URL handling — not invoked from the
// frontend, so it is intentionally not a `#[tauri::command]`.
async fn handle_url_open(app: tauri::AppHandle, url: String) -> Result<(), String> {
  log::info!("handle_url_open called with URL: {url}");

  // Check if the main window exists and is ready
  if let Some(window) = app.get_webview_window("main") {
    log::debug!("Main window exists");

    // Try to show and focus the window first
    let _ = window.show();
    let _ = window.set_focus();
    let _ = window.unminimize();

    events::emit("show-profile-selector", url.clone())
      .map_err(|e| format!("Failed to emit URL open event: {e}"))?;
  } else {
    // Window doesn't exist yet - add to pending URLs
    log::debug!("Main window doesn't exist, adding URL to pending list");
    let mut pending = PENDING_URLS.lock().unwrap();
    pending.push(url);
  }

  Ok(())
}

#[tauri::command]
async fn create_stored_proxy(
  app_handle: tauri::AppHandle,
  name: String,
  proxy_settings: Option<crate::browser::ProxySettings>,
) -> Result<crate::proxy_manager::StoredProxy, String> {
  if let Some(settings) = proxy_settings {
    crate::proxy_manager::PROXY_MANAGER
      .create_stored_proxy(&app_handle, name, settings)
      .map_err(|e| format!("Failed to create stored proxy: {e}"))
  } else {
    Err("proxy_settings is required".to_string())
  }
}

#[tauri::command]
async fn get_stored_proxies() -> Result<Vec<crate::proxy_manager::StoredProxy>, String> {
  Ok(crate::proxy_manager::PROXY_MANAGER.get_stored_proxies())
}

#[tauri::command]
async fn update_stored_proxy(
  app_handle: tauri::AppHandle,
  proxy_id: String,
  name: Option<String>,
  proxy_settings: Option<crate::browser::ProxySettings>,
) -> Result<crate::proxy_manager::StoredProxy, String> {
  crate::proxy_manager::PROXY_MANAGER
    .update_stored_proxy(&app_handle, &proxy_id, name, proxy_settings)
    .map_err(|e| format!("Failed to update stored proxy: {e}"))
}

#[tauri::command]
async fn delete_stored_proxy(app_handle: tauri::AppHandle, proxy_id: String) -> Result<(), String> {
  crate::proxy_manager::PROXY_MANAGER
    .delete_stored_proxy(&app_handle, &proxy_id)
    .map_err(|e| format!("Failed to delete stored proxy: {e}"))
}

#[tauri::command]
async fn check_proxy_validity(
  proxy_id: String,
  proxy_settings: Option<crate::browser::ProxySettings>,
) -> Result<crate::proxy_manager::ProxyCheckResult, String> {
  let settings = if let Some(s) = proxy_settings {
    s
  } else {
    crate::proxy_manager::PROXY_MANAGER
      .get_proxy_settings_by_id(&proxy_id)
      .ok_or_else(|| format!("Proxy '{proxy_id}' not found"))?
  };
  crate::proxy_manager::PROXY_MANAGER
    .check_proxy_validity(&proxy_id, &settings)
    .await
}

#[tauri::command]
fn get_cached_proxy_check(proxy_id: String) -> Option<crate::proxy_manager::ProxyCheckResult> {
  crate::proxy_manager::PROXY_MANAGER.get_cached_proxy_check(&proxy_id)
}

#[tauri::command]
fn export_proxies(format: String) -> Result<String, String> {
  match format.as_str() {
    "json" => crate::proxy_manager::PROXY_MANAGER.export_proxies_json(),
    "txt" => Ok(crate::proxy_manager::PROXY_MANAGER.export_proxies_txt()),
    _ => Err(format!("Unsupported export format: {format}")),
  }
}

#[tauri::command]
async fn import_proxies_json(
  app_handle: tauri::AppHandle,
  content: String,
) -> Result<crate::proxy_manager::ProxyImportResult, String> {
  crate::proxy_manager::PROXY_MANAGER
    .import_proxies_json(&app_handle, &content)
    .map_err(|e| format!("Failed to import proxies: {e}"))
}

#[tauri::command]
fn parse_txt_proxies(content: String) -> Vec<crate::proxy_manager::ProxyParseResult> {
  crate::proxy_manager::ProxyManager::parse_txt_proxies(&content)
}

#[tauri::command]
async fn import_proxies_from_parsed(
  app_handle: tauri::AppHandle,
  parsed_proxies: Vec<crate::proxy_manager::ParsedProxyLine>,
  name_prefix: Option<String>,
) -> Result<crate::proxy_manager::ProxyImportResult, String> {
  crate::proxy_manager::PROXY_MANAGER
    .import_proxies_from_parsed(&app_handle, parsed_proxies, name_prefix)
    .map_err(|e| format!("Failed to import proxies: {e}"))
}

#[tauri::command]
async fn read_profile_cookies(
  profile_id: String,
) -> Result<cookie_manager::CookieReadResult, String> {
  tokio::task::spawn_blocking(move || cookie_manager::CookieManager::read_cookies(&profile_id))
    .await
    .map_err(|e| format!("Failed to read profile cookies: {e}"))?
}

#[tauri::command]
async fn get_profile_cookie_stats(
  profile_id: String,
) -> Result<cookie_manager::CookieStats, String> {
  tokio::task::spawn_blocking(move || cookie_manager::CookieManager::read_stats(&profile_id))
    .await
    .map_err(|e| format!("Failed to read profile cookie stats: {e}"))?
}

#[tauri::command]
async fn copy_profile_cookies(
  app_handle: tauri::AppHandle,
  request: cookie_manager::CookieCopyRequest,
) -> Result<Vec<cookie_manager::CookieCopyResult>, String> {
  let target_ids = request.target_profile_ids.clone();
  let results = cookie_manager::CookieManager::copy_cookies(&app_handle, request).await?;

  // Trigger sync for target profiles that have sync enabled
  if let Some(scheduler) = crate::sync::get_global_scheduler() {
    let profile_manager = profile::manager::ProfileManager::instance();
    if let Ok(profiles) = profile_manager.list_profiles() {
      let sync_ids: Vec<String> = target_ids
        .iter()
        .filter(|tid| {
          profiles
            .iter()
            .any(|p| p.id.to_string() == **tid && p.is_sync_enabled())
        })
        .cloned()
        .collect();
      if !sync_ids.is_empty() {
        tauri::async_runtime::spawn(async move {
          for id in sync_ids {
            scheduler.queue_profile_sync(id).await;
          }
        });
      }
    }
  }

  Ok(results)
}

#[tauri::command]
async fn import_cookies_from_file(
  app_handle: tauri::AppHandle,
  profile_id: String,
  content: String,
) -> Result<cookie_manager::CookieImportResult, String> {
  let result =
    cookie_manager::CookieManager::import_cookies(&app_handle, &profile_id, &content).await?;

  // Trigger sync for the profile if sync is enabled
  if let Some(scheduler) = crate::sync::get_global_scheduler() {
    let profile_manager = profile::manager::ProfileManager::instance();
    if let Ok(profiles) = profile_manager.list_profiles() {
      if let Some(profile) = profiles.iter().find(|p| p.id.to_string() == profile_id) {
        if profile.is_sync_enabled() {
          let pid = profile_id.clone();
          tauri::async_runtime::spawn(async move {
            scheduler.queue_profile_sync(pid).await;
          });
        }
      }
    }
  }

  Ok(result)
}

#[tauri::command]
async fn export_profile_cookies(profile_id: String, format: String) -> Result<String, String> {
  cookie_manager::CookieManager::export_cookies(&profile_id, &format)
}

#[tauri::command]
fn check_wayfern_terms_accepted() -> bool {
  wayfern_terms::WayfernTermsManager::instance().is_terms_accepted()
}

#[tauri::command]
fn check_wayfern_downloaded() -> bool {
  wayfern_terms::WayfernTermsManager::instance().is_wayfern_downloaded()
}

#[tauri::command]
async fn accept_wayfern_terms() -> Result<(), String> {
  wayfern_terms::WayfernTermsManager::instance()
    .accept_terms()
    .await
}

#[tauri::command]
async fn get_commercial_trial_status(
  app_handle: tauri::AppHandle,
) -> Result<commercial_license::TrialStatus, String> {
  commercial_license::CommercialLicenseManager::instance()
    .get_trial_status(&app_handle)
    .await
}

#[tauri::command]
async fn acknowledge_trial_expiration(app_handle: tauri::AppHandle) -> Result<(), String> {
  commercial_license::CommercialLicenseManager::instance()
    .acknowledge_expiration(&app_handle)
    .await
}

#[tauri::command]
fn has_acknowledged_trial_expiration(app_handle: tauri::AppHandle) -> Result<bool, String> {
  commercial_license::CommercialLicenseManager::instance().has_acknowledged(&app_handle)
}

#[tauri::command]
async fn start_mcp_server(app_handle: tauri::AppHandle) -> Result<u16, String> {
  mcp_server::McpServer::instance().start(app_handle).await
}

#[tauri::command]
async fn stop_mcp_server() -> Result<(), String> {
  mcp_server::McpServer::instance().stop().await
}

#[tauri::command]
fn get_mcp_server_status() -> bool {
  mcp_server::McpServer::instance().is_running()
}

#[derive(serde::Serialize)]
struct McpConfig {
  port: u16,
  token: String,
}

#[tauri::command]
async fn get_mcp_config(app_handle: tauri::AppHandle) -> Result<Option<McpConfig>, String> {
  let mcp_server = mcp_server::McpServer::instance();
  if !mcp_server.is_running() {
    return Ok(None);
  }

  let port = mcp_server
    .get_port()
    .ok_or("MCP server port not available")?;

  let settings_manager = settings_manager::SettingsManager::instance();
  let token = settings_manager
    .get_mcp_token(&app_handle)
    .await
    .map_err(|e| format!("Failed to get MCP token: {e}"))?
    .ok_or("MCP token not found")?;

  Ok(Some(McpConfig { port, token }))
}

fn claude_desktop_extension_dir() -> Option<std::path::PathBuf> {
  #[cfg(target_os = "macos")]
  {
    dirs::home_dir().map(|h| {
      h.join("Library")
        .join("Application Support")
        .join("Claude")
        .join("Claude Extensions")
        .join("local.mcpb.donut-browser.donut-browser")
    })
  }
  #[cfg(target_os = "windows")]
  {
    std::env::var("APPDATA").ok().map(|appdata| {
      std::path::PathBuf::from(appdata)
        .join("Claude")
        .join("Claude Extensions")
        .join("local.mcpb.donut-browser.donut-browser")
    })
  }
  #[cfg(target_os = "linux")]
  {
    dirs::config_dir().map(|c| {
      c.join("Claude")
        .join("Claude Extensions")
        .join("local.mcpb.donut-browser.donut-browser")
    })
  }
}

fn is_mcp_in_claude_desktop_internal() -> bool {
  let Some(dir) = claude_desktop_extension_dir() else {
    return false;
  };
  dir.join("manifest.json").exists()
}

async fn add_mcp_to_claude_desktop_internal(app_handle: &tauri::AppHandle) -> Result<(), String> {
  let mcp_server = mcp_server::McpServer::instance();
  let port = mcp_server.get_port().ok_or("MCP server is not running")?;

  let settings_manager = settings_manager::SettingsManager::instance();
  let token = settings_manager
    .get_mcp_token(app_handle)
    .await
    .map_err(|e| format!("Failed to get MCP token: {e}"))?
    .ok_or("MCP token not found")?;

  let ext_dir = claude_desktop_extension_dir().ok_or("Unsupported platform")?;
  let server_dir = ext_dir.join("server");
  std::fs::create_dir_all(&server_dir)
    .map_err(|e| format!("Failed to create extension directory: {e}"))?;

  let mcp_url = format!("http://127.0.0.1:{port}/mcp/{token}");

  let manifest = serde_json::json!({
    "manifest_version": "0.3",
    "name": "donut-browser",
    "display_name": "Donut Browser",
    "version": env!("CARGO_PKG_VERSION"),
    "description": "Control Donut Browser profiles, proxies, and automation via MCP",
    "author": { "name": "Donut Browser" },
    "tools_generated": true,
    "server": {
      "type": "node",
      "entry_point": "server/index.js",
      "mcp_config": {
        "command": "node",
        "args": ["${__dirname}/server/index.js"],
        "env": {}
      }
    },
    "license": "AGPL-3.0"
  });
  std::fs::write(
    ext_dir.join("manifest.json"),
    serde_json::to_string_pretty(&manifest)
      .map_err(|e| format!("Failed to serialize manifest: {e}"))?,
  )
  .map_err(|e| format!("Failed to write manifest: {e}"))?;

  let bridge_js = format!(
    r#"#!/usr/bin/env node
const http = require("http");
const readline = require("readline");
const MCP_URL = "{mcp_url}";
let sid = null;
function post(line) {{
  return new Promise((resolve, reject) => {{
    const u = new URL(MCP_URL);
    const o = {{
      hostname: u.hostname, port: u.port, path: u.pathname, method: "POST",
      headers: {{ "Content-Type": "application/json", Accept: "application/json" }},
    }};
    if (sid) o.headers["mcp-session-id"] = sid;
    const r = http.request(o, (res) => {{
      const s = res.headers["mcp-session-id"];
      if (s) sid = s;
      let b = "";
      res.on("data", (c) => (b += c));
      res.on("end", () => resolve(b));
    }});
    r.on("error", reject);
    r.write(line);
    r.end();
  }});
}}
const rl = readline.createInterface({{ input: process.stdin, crlfDelay: Infinity }});
rl.on("line", (line) => {{
  if (!line.trim()) return;
  let notif = false;
  try {{ notif = JSON.parse(line).id == null; }} catch {{}}
  post(line).then((b) => {{
    if (!notif && b.trim()) process.stdout.write(b.trim() + "\n");
  }}).catch((e) => {{
    if (!notif) process.stdout.write(JSON.stringify({{
      jsonrpc: "2.0", id: null, error: {{ code: -32000, message: "HTTP error: " + e.message }}
    }}) + "\n");
  }});
}});
rl.on("close", () => setTimeout(() => process.exit(0), 500));
"#
  );
  std::fs::write(server_dir.join("index.js"), bridge_js)
    .map_err(|e| format!("Failed to write bridge script: {e}"))?;

  // Update the extensions-installations.json registry so Claude Desktop picks it up
  update_claude_extensions_registry("local.mcpb.donut-browser.donut-browser", Some(manifest))?;

  Ok(())
}

fn remove_mcp_from_claude_desktop_internal() -> Result<(), String> {
  let ext_dir = claude_desktop_extension_dir().ok_or("Unsupported platform")?;
  if ext_dir.exists() {
    std::fs::remove_dir_all(&ext_dir).map_err(|e| format!("Failed to remove extension: {e}"))?;
  }
  update_claude_extensions_registry("local.mcpb.donut-browser.donut-browser", None)?;
  Ok(())
}

fn update_claude_extensions_registry(
  ext_id: &str,
  manifest: Option<serde_json::Value>,
) -> Result<(), String> {
  let registry_path = claude_desktop_extension_dir()
    .ok_or("Unsupported platform")?
    .parent()
    .and_then(|p| p.parent())
    .map(|p| p.join("extensions-installations.json"))
    .ok_or("Failed to resolve registry path")?;

  let mut registry: serde_json::Value = if registry_path.exists() {
    let content = std::fs::read_to_string(&registry_path)
      .map_err(|e| format!("Failed to read registry: {e}"))?;
    serde_json::from_str(&content).unwrap_or(serde_json::json!({"extensions": {}}))
  } else {
    serde_json::json!({"extensions": {}})
  };

  if registry.get("extensions").is_none() {
    registry["extensions"] = serde_json::json!({});
  }

  match manifest {
    Some(m) => {
      registry["extensions"][ext_id] = serde_json::json!({
        "id": ext_id,
        "version": m.get("version").and_then(|v| v.as_str()).unwrap_or("0.0.0"),
        "hash": "",
        "installedAt": chrono::Utc::now().to_rfc3339(),
        "manifest": m,
        "signatureInfo": { "status": "unsigned" },
        "source": "local"
      });
    }
    None => {
      if let Some(exts) = registry
        .get_mut("extensions")
        .and_then(|e| e.as_object_mut())
      {
        exts.remove(ext_id);
      }
    }
  }

  let output =
    serde_json::to_string(&registry).map_err(|e| format!("Failed to serialize registry: {e}"))?;
  let tmp = registry_path.with_extension("json.tmp");
  std::fs::write(&tmp, &output).map_err(|e| format!("Failed to write registry: {e}"))?;
  std::fs::rename(&tmp, &registry_path).map_err(|e| format!("Failed to save registry: {e}"))?;
  Ok(())
}

async fn current_mcp_url(app_handle: &tauri::AppHandle) -> Result<String, String> {
  let mcp_server = mcp_server::McpServer::instance();
  let port = mcp_server.get_port().ok_or("MCP server is not running")?;
  let settings_manager = settings_manager::SettingsManager::instance();
  let token = settings_manager
    .get_mcp_token(app_handle)
    .await
    .map_err(|e| format!("Failed to get MCP token: {e}"))?
    .ok_or("MCP token not found")?;
  Ok(format!("http://127.0.0.1:{port}/mcp/{token}"))
}

#[tauri::command]
async fn list_mcp_agents() -> Result<Vec<mcp_integrations::McpAgentInfo>, String> {
  let claude_desktop_connected = is_mcp_in_claude_desktop_internal();
  Ok(mcp_integrations::list_agents_with_status(&[(
    "claude-desktop",
    claude_desktop_connected,
  )]))
}

#[tauri::command]
async fn add_mcp_to_agent(app_handle: tauri::AppHandle, agent_id: String) -> Result<(), String> {
  if !mcp_integrations::agent_exists(&agent_id) {
    return Err(format!("Unknown agent: {agent_id}"));
  }
  if agent_id == "claude-desktop" {
    return add_mcp_to_claude_desktop_internal(&app_handle).await;
  }
  let url = current_mcp_url(&app_handle).await?;
  mcp_integrations::install_generic(&agent_id, &url)
}

#[tauri::command]
async fn remove_mcp_from_agent(agent_id: String) -> Result<(), String> {
  if !mcp_integrations::agent_exists(&agent_id) {
    return Err(format!("Unknown agent: {agent_id}"));
  }
  if agent_id == "claude-desktop" {
    return remove_mcp_from_claude_desktop_internal();
  }
  mcp_integrations::uninstall_generic(&agent_id)
}

#[tauri::command]
async fn is_geoip_database_available() -> Result<bool, String> {
  Ok(GeoIPDownloader::is_geoip_database_available())
}

#[tauri::command]
async fn get_all_traffic_snapshots() -> Result<Vec<crate::traffic_stats::TrafficSnapshot>, String> {
  // Use real-time snapshots that merge in-memory data with disk data
  Ok(crate::traffic_stats::get_all_traffic_snapshots_realtime())
}

#[tauri::command]
async fn get_profile_traffic_snapshot(
  profile_id: String,
) -> Result<Option<crate::traffic_stats::TrafficSnapshot>, String> {
  Ok(crate::traffic_stats::get_traffic_snapshot_for_profile(
    &profile_id,
  ))
}

#[tauri::command]
async fn clear_all_traffic_stats() -> Result<(), String> {
  crate::traffic_stats::clear_all_traffic_stats()
    .map_err(|e| format!("Failed to clear traffic stats: {e}"))
}

#[tauri::command]
async fn get_traffic_stats_for_period(
  profile_id: String,
  seconds: u64,
) -> Result<Option<crate::traffic_stats::FilteredTrafficStats>, String> {
  Ok(crate::traffic_stats::get_traffic_stats_for_period(
    &profile_id,
    seconds,
  ))
}

#[tauri::command]
async fn download_geoip_database(app_handle: tauri::AppHandle) -> Result<(), String> {
  let downloader = GeoIPDownloader::instance();
  downloader
    .download_geoip_database(&app_handle)
    .await
    .map_err(|e| format!("Failed to download GeoIP database: {e}"))
}

// VPN commands
#[tauri::command]
async fn import_vpn_config(
  content: String,
  filename: String,
  name: Option<String>,
) -> Result<vpn::VpnImportResult, String> {
  let storage = vpn::VPN_STORAGE
    .lock()
    .map_err(|e| format!("Failed to lock VPN storage: {e}"))?;

  match storage.import_config(&content, &filename, name.clone()) {
    Ok(config) => {
      if config.sync_enabled {
        if let Some(scheduler) = sync::get_global_scheduler() {
          let id = config.id.clone();
          tauri::async_runtime::spawn(async move {
            scheduler.queue_vpn_sync(id).await;
          });
        }
      }
      Ok(vpn::VpnImportResult {
        success: true,
        vpn_id: Some(config.id),
        vpn_type: Some(config.vpn_type),
        name: config.name,
        error: None,
      })
    }
    Err(e) => Ok(vpn::VpnImportResult {
      success: false,
      vpn_id: None,
      vpn_type: None,
      name: name.unwrap_or_else(|| filename.clone()),
      error: Some(e.to_string()),
    }),
  }
}

#[tauri::command]
async fn list_vpn_configs() -> Result<Vec<vpn::VpnConfig>, String> {
  let storage = vpn::VPN_STORAGE
    .lock()
    .map_err(|e| format!("Failed to lock VPN storage: {e}"))?;

  storage
    .list_configs()
    .map_err(|e| format!("Failed to list VPN configs: {e}"))
}

#[tauri::command]
async fn get_vpn_config(vpn_id: String) -> Result<vpn::VpnConfig, String> {
  let storage = vpn::VPN_STORAGE
    .lock()
    .map_err(|e| format!("Failed to lock VPN storage: {e}"))?;

  storage
    .load_config(&vpn_id)
    .map_err(|e| format!("Failed to load VPN config: {e}"))
}

#[tauri::command]
async fn delete_vpn_config(app_handle: tauri::AppHandle, vpn_id: String) -> Result<(), String> {
  // First disconnect if connected (stop VPN worker)
  let _ = vpn_worker_runner::stop_vpn_worker_by_vpn_id(&vpn_id).await;

  // Check if sync was enabled before deleting
  let was_sync_enabled = {
    let storage = vpn::VPN_STORAGE
      .lock()
      .map_err(|e| format!("Failed to lock VPN storage: {e}"))?;
    storage
      .load_config(&vpn_id)
      .map(|c| c.sync_enabled)
      .unwrap_or(false)
  };

  // Delete from storage
  {
    let storage = vpn::VPN_STORAGE
      .lock()
      .map_err(|e| format!("Failed to lock VPN storage: {e}"))?;

    storage
      .delete_config(&vpn_id)
      .map_err(|e| format!("Failed to delete VPN config: {e}"))?;
  }

  // If sync was enabled, also delete from remote
  if was_sync_enabled {
    let vpn_id_clone = vpn_id.clone();
    let app_handle_clone = app_handle.clone();
    tauri::async_runtime::spawn(async move {
      match sync::SyncEngine::create_from_settings(&app_handle_clone).await {
        Ok(engine) => {
          if let Err(e) = engine.delete_vpn(&vpn_id_clone).await {
            log::warn!("Failed to delete VPN {} from sync: {}", vpn_id_clone, e);
          } else {
            log::info!("VPN {} deleted from sync storage", vpn_id_clone);
          }
        }
        Err(e) => {
          log::debug!("Sync not configured, skipping remote VPN deletion: {}", e);
        }
      }
    });
  }

  let _ = events::emit("vpn-configs-changed", ());

  Ok(())
}

#[tauri::command]
async fn create_vpn_config_manual(
  name: String,
  vpn_type: vpn::VpnType,
  config_data: String,
) -> Result<vpn::VpnConfig, String> {
  let config = {
    let storage = vpn::VPN_STORAGE
      .lock()
      .map_err(|e| format!("Failed to lock VPN storage: {e}"))?;

    storage
      .create_config_manual(&name, vpn_type, &config_data)
      .map_err(|e| format!("Failed to create VPN config: {e}"))?
  };

  if config.sync_enabled {
    if let Some(scheduler) = sync::get_global_scheduler() {
      let id = config.id.clone();
      tauri::async_runtime::spawn(async move {
        scheduler.queue_vpn_sync(id).await;
      });
    }
  }

  Ok(config)
}

#[tauri::command]
async fn update_vpn_config(vpn_id: String, name: String) -> Result<vpn::VpnConfig, String> {
  let config = {
    let storage = vpn::VPN_STORAGE
      .lock()
      .map_err(|e| format!("Failed to lock VPN storage: {e}"))?;

    storage
      .update_config_name(&vpn_id, &name)
      .map_err(|e| format!("Failed to update VPN config: {e}"))?
  };

  if config.sync_enabled {
    if let Some(scheduler) = sync::get_global_scheduler() {
      let id = config.id.clone();
      tauri::async_runtime::spawn(async move {
        scheduler.queue_vpn_sync(id).await;
      });
    }
  }

  Ok(config)
}

#[tauri::command]
async fn check_vpn_validity(
  vpn_id: String,
) -> Result<crate::proxy_manager::ProxyCheckResult, String> {
  check_vpn_validity_core(&vpn_id).await
}

pub async fn check_vpn_validity_core(
  vpn_id: &str,
) -> Result<crate::proxy_manager::ProxyCheckResult, String> {
  let now = std::time::SystemTime::now()
    .duration_since(std::time::UNIX_EPOCH)
    .unwrap_or_default()
    .as_secs();

  let had_existing_worker = vpn_worker_storage::find_vpn_worker_by_vpn_id(vpn_id).is_some();

  let vpn_worker = vpn_worker_runner::start_vpn_worker(vpn_id)
    .await
    .map_err(|e| format!("Failed to start VPN worker: {e}"))?;

  let socks_url = format!(
    "socks5://127.0.0.1:{}",
    vpn_worker.local_port.unwrap_or_default()
  );

  let local_proxy = crate::proxy_runner::start_proxy_process(Some(socks_url), None)
    .await
    .map_err(|error| error.to_string());
  let local_proxy = match local_proxy {
    Ok(proxy) => proxy,
    Err(error_message) => {
      if !had_existing_worker {
        let _ = vpn_worker_runner::stop_vpn_worker(&vpn_worker.id).await;
      }
      return Err(format!("Failed to start validation proxy: {error_message}"));
    }
  };

  let local_proxy_url = format!(
    "http://127.0.0.1:{}",
    local_proxy.local_port.unwrap_or_default()
  );

  let mut result = None;
  for attempt in 0..3 {
    if attempt > 0 {
      tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    }

    match ip_utils::fetch_public_ip(Some(&local_proxy_url)).await {
      Ok(ip) => {
        let (city, country, country_code) =
          crate::proxy_manager::ProxyManager::get_ip_geolocation(&ip)
            .await
            .unwrap_or_default();

        result = Some(crate::proxy_manager::ProxyCheckResult {
          ip,
          city,
          country,
          country_code,
          timestamp: now,
          is_valid: true,
        });
        break;
      }
      Err(error) => {
        log::warn!(
          "VPN validation attempt {} failed to fetch public IP through donut-proxy: {}",
          attempt + 1,
          error
        );
      }
    }
  }

  let _ = crate::proxy_runner::stop_proxy_process(&local_proxy.id).await;
  if !had_existing_worker {
    let _ = vpn_worker_runner::stop_vpn_worker(&vpn_worker.id).await;
  }

  let result = result.unwrap_or(crate::proxy_manager::ProxyCheckResult {
    ip: String::new(),
    city: None,
    country: None,
    country_code: None,
    timestamp: now,
    is_valid: false,
  });

  Ok(result)
}

/// Validate that a profile's selected proxy or VPN actually works before the
/// profile is created. Shared by the Tauri command, REST API, and MCP create
/// paths so a dead/unreachable proxy or VPN (or a 402 from an expired proxy
/// subscription) fails creation identically everywhere. Returns structured
/// `{ "code": ... }` error strings the frontend translates via backend-errors.ts.
pub async fn validate_profile_network(
  proxy_id: Option<&str>,
  vpn_id: Option<&str>,
) -> Result<(), String> {
  if let Some(vpn_id) = vpn_id.filter(|s| !s.is_empty()) {
    let result = check_vpn_validity_core(vpn_id).await?;
    if !result.is_valid {
      return Err(serde_json::json!({ "code": "VPN_NOT_WORKING" }).to_string());
    }
    return Ok(());
  }

  if let Some(proxy_id) = proxy_id.filter(|s| !s.is_empty()) {
    // The cloud-included proxy is managed infrastructure; its only failure mode
    // is the user hitting their usage limit, which surfaces as a 402 at request
    // time. There's nothing to pre-validate here.
    if proxy_id == crate::proxy_manager::CLOUD_PROXY_ID {
      return Ok(());
    }
    let settings = crate::proxy_manager::PROXY_MANAGER
      .get_proxy_settings_by_id(proxy_id)
      .ok_or_else(|| format!("Proxy '{proxy_id}' not found"))?;
    match crate::proxy_manager::PROXY_MANAGER
      .check_proxy_validity(proxy_id, &settings)
      .await
    {
      Ok(result) if result.is_valid => {}
      Ok(_) => {
        return Err(serde_json::json!({ "code": "PROXY_NOT_WORKING" }).to_string());
      }
      Err(err) if err.contains("402") => {
        return Err(serde_json::json!({ "code": "PROXY_PAYMENT_REQUIRED" }).to_string());
      }
      Err(_) => {
        return Err(serde_json::json!({ "code": "PROXY_NOT_WORKING" }).to_string());
      }
    }
  }

  Ok(())
}

#[tauri::command]
async fn connect_vpn(vpn_id: String) -> Result<(), String> {
  // Start VPN worker process (detached, survives GUI shutdown)
  vpn_worker_runner::start_vpn_worker(&vpn_id)
    .await
    .map_err(|e| format!("Failed to connect VPN: {e}"))?;

  // Update last_used timestamp
  {
    let storage = vpn::VPN_STORAGE
      .lock()
      .map_err(|e| format!("Failed to lock VPN storage: {e}"))?;
    let _ = storage.update_last_used(&vpn_id);
  }

  Ok(())
}

#[tauri::command]
async fn disconnect_vpn(vpn_id: String) -> Result<(), String> {
  vpn_worker_runner::stop_vpn_worker_by_vpn_id(&vpn_id)
    .await
    .map_err(|e| format!("Failed to disconnect VPN: {e}"))?;
  Ok(())
}

#[tauri::command]
async fn get_vpn_status(vpn_id: String) -> Result<vpn::VpnStatus, String> {
  use crate::proxy_storage::is_process_running;

  if let Some(worker) = vpn_worker_storage::find_vpn_worker_by_vpn_id(&vpn_id) {
    let connected = worker.pid.map(is_process_running).unwrap_or(false);
    Ok(vpn::VpnStatus {
      connected,
      vpn_id,
      connected_at: None,
      bytes_sent: None,
      bytes_received: None,
      last_handshake: None,
    })
  } else {
    Ok(vpn::VpnStatus {
      connected: false,
      vpn_id,
      connected_at: None,
      bytes_sent: None,
      bytes_received: None,
      last_handshake: None,
    })
  }
}

#[tauri::command]
async fn list_active_vpn_connections() -> Result<Vec<vpn::VpnStatus>, String> {
  use crate::proxy_storage::is_process_running;

  let workers = vpn_worker_storage::list_vpn_worker_configs();
  Ok(
    workers
      .into_iter()
      .filter(|w| w.pid.map(is_process_running).unwrap_or(false))
      .map(|w| vpn::VpnStatus {
        connected: true,
        vpn_id: w.vpn_id,
        connected_at: None,
        bytes_sent: None,
        bytes_received: None,
        last_handshake: None,
      })
      .collect(),
  )
}

#[tauri::command]
async fn generate_sample_fingerprint(
  app_handle: tauri::AppHandle,
  browser: String,
  version: String,
  config_json: String,
) -> Result<String, String> {
  let temp_profile = crate::profile::BrowserProfile {
    id: uuid::Uuid::new_v4(),
    name: "temp_fingerprint_gen".to_string(),
    browser: browser.clone(),
    version: version.clone(),
    process_id: None,
    proxy_id: None,
    vpn_id: None,
    launch_hook: None,
    last_launch: None,
    release_type: "stable".to_string(),
    wayfern_config: None,
    group_id: None,
    tags: Vec::new(),
    note: None,
    window_color: None,
    sync_mode: crate::profile::types::SyncMode::Disabled,
    encryption_salt: None,
    last_sync: None,
    host_os: None,
    ephemeral: false,
    extension_group_id: None,
    proxy_bypass_rules: Vec::new(),
    created_by_id: None,
    created_by_email: None,
    dns_blocklist: None,
    password_protected: false,
    created_at: None,
    updated_at: None,
  };

  if browser == "wayfern" {
    let config: crate::wayfern_manager::WayfernConfig =
      serde_json::from_str(&config_json).map_err(|e| format!("Failed to parse config: {e}"))?;
    let manager = crate::wayfern_manager::WayfernManager::instance();
    manager
      .generate_fingerprint_config(&app_handle, &temp_profile, &config)
      .await
      .map_err(|e| format!("Failed to generate fingerprint: {e}"))
  } else {
    Err(format!(
      "Unsupported browser for fingerprint generation: {browser}"
    ))
  }
}

/// Confirm a quit chosen from the close-confirmation dialog and exit the app.
#[tauri::command]
fn confirm_quit(app_handle: tauri::AppHandle) {
  QUIT_CONFIRMED.store(true, Ordering::SeqCst);
  app_handle.exit(0);
}

/// Hide the main window so the app keeps running behind its tray icon.
#[tauri::command]
fn hide_to_tray(app_handle: tauri::AppHandle) -> Result<(), String> {
  if let Some(window) = app_handle.get_webview_window("main") {
    window.hide().map_err(|e| e.to_string())?;
  }
  Ok(())
}

fn show_main_window(app_handle: &tauri::AppHandle) {
  if let Some(window) = app_handle.get_webview_window("main") {
    let _ = window.show();
    let _ = window.unminimize();
    let _ = window.set_focus();
  }
}

/// Update the tray menu labels with localized strings pushed from the frontend
/// (which owns the active language). The item ids are unchanged so the existing
/// menu-event handler keeps matching.
#[tauri::command]
fn update_tray_menu(
  app_handle: tauri::AppHandle,
  show_label: String,
  quit_label: String,
) -> Result<(), String> {
  use tauri::menu::{MenuBuilder, MenuItemBuilder};
  if let Some(tray) = app_handle.tray_by_id("main") {
    let show_item = MenuItemBuilder::with_id("tray_show", show_label)
      .build(&app_handle)
      .map_err(|e| e.to_string())?;
    let quit_item = MenuItemBuilder::with_id("tray_quit", quit_label)
      .build(&app_handle)
      .map_err(|e| e.to_string())?;
    let menu = MenuBuilder::new(&app_handle)
      .item(&show_item)
      .separator()
      .item(&quit_item)
      .build()
      .map_err(|e| e.to_string())?;
    tray.set_menu(Some(menu)).map_err(|e| e.to_string())?;
  }
  Ok(())
}

/// Build the system tray. Best-effort: on Linux the tray depends on
/// libayatana-appindicator at runtime, so any failure here must not abort app
/// startup — the caller logs and continues without a tray.
fn setup_system_tray(app: &tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
  use std::sync::atomic::Ordering;
  use tauri::menu::{MenuBuilder, MenuItemBuilder};
  use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};

  // Bootstrap labels only — the frontend pushes localized labels via
  // `update_tray_menu` on mount and on language change, and the menu is only
  // opened after a minimize-to-tray (post-mount), so these are never shown.
  let show_item = MenuItemBuilder::with_id("tray_show", "Show Donut Browser").build(app)?;
  let quit_item = MenuItemBuilder::with_id("tray_quit", "Quit").build(app)?;
  let tray_menu = MenuBuilder::new(app)
    .item(&show_item)
    .separator()
    .item(&quit_item)
    .build()?;

  // macOS uses the black icon as a template — the OS tints it for the light or
  // dark menu bar. Linux (and other non-Windows desktops) get a white-bodied
  // icon with a dark outline so it stays legible on both dark and light
  // panels: Tauri feeds the SNI/AppIndicator a fixed pixmap with no template
  // tinting, so the icon has to carry its own contrast (a solid black icon is
  // invisible on GNOME's dark top bar). Windows keeps its own solid icon.
  #[cfg(target_os = "macos")]
  let tray_icon_bytes: &[u8] = include_bytes!("../icons/tray-icon-44.png");
  #[cfg(target_os = "windows")]
  let tray_icon_bytes: &[u8] = include_bytes!("../icons/tray-icon-win-44.png");
  #[cfg(not(any(target_os = "macos", target_os = "windows")))]
  let tray_icon_bytes: &[u8] = include_bytes!("../icons/tray-icon-linux-44.png");
  let tray_rgba = image::load_from_memory(tray_icon_bytes)?.into_rgba8();
  let (tray_w, tray_h) = tray_rgba.dimensions();
  let tray_image = tauri::image::Image::new_owned(tray_rgba.into_raw(), tray_w, tray_h);

  TrayIconBuilder::with_id("main")
    .icon(tray_image)
    .icon_as_template(cfg!(target_os = "macos"))
    .tooltip("Donut Browser")
    .menu(&tray_menu)
    .show_menu_on_left_click(false)
    .on_menu_event(|app_handle, event| match event.id().as_ref() {
      "tray_show" => show_main_window(app_handle),
      "tray_quit" => {
        QUIT_CONFIRMED.store(true, Ordering::SeqCst);
        app_handle.exit(0);
      }
      _ => {}
    })
    .on_tray_icon_event(|tray, event| {
      // Click events are not delivered on Linux (AppIndicator/SNI only drives
      // the menu), so left-click-to-restore is macOS/Windows only — Linux users
      // restore via the "Show Donut Browser" menu item.
      if let TrayIconEvent::Click {
        button: MouseButton::Left,
        button_state: MouseButtonState::Up,
        ..
      } = event
      {
        show_main_window(tray.app_handle());
      }
    })
    .build(app)?;

  Ok(())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
  let args: Vec<String> = env::args().collect();
  let startup_url = args.iter().find(|arg| arg.starts_with("http")).cloned();

  if let Some(url) = startup_url.clone() {
    log::info!("Found startup URL in command line: {url}");
    let mut pending = PENDING_URLS.lock().unwrap();
    pending.push(url.clone());
  }

  let log_file_name = app_dirs::app_name();

  // Honor DONUTBROWSER_DATA_ROOT: when set, logs go to <root>/logs instead of
  // the platform default app log dir, so all on-disk state lives under one root.
  let file_log_target = match app_dirs::log_dir_override() {
    Some(path) => Target::new(TargetKind::Folder {
      path,
      file_name: Some(log_file_name.to_string()),
    }),
    None => Target::new(TargetKind::LogDir {
      file_name: Some(log_file_name.to_string()),
    }),
  };

  tauri::Builder::default()
    .plugin(
      tauri_plugin_log::Builder::new()
        .clear_targets() // Clear default targets to avoid duplicates
        .target(Target::new(TargetKind::Stdout))
        .target(Target::new(TargetKind::Webview))
        .target(file_log_target)
        // 5 MB per rotated file × KeepAll — the previous 100 KB limit
        // truncated useful context in customer support reports; 50 MB
        // turned out to be excessive disk pressure.
        .max_file_size(5 * 1024 * 1024)
        .rotation_strategy(tauri_plugin_log::RotationStrategy::KeepAll)
        .level(log::LevelFilter::Info)
        .format(|out, message, record| {
          use chrono::Local;
          let now = Local::now();
          let timestamp = format!(
            "{}.{:03}",
            now.format("%Y-%m-%d %H:%M:%S"),
            now.timestamp_subsec_millis()
          );
          out.finish(format_args!(
            "[{}][{}][{}] {}",
            timestamp,
            record.target(),
            record.level(),
            message
          ))
        })
        .build(),
    )
    .plugin(tauri_plugin_single_instance::init(
      |app_handle, args, _cwd| {
        log::info!("Single instance triggered with args: {args:?}");
        if let Some(window) = app_handle.get_webview_window("main") {
          let _ = window.show();
          let _ = window.set_focus();
          let _ = window.unminimize();
        }
      },
    ))
    .plugin(tauri_plugin_deep_link::init())
    .plugin(tauri_plugin_fs::init())
    .plugin(tauri_plugin_opener::init())
    .plugin(tauri_plugin_shell::init())
    .plugin(tauri_plugin_dialog::init())
    .plugin(tauri_plugin_macos_permissions::init())
    .plugin(tauri_plugin_clipboard_manager::init())
    // Persist window size/position across restarts. VISIBLE is excluded
    // because the app hides to tray: restoring visibility would otherwise
    // relaunch with an invisible window after quitting from the tray while
    // hidden. FULLSCREEN is excluded because native fullscreen is disabled
    // (the green button zooms instead) — the maximized flag captures the
    // "filled screen" state, including green-button zoom on macOS.
    .plugin(
      tauri_plugin_window_state::Builder::default()
        .with_state_flags(
          tauri_plugin_window_state::StateFlags::all()
            & !tauri_plugin_window_state::StateFlags::VISIBLE
            & !tauri_plugin_window_state::StateFlags::FULLSCREEN,
        )
        .build(),
    )
    .setup(|app| {
      // Recover ephemeral dir mappings from RAM-backed storage (tmpfs/ramdisk)
      ephemeral_dirs::recover_ephemeral_dirs();

      // Extract icons and metadata for existing extensions that don't have them yet
      {
        let mgr = extension_manager::ExtensionManager::new();
        mgr.ensure_icons_extracted();
      }

      // Create the main window programmatically
      #[allow(unused_variables)]
      let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
        .title("Donut Browser")
        .inner_size(880.0, 500.0)
        .min_inner_size(640.0, 400.0)
        .resizable(true)
        .fullscreen(false)
        .center()
        .focused(true)
        .visible(true);

      #[cfg(target_os = "windows")]
      let win_builder = win_builder.decorations(false);

      #[allow(unused_variables)]
      let window = win_builder.build().unwrap();

      // System tray so the user can keep the app running after the close
      // dialog's "Minimize" action hides the window. Best-effort: a tray
      // failure (e.g. missing libayatana-appindicator on Linux) must never
      // prevent the app from launching, so we log and continue without it.
      if let Err(e) = setup_system_tray(app.handle()) {
        log::warn!("System tray unavailable, continuing without it: {e}");
      }

      // Intercept the window close so the frontend can ask the user whether
      // to minimize or quit. The app exits when `confirm_quit` flips
      // QUIT_CONFIRMED — until then, every CloseRequested is held back.
      {
        let app_handle = app.handle().clone();
        window.on_window_event(move |event| {
          if let tauri::WindowEvent::CloseRequested { api, .. } = event {
            if QUIT_CONFIRMED.load(Ordering::SeqCst) {
              return;
            }
            api.prevent_close();
            if let Err(e) = app_handle.emit("close-confirm-requested", ()) {
              log::warn!("Failed to emit close-confirm-requested: {e}");
            }
          }
        });
      }

      // Set transparent titlebar for macOS
      #[cfg(target_os = "macos")]
      {
        if let Err(e) = window.set_transparent_titlebar(true) {
          log::warn!("Failed to set transparent titlebar: {e}");
        }
        // Green title-bar button maximizes (zoom) the window rather than
        // entering immersive native fullscreen.
        if let Err(e) = window.disable_native_fullscreen() {
          log::warn!("Failed to disable native fullscreen: {e}");
        }
      }

      // Set up deep link handler
      let handle = app.handle().clone();

      // Initialize the global event emitter for the events module
      let emitter = std::sync::Arc::new(events::TauriEmitter::new(handle.clone()));
      if let Err(e) = events::set_global_emitter(emitter) {
        log::warn!("Failed to set global event emitter: {e}");
      }

      #[cfg(windows)]
      {
        // For Windows, register all deep links at runtime
        if let Err(e) = app.deep_link().register_all() {
          log::warn!("Failed to register deep links: {e}");
        }
      }

      #[cfg(target_os = "macos")]
      {
        // On macOS, try to register deep links for development builds
        if let Err(e) = app.deep_link().register_all() {
          log::debug!(
            "Note: Deep link registration failed on macOS (this is normal for production): {e}"
          );
        }
      }

      app.deep_link().on_open_url({
        let handle = handle.clone();
        move |event| {
          let urls = event.urls();
          log::info!("Deep link event received with {} URLs", urls.len());

          for url in urls {
            let url_string = url.to_string();
            log::info!("Deep link received: {url_string}");

            // Clone the handle for each async task
            let handle_clone = handle.clone();

            // Handle the URL asynchronously
            tauri::async_runtime::spawn(async move {
              if let Err(e) = handle_url_open(handle_clone, url_string.clone()).await {
                log::error!("Failed to handle deep link URL: {e}");
              }
            });
          }
        }
      });

      if let Some(startup_url) = startup_url {
        let handle_clone = handle.clone();
        tauri::async_runtime::spawn(async move {
          log::info!("Processing startup URL from command line: {startup_url}");
          if let Err(e) = handle_url_open(handle_clone, startup_url.clone()).await {
            log::error!("Failed to handle startup URL: {e}");
          }
        });
      }

      // Initialize and start background version updater
      let app_handle = app.handle().clone();
      tauri::async_runtime::spawn(async move {
        let version_updater = get_version_updater();

        // Set the app handle
        {
          let mut updater_guard = version_updater.lock().await;
          updater_guard.set_app_handle(app_handle);
        }

        // Run startup check without holding the lock
        {
          let updater_guard = version_updater.lock().await;
          if let Err(e) = updater_guard.start_background_updates().await {
            log::error!("Failed to start background updates: {e}");
          }
        }
      });

      // Start the background update task separately
      tauri::async_runtime::spawn(async move {
        version_updater::VersionUpdater::run_background_task().await;
      });

      // Auto-start MCP server if it was previously enabled. Always log the
      // decision so customer logs reveal whether MCP is actually running —
      // "automation features don't work" is otherwise indistinguishable from
      // "MCP server isn't enabled" without this line.
      {
        let mcp_handle = app.handle().clone();
        let settings_mgr = settings_manager::SettingsManager::instance();
        match settings_mgr.load_settings() {
          Ok(settings) => {
            if settings.mcp_enabled {
              log::info!("MCP server is enabled in settings, attempting auto-start");
              tauri::async_runtime::spawn(async move {
                match mcp_server::McpServer::instance().start(mcp_handle).await {
                  Ok(port) => log::info!("MCP server auto-started on port {port}"),
                  Err(e) => log::warn!("Failed to auto-start MCP server: {e}"),
                }
              });
            } else {
              log::info!(
                "MCP server is DISABLED in settings (mcp_enabled=false). Browser automation tools will not be available until it's enabled in Settings → Integrations."
              );
            }
          }
          Err(e) => {
            log::warn!("Could not read settings to determine MCP state: {e}");
          }
        }
      }

      // Clear stale process IDs from profiles (processes that died while app was closed)
      {
        let profile_manager = crate::profile::ProfileManager::instance();
        if let Ok(profiles) = profile_manager.list_profiles() {
          let system = sysinfo::System::new_with_specifics(
            sysinfo::RefreshKind::nothing()
              .with_processes(sysinfo::ProcessRefreshKind::everything()),
          );
          for profile in profiles {
            if let Some(pid) = profile.process_id {
              let sysinfo_pid = sysinfo::Pid::from_u32(pid);
              if system.process(sysinfo_pid).is_none() {
                log::info!(
                  "Clearing stale process_id {} for profile {}",
                  pid,
                  profile.name
                );
                let mut updated = profile.clone();
                updated.process_id = None;
                let _ = profile_manager.save_profile(&updated);
              }
            }
          }
        }
      }

      // Kill orphaned proxy and VPN worker processes from previous app runs.
      // Since active_proxies is an in-memory map that starts empty, any running
      // donut-proxy workers on disk must be orphans the current app can't track.
      // Without this cleanup, users on Windows accumulate dozens of idle workers
      // (one per profile launch) that the periodic cleanup won't touch because
      // profile-associated workers are deliberately skipped to avoid regressions.
      //
      // Preserves workers whose associated profile still has a running browser
      // process — if the app crashed while a browser was running, its detached
      // browser keeps going and needs the proxy/VPN worker to stay alive.
      tauri::async_runtime::spawn(async move {
        use crate::proxy_storage::{delete_proxy_config, is_process_running, list_proxy_configs};
        use crate::vpn_worker_storage::{delete_vpn_worker_config, list_vpn_worker_configs};

        // Build sets of (profile_id, vpn_id) whose browsers are still running
        let profile_manager = crate::profile::ProfileManager::instance();
        let profiles = profile_manager.list_profiles().unwrap_or_default();

        let running_profile_ids: std::collections::HashSet<String> = profiles
          .iter()
          .filter(|p| p.process_id.is_some_and(is_process_running))
          .map(|p| p.id.to_string())
          .collect();

        let running_vpn_ids: std::collections::HashSet<String> = profiles
          .iter()
          .filter(|p| p.process_id.is_some_and(is_process_running))
          .filter_map(|p| p.vpn_id.clone())
          .collect();

        for config in list_proxy_configs() {
          let has_running_browser = config
            .profile_id
            .as_ref()
            .is_some_and(|pid| running_profile_ids.contains(pid));
          if has_running_browser {
            log::info!(
              "Startup: preserving proxy worker {} (profile browser still running)",
              config.id
            );
            continue;
          }

          if let Some(pid) = config.pid {
            if is_process_running(pid) {
              log::info!(
                "Startup: killing orphaned proxy worker {} (PID {})",
                config.id,
                pid
              );
              let _ = crate::proxy_runner::stop_proxy_process(&config.id).await;
              continue;
            }
          }
          delete_proxy_config(&config.id);
        }

        for worker in list_vpn_worker_configs() {
          if running_vpn_ids.contains(&worker.vpn_id) {
            log::info!(
              "Startup: preserving VPN worker {} (profile browser using vpn_id {} still running)",
              worker.id,
              worker.vpn_id
            );
            continue;
          }

          if let Some(pid) = worker.pid {
            if is_process_running(pid) {
              log::info!(
                "Startup: killing orphaned VPN worker {} (PID {})",
                worker.id,
                pid
              );
              let _ = crate::vpn_worker_runner::stop_vpn_worker(&worker.id).await;
              continue;
            }
          }
          delete_vpn_worker_config(&worker.id);
        }
      });

      // Immediately bump non-running profiles to the latest installed browser version.
      // This runs synchronously before any network calls so profiles are updated on launch.
      {
        let app_handle_bump = app.handle().clone();
        match auto_updater::AutoUpdater::instance()
          .update_profiles_to_latest_installed(&app_handle_bump)
        {
          Ok(updated) => {
            if !updated.is_empty() {
              log::info!(
                "Startup: bumped {} profiles to latest installed versions: {:?}",
                updated.len(),
                updated
              );
            }
          }
          Err(e) => {
            log::error!("Startup: failed to bump profiles to latest installed versions: {e}");
          }
        }
      }

      let app_handle_auto_updater = app.handle().clone();

      // Start the auto-update check task separately
      tauri::async_runtime::spawn(async move {
        auto_updater::check_for_updates_with_progress(app_handle_auto_updater).await;
      });

      // Handle any pending URLs that were received before the window was ready
      let handle_pending = handle.clone();
      tauri::async_runtime::spawn(async move {
        // Wait a bit for the window to be fully ready
        tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;

        let pending_urls = {
          let mut pending = PENDING_URLS.lock().unwrap();
          let urls = pending.clone();
          pending.clear();
          urls
        };

        for url in pending_urls {
          log::info!("Processing pending URL: {url}");
          if let Err(e) = handle_url_open(handle_pending.clone(), url).await {
            log::error!("Failed to handle pending URL: {e}");
          }
        }
      });

      // Start periodic cleanup task for unused binaries
      // Only runs when sync is not in progress to avoid deleting browsers
      // that might be needed for profiles being synced from the cloud
      tauri::async_runtime::spawn(async move {
        let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(43200)); // Every 12 hours

        loop {
          interval.tick().await;

          // Check if sync is in progress before running cleanup
          if let Some(scheduler) = sync::get_global_scheduler() {
            if scheduler.is_sync_in_progress().await {
              log::debug!("Skipping cleanup: sync is in progress");
              continue;
            }
          }

          let registry =
            crate::downloaded_browsers_registry::DownloadedBrowsersRegistry::instance();
          if let Err(e) = registry.cleanup_unused_binaries() {
            log::error!("Periodic cleanup failed: {e}");
          } else {
            log::debug!("Periodic cleanup completed successfully");
          }
        }
      });

      // DNS blocklist refresh task (every 12 hours)
      tauri::async_runtime::spawn(async move {
        let manager = dns_blocklist::BlocklistManager::instance();
        let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(43200));
        interval.tick().await; // Skip the immediate first tick
        loop {
          interval.tick().await;
          manager.refresh_all_stale().await;
        }
      });

      tauri::async_runtime::spawn(async move {
        let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(3 * 60 * 60));

        loop {
          interval.tick().await;

          log::info!("Checking for app updates...");
          // Route through check_for_app_updates (not the raw check_for_updates)
          // so the background loop respects portable mode and the
          // disable_auto_updates setting. Previously it bypassed both, so a
          // portable install would auto-download and run the NSIS installer,
          // clobbering the portable folder instead of updating in place.
          match app_auto_updater::check_for_app_updates().await {
            Ok(Some(update_info)) => {
              log::info!(
                "App update available: {} -> {}",
                update_info.current_version,
                update_info.new_version
              );
              if let Err(e) = events::emit("app-update-available", &update_info) {
                log::error!("Failed to emit app update event: {e}");
              }
            }
            Ok(None) => {
              log::debug!("No app updates available");
            }
            Err(e) => {
              log::error!("Failed to check for app updates: {e}");
            }
          }
        }
      });

      // Check and download GeoIP database at startup if needed
      let app_handle_geoip = app.handle().clone();
      tauri::async_runtime::spawn(async move {
        // Wait a bit for the app to fully initialize
        tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

        let geoip_downloader = crate::geoip_downloader::GeoIPDownloader::instance();
        match geoip_downloader.check_missing_geoip_database() {
          Ok(true) => {
            log::info!(
              "GeoIP database is missing for Wayfern profiles, downloading at startup..."
            );
            let geoip_downloader = GeoIPDownloader::instance();
            if let Err(e) = geoip_downloader
              .download_geoip_database(&app_handle_geoip)
              .await
            {
              log::error!("Failed to download GeoIP database at startup: {e}");
            } else {
              log::info!("GeoIP database downloaded successfully at startup");
            }
          }
          Ok(false) => {
            // No Wayfern profiles or GeoIP database already available
          }
          Err(e) => {
            log::error!("Failed to check GeoIP database status at startup: {e}");
          }
        }
      });

      // Start proxy cleanup task for dead browser processes
      let app_handle_proxy_cleanup = app.handle().clone();
      tauri::async_runtime::spawn(async move {
        let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(30));

        loop {
          interval.tick().await;

          match crate::proxy_manager::PROXY_MANAGER
            .cleanup_dead_proxies(app_handle_proxy_cleanup.clone())
            .await
          {
            Ok(dead_pids) => {
              if !dead_pids.is_empty() {
                log::info!(
                  "Cleaned up proxies for {} dead browser processes",
                  dead_pids.len()
                );
              }
            }
            Err(e) => {
              log::error!("Error during proxy cleanup: {e}");
            }
          }
        }
      });

      // Periodically broadcast browser running status to the frontend.
      // When no profiles have stored PIDs (nothing was ever launched this
      // session), we use a long interval (30s) to avoid burning CPU on
      // full process-table scans via sysinfo. Once any profile is running
      // we switch to the fast interval (5s) for responsive UI updates.
      let app_handle_status = app.handle().clone();
      tauri::async_runtime::spawn(async move {
        const FAST_INTERVAL_SECS: u64 = 5;
        const IDLE_INTERVAL_SECS: u64 = 30;

        let mut interval =
          tokio::time::interval(tokio::time::Duration::from_secs(FAST_INTERVAL_SECS));
        interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
        let mut last_running_states: std::collections::HashMap<String, bool> =
          std::collections::HashMap::new();
        let mut current_interval_secs = FAST_INTERVAL_SECS;

        loop {
          interval.tick().await;

          let runner = crate::browser_runner::BrowserRunner::instance();
          let profiles = match runner.profile_manager.list_profiles() {
            Ok(p) => p,
            Err(e) => {
              log::warn!("Failed to list profiles in status checker: {e}");
              continue;
            }
          };

          // If no profile has a stored PID and we have no previously-known
          // running states, there's nothing to check — skip the expensive
          // process scan entirely.
          let any_has_pid = profiles.iter().any(|p| p.process_id.is_some());
          let any_was_running = last_running_states.values().any(|&v| v);

          if !any_has_pid && !any_was_running {
            // Switch to the idle interval to reduce CPU
            if current_interval_secs != IDLE_INTERVAL_SECS {
              current_interval_secs = IDLE_INTERVAL_SECS;
              interval =
                tokio::time::interval(tokio::time::Duration::from_secs(IDLE_INTERVAL_SECS));
              interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
            }
            continue;
          }

          // At least one profile might be running — use the fast interval
          if current_interval_secs != FAST_INTERVAL_SECS {
            current_interval_secs = FAST_INTERVAL_SECS;
            interval = tokio::time::interval(tokio::time::Duration::from_secs(FAST_INTERVAL_SECS));
            interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
          }

          // Only walk profiles that either have a stored PID or that we last
          // saw as running — for users with hundreds of idle profiles this
          // turns an O(N) sysinfo scan into an O(running) scan. The Rust
          // launch path always emits profile-running-changed when a profile
          // STARTS, so newly-running profiles still get tracked here.
          let profiles_to_check: Vec<_> = profiles
            .into_iter()
            .filter(|p| {
              p.process_id.is_some()
                || last_running_states
                  .get(&p.id.to_string())
                  .copied()
                  .unwrap_or(false)
            })
            .collect();

          for profile in profiles_to_check {
            let had_pid = profile.process_id.is_some();
            // Check browser status and track changes
            match runner
              .check_browser_status(app_handle_status.clone(), &profile)
              .await
            {
              Ok(is_running) => {
                let profile_id = profile.id.to_string();
                let last_state = last_running_states
                  .get(&profile_id)
                  .copied()
                  .unwrap_or(false);

                // Emit when the running state changed, or when we still had a
                // stored PID but the browser is gone — the launch path sets the
                // frontend to "running" immediately, and a missed transition
                // here leaves the stop button stuck.
                let should_emit =
                  last_state != is_running || (!is_running && had_pid);

                if should_emit {
                  log::debug!(
                    "Status checker detected change for profile {}: {} -> {}",
                    profile.name,
                    last_state,
                    is_running
                  );

                  #[derive(serde::Serialize)]
                  struct RunningChangedPayload {
                    id: String,
                    is_running: bool,
                  }

                  let payload = RunningChangedPayload {
                    id: profile_id.clone(),
                    is_running,
                  };

                  if let Err(e) = events::emit("profile-running-changed", &payload) {
                    log::warn!("Failed to emit profile running changed event: {e}");
                  } else {
                    log::debug!(
                      "Status checker emitted profile-running-changed event for {}: running={}",
                      profile.name,
                      is_running
                    );
                  }

                  // Re-encrypt password-protected profiles when the browser
                  // exits naturally (user closing the window) — the explicit
                  // kill path in browser_runner.rs handles app-driven stops.
                  // Must run BEFORE `mark_profile_stopped` because that
                  // releases any queued sync run, and a sync that picks up
                  // the on-disk dir before re-encryption finishes uploads
                  // the previous snapshot (issue: encrypted profiles not
                  // syncing fresh data).
                  if !is_running && profile.password_protected {
                    crate::profile::password::complete_after_quit_and_wait(&profile)
                      .await;
                  }

                  // Notify sync scheduler of running state changes
                  if let Some(scheduler) = sync::get_global_scheduler() {
                    if is_running {
                      scheduler.mark_profile_running(&profile_id).await;
                    } else {
                      // Sync was queued at launch; mark_profile_stopped triggers it
                      scheduler.mark_profile_stopped(&profile_id).await;
                    }
                  }

                  // Release the cloud team lock when the browser exits naturally
                  // (window closed by the user). The explicit kill path in
                  // browser_runner.rs already releases it, but this branch did
                  // not — leaking the lock, which the 30s heartbeat then renews
                  // indefinitely. No-op for non-sync/non-paid
                  // profiles thanks to the guards inside the helper.
                  if !is_running {
                    crate::team_lock::release_team_lock_if_needed(&profile).await;
                  }

                  last_running_states.insert(profile_id, is_running);
                } else {
                  // Update the state even if unchanged to ensure we have it tracked
                  last_running_states.insert(profile_id, is_running);
                }
              }
              Err(e) => {
                log::warn!("Status check failed for profile {}: {}", profile.name, e);
                continue;
              }
            }
          }
        }
      });

      // Nodecar warm-up is now triggered from the frontend to allow UI blocking overlay

      // Start API server if enabled in settings
      let app_handle_api = app.handle().clone();
      tauri::async_runtime::spawn(async move {
        match crate::settings_manager::get_app_settings(app_handle_api.clone()).await {
          Ok(settings) => {
            if settings.api_enabled {
              log::info!("API is enabled in settings, starting API server...");
              match crate::api_server::start_api_server_internal(settings.api_port, &app_handle_api)
                .await
              {
                Ok(port) => {
                  log::info!("API server started successfully on port {port}");
                  // Emit success toast to frontend
                  if let Err(e) = events::emit(
                    "show-toast",
                    crate::api_server::ToastPayload {
                      message: "API server started successfully".to_string(),
                      variant: "success".to_string(),
                      title: "Local API Started".to_string(),
                      description: Some(format!("API server running on port {port}")),
                    },
                  ) {
                    log::error!("Failed to emit API start toast: {e}");
                  }
                }
                Err(e) => {
                  log::error!("Failed to start API server at startup: {e}");
                  // Emit error toast to frontend
                  if let Err(toast_err) = events::emit(
                    "show-toast",
                    crate::api_server::ToastPayload {
                      message: "Failed to start API server".to_string(),
                      variant: "error".to_string(),
                      title: "Failed to Start Local API".to_string(),
                      description: Some(format!("Error: {e}")),
                    },
                  ) {
                    log::error!("Failed to emit API error toast: {toast_err}");
                  }
                }
              }
            }
          }
          Err(e) => {
            log::error!("Failed to load app settings for API startup: {e}");
          }
        }
      });

      // Start sync subscription and scheduler if configured
      let app_handle_sync = app.handle().clone();
      tauri::async_runtime::spawn(async move {
        use std::sync::Arc;

        let mut subscription_manager = sync::SubscriptionManager::new();
        let work_rx = subscription_manager.take_work_receiver();

        if let Err(e) = subscription_manager.start(app_handle_sync.clone()).await {
          log::warn!("Failed to start sync subscription: {e}");
        }

        if let Some(work_rx) = work_rx {
          let scheduler = Arc::new(sync::SyncScheduler::new());

          // Set the global scheduler so commands can access it
          sync::set_global_scheduler(scheduler.clone());

          // Start initial sync for all enabled profiles
          scheduler.sync_all_enabled_profiles(&app_handle_sync).await;

          // Check for missing synced profiles (deleted locally but exist remotely)
          match sync::SyncEngine::create_from_settings(&app_handle_sync).await {
            Ok(engine) => {
              if let Err(e) = engine
                .check_for_missing_synced_profiles(&app_handle_sync)
                .await
              {
                log::warn!("Failed to check for missing profiles: {}", e);
              }
              if let Err(e) = engine
                .check_for_missing_synced_entities(&app_handle_sync)
                .await
              {
                log::warn!("Failed to check for missing entities: {}", e);
              }
            }
            Err(e) => {
              log::warn!("Sync not configured, skipping missing profile check: {}", e);
            }
          }

          scheduler
            .clone()
            .start(app_handle_sync.clone(), work_rx)
            .await;
          log::info!("Sync scheduler started");
        }
      });

      // Start cloud auth background refresh loop
      let app_handle_cloud = app.handle().clone();
      tauri::async_runtime::spawn(async move {
        // On startup, refresh sync token, proxy config, and wayfern token in
        // PARALLEL. Previously they were awaited sequentially, so the wayfern
        // token request didn't even start until the earlier two API calls had
        // finished. Wayfern launch can race with this task — a few seconds of
        // serialized API calls translates directly into a slow first launch
        // because launch_wayfern blocks waiting for the token to land.
        // api_call_with_retry handles 401/refresh internally — no direct
        // refresh_access_token call needed.
        if cloud_auth::CLOUD_AUTH.is_logged_in().await {
          let sync_token_fut = async {
            if let Err(e) = cloud_auth::CLOUD_AUTH.get_or_refresh_sync_token().await {
              log::warn!("Failed to refresh cloud sync token on startup: {e}");
            }
          };
          let proxy_fut = async {
            cloud_auth::CLOUD_AUTH.sync_cloud_proxy().await;
          };
          let wayfern_fut = async {
            if cloud_auth::CLOUD_AUTH.has_active_paid_subscription().await {
              if let Err(e) = cloud_auth::CLOUD_AUTH.request_wayfern_token().await {
                log::warn!("Failed to request wayfern token on startup: {e}");
              }
            }
          };
          tokio::join!(sync_token_fut, proxy_fut, wayfern_fut);
        }
        cloud_auth::CloudAuthManager::start_sync_token_refresh_loop(app_handle_cloud).await;
      });

      Ok(())
    })
    .invoke_handler(tauri::generate_handler![
      confirm_quit,
      hide_to_tray,
      update_tray_menu,
      get_supported_browsers,
      is_browser_supported_on_platform,
      download_browser,
      cancel_download,
      delete_profile,
      clone_profile,
      check_browser_exists,
      create_browser_profile_new,
      list_browser_profiles,
      launch_browser_profile,
      fetch_browser_versions_with_count,
      fetch_browser_versions_cached_first,
      fetch_browser_versions_with_count_cached_first,
      get_downloaded_browser_versions,
      get_all_tags,
      get_browser_release_types,
      update_profile_proxy,
      update_profile_vpn,
      update_profile_tags,
      update_profile_note,
      update_profile_launch_hook,
      update_profile_window_color,
      update_profile_proxy_bypass_rules,
      update_profile_dns_blocklist,
      check_browser_status,
      kill_browser_profile,
      rename_profile,
      get_app_settings,
      save_app_settings,
      read_log_files,
      open_log_directory,
      get_table_sorting_settings,
      save_table_sorting_settings,
      get_system_language,
      get_system_info,
      dismiss_window_resize_warning,
      get_window_resize_warning_dismissed,
      get_onboarding_completed,
      complete_onboarding,
      clear_all_version_cache_and_refetch,
      is_default_browser,
      open_url_with_profile,
      set_as_default_browser,
      trigger_manual_version_update,
      get_version_update_status,
      check_for_browser_updates,
      dismiss_update_notification,
      complete_browser_update_with_auto_update,
      check_for_app_updates,
      check_for_app_updates_manual,
      download_and_prepare_app_update,
      restart_application,
      detect_existing_profiles,
      import_browser_profile,
      check_missing_binaries,
      check_missing_geoip_database,
      ensure_all_binaries_exist,
      ensure_active_browsers_downloaded,
      create_stored_proxy,
      get_stored_proxies,
      update_stored_proxy,
      delete_stored_proxy,
      check_proxy_validity,
      get_cached_proxy_check,
      export_proxies,
      import_proxies_json,
      parse_txt_proxies,
      import_proxies_from_parsed,
      update_wayfern_config,
      generate_sample_fingerprint,
      get_profile_groups,
      get_groups_with_profile_counts,
      create_profile_group,
      update_profile_group,
      delete_profile_group,
      assign_profiles_to_group,
      delete_selected_profiles,
      list_extensions,
      get_extension_icon,
      add_extension,
      update_extension,
      delete_extension,
      list_extension_groups,
      create_extension_group,
      update_extension_group,
      delete_extension_group,
      add_extension_to_group,
      remove_extension_from_group,
      assign_extension_group_to_profile,
      get_extension_group_for_profile,
      is_geoip_database_available,
      download_geoip_database,
      start_api_server,
      stop_api_server,
      get_api_server_status,
      get_all_traffic_snapshots,
      get_profile_traffic_snapshot,
      clear_all_traffic_stats,
      get_traffic_stats_for_period,
      get_sync_settings,
      save_sync_settings,
      set_profile_sync_mode,
      cancel_profile_sync,
      request_profile_sync,
      set_proxy_sync_enabled,
      set_group_sync_enabled,
      is_proxy_in_use_by_synced_profile,
      is_group_in_use_by_synced_profile,
      set_vpn_sync_enabled,
      is_vpn_in_use_by_synced_profile,
      set_extension_sync_enabled,
      set_extension_group_sync_enabled,
      get_unsynced_entity_counts,
      enable_sync_for_all_entities,
      set_e2e_password,
      check_has_e2e_password,
      verify_e2e_password,
      delete_e2e_password,
      rollover_encryption_for_all_entities,
      read_profile_cookies,
      get_profile_cookie_stats,
      copy_profile_cookies,
      import_cookies_from_file,
      export_profile_cookies,
      check_wayfern_terms_accepted,
      check_wayfern_downloaded,
      accept_wayfern_terms,
      get_commercial_trial_status,
      acknowledge_trial_expiration,
      has_acknowledged_trial_expiration,
      start_mcp_server,
      stop_mcp_server,
      get_mcp_server_status,
      get_mcp_config,
      list_mcp_agents,
      add_mcp_to_agent,
      remove_mcp_from_agent,
      // VPN commands
      import_vpn_config,
      list_vpn_configs,
      get_vpn_config,
      delete_vpn_config,
      create_vpn_config_manual,
      update_vpn_config,
      check_vpn_validity,
      connect_vpn,
      disconnect_vpn,
      get_vpn_status,
      list_active_vpn_connections,
      // Cloud auth commands
      cloud_auth::cloud_exchange_device_code,
      cloud_auth::cloud_get_user,
      cloud_auth::cloud_refresh_profile,
      cloud_auth::cloud_logout,
      cloud_auth::cloud_get_proxy_usage,
      cloud_auth::cloud_get_countries,
      cloud_auth::cloud_get_regions,
      cloud_auth::cloud_get_cities,
      cloud_auth::cloud_get_isps,
      cloud_auth::create_cloud_location_proxy,
      cloud_auth::restart_sync_service,
      cloud_auth::cloud_get_wayfern_token,
      cloud_auth::cloud_refresh_wayfern_token,
      // Team lock commands
      team_lock::get_team_locks,
      team_lock::get_team_lock_status,
      // Synchronizer commands
      synchronizer::start_sync_session,
      synchronizer::stop_sync_session,
      synchronizer::remove_sync_follower,
      synchronizer::get_sync_sessions,
      // DNS blocklist commands
      dns_blocklist::get_dns_blocklist_cache_status,
      dns_blocklist::refresh_dns_blocklists,
      // Profile password commands
      set_profile_password,
      change_profile_password,
      remove_profile_password,
      verify_profile_password,
      unlock_profile,
      lock_profile,
      is_profile_locked,
    ])
    .build(tauri::generate_context!())
    .expect("error while building tauri application")
    .run(|_app_handle, _event| {
      #[cfg(target_os = "macos")]
      if let tauri::RunEvent::Reopen { .. } = _event {
        if let Some(window) = _app_handle.get_webview_window("main") {
          let _ = window.show();
          let _ = window.set_focus();
          let _ = window.unminimize();
        }
      }
    });
}

#[cfg(test)]
mod tests {
  use std::fs;

  #[test]
  fn test_no_unused_tauri_commands() {
    check_unused_commands(false); // Run in strict mode for CI
  }

  #[test]
  fn test_unused_tauri_commands_detailed() {
    check_unused_commands(true); // Run in verbose mode for development
  }

  fn check_unused_commands(verbose: bool) {
    // Commands that are intentionally not used in the frontend
    // but are used via MCP server or other programmatic APIs
    let mcp_only_commands = [
      "connect_vpn",
      "disconnect_vpn",
      "get_vpn_status",
      "get_vpn_config",
      "list_active_vpn_connections",
      "export_profile_cookies",
      "update_extension",
      "set_extension_sync_enabled",
      "set_extension_group_sync_enabled",
      "get_team_lock_status",
      "generate_sample_fingerprint",
      "cloud_get_wayfern_token",
      "cloud_refresh_wayfern_token",
      "lock_profile",
    ];

    // Extract command names from the generate_handler! macro in this file
    let lib_rs_content = fs::read_to_string("src/lib.rs").expect("Failed to read lib.rs");
    let commands = extract_tauri_commands(&lib_rs_content);

    // Get all frontend files
    let frontend_files = get_frontend_files("../src");

    // Check which commands are actually used
    let mut unused_commands = Vec::new();
    let mut used_commands = Vec::new();

    for command in &commands {
      // Skip commands that are intentionally MCP-only
      if mcp_only_commands.contains(&command.as_str()) {
        used_commands.push(command.clone());
        if verbose {
          println!("✅ {command} (MCP-only)");
        }
        continue;
      }

      let mut is_used = false;

      for file_content in &frontend_files {
        // More comprehensive search for command usage
        if is_command_used(file_content, command) {
          is_used = true;
          break;
        }
      }

      if is_used {
        used_commands.push(command.clone());
        if verbose {
          println!("✅ {command}");
        }
      } else {
        unused_commands.push(command.clone());
        if verbose {
          println!("❌ {command} (UNUSED)");
        }
      }
    }

    if verbose {
      println!("\n📊 Summary:");
      println!("  ✅ Used commands: {}", used_commands.len());
      println!("  ❌ Unused commands: {}", unused_commands.len());
    }

    if !unused_commands.is_empty() {
      let message = format!(
        "Found {} unused Tauri commands: {}\n\nThese commands are exported in generate_handler! but not used in the frontend.\nConsider removing them or add them to the allowlist if they're used elsewhere.\n\nRun `pnpm check-unused-commands` for detailed analysis.",
        unused_commands.len(),
        unused_commands.join(", ")
      );

      if verbose {
        println!("\n🚨 {message}");
      } else {
        panic!("{}", message);
      }
    } else if verbose {
      println!("\n🎉 All exported commands are being used!");
    } else {
      println!(
        "✅ All {} exported Tauri commands are being used in the frontend",
        commands.len()
      );
    }
  }

  fn is_command_used(content: &str, command: &str) -> bool {
    // Check various patterns for invoke usage
    let patterns = vec![
      format!("invoke<{}>(\"{}\"", "", command), // invoke<Type>("command"
      format!("invoke(\"{}\"", command),         // invoke("command"
      format!("invoke<{}>(\"{}\",", "", command), // invoke<Type>("command",
      format!("invoke(\"{}\",", command),        // invoke("command",
      format!("\"{}\"", command),                // Just the command name in quotes
    ];

    for pattern in patterns {
      if content.contains(&pattern) {
        return true;
      }
    }

    // Also check for the command name appearing after "invoke" within a reasonable distance
    if let Some(invoke_pos) = content.find("invoke") {
      let after_invoke = &content[invoke_pos..];
      if let Some(cmd_pos) = after_invoke.find(&format!("\"{command}\"")) {
        // If the command appears within 100 characters of "invoke", consider it used
        if cmd_pos < 100 {
          return true;
        }
      }
    }

    false
  }

  fn extract_tauri_commands(content: &str) -> Vec<String> {
    let mut commands = Vec::new();

    // Find the generate_handler! macro
    if let Some(start) = content.find("tauri::generate_handler![") {
      if let Some(end) = content[start..].find("])") {
        let handler_content = &content[start + 25..start + end]; // Skip "tauri::generate_handler!["

        // Extract command names
        for line in handler_content.lines() {
          let line = line.trim();
          if !line.is_empty() && !line.starts_with("//") {
            // Remove trailing comma and whitespace
            let command = line.trim_end_matches(',').trim();
            if !command.is_empty() {
              // Strip module prefix (e.g., "cloud_auth::cloud_get_user" -> "cloud_get_user")
              let command = command.rsplit("::").next().unwrap_or(command);
              commands.push(command.to_string());
            }
          }
        }
      }
    }

    commands
  }

  fn get_frontend_files(src_dir: &str) -> Vec<String> {
    let mut files_content = Vec::new();

    if let Ok(entries) = fs::read_dir(src_dir) {
      for entry in entries.flatten() {
        let path = entry.path();

        if path.is_dir() {
          // Recursively read subdirectories
          let subdir_files = get_frontend_files(&path.to_string_lossy());
          files_content.extend(subdir_files);
        } else if let Some(extension) = path.extension() {
          if matches!(
            extension.to_str(),
            Some("ts") | Some("tsx") | Some("js") | Some("jsx")
          ) {
            if let Ok(content) = fs::read_to_string(&path) {
              files_content.push(content);
            }
          }
        }
      }
    }

    files_content
  }
}
