//! Athen desktop application -- Tauri composition root.
//!
//! This crate wires all Athen components together and exposes them
//! to the frontend through Tauri IPC commands.

pub(crate) mod app_tools;
pub(crate) mod approval;
mod commands;
pub(crate) mod compaction;
mod contacts;
pub(crate) mod delegation;
pub(crate) mod email_gate;
pub(crate) mod file_gate;
pub(crate) mod notifier;
pub(crate) mod process;
pub(crate) mod sense_router;
mod settings;
pub(crate) mod state;
pub(crate) mod telegram_progress;

use state::AppState;

/// Build and run the Tauri application.
pub fn run() {
    // Initialize tracing with RUST_LOG env filter (defaults to info).
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .init();

    // Install the rustls crypto provider before anything else uses TLS.
    // Both reqwest (for LLM calls) and rustls-connector (for IMAP) need this.
    let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();

    // If the wizard has previously installed portable Python / Node into
    // <athen_data_dir>/toolbox/runtimes/, prepend their bin dirs to PATH
    // BEFORE anything probes for runtimes or builds shell envs. Done at
    // process scope so every later Command::new("python") / "node" /
    // "pip" / "npm" resolves to the portable copy without per-call
    // plumbing.
    athen_agent::runtimes::init_portable_path();
    tauri::Builder::default()
        .plugin(tauri_plugin_updater::Builder::new().build())
        .plugin(tauri_plugin_process::init())
        .invoke_handler(tauri::generate_handler![
            commands::send_message,
            commands::get_status,
            commands::approve_task,
            commands::submit_approval,
            commands::cancel_task,
            commands::new_arc,
            commands::get_arc_history,
            commands::get_arc_entries,
            commands::compact_arc,
            commands::list_arcs,
            commands::switch_arc,
            commands::rename_arc,
            commands::delete_arc,
            commands::get_current_arc,
            commands::branch_arc,
            commands::merge_arcs,
            commands::get_timeline_data,
            settings::get_settings,
            settings::save_provider,
            settings::delete_provider,
            settings::test_provider,
            settings::save_settings,
            settings::set_active_provider,
            settings::is_first_launch,
            settings::complete_onboarding,
            settings::detect_device_capabilities,
            settings::list_provider_catalog,
            settings::save_email_settings,
            settings::test_email_connection,
            settings::save_smtp_settings,
            settings::test_smtp_connection,
            settings::save_telegram_settings,
            settings::test_telegram_connection,
            settings::save_web_search_settings,
            settings::test_web_search_provider,
            commands::list_calendar_events,
            commands::create_calendar_event,
            commands::update_calendar_event,
            commands::delete_calendar_event,
            contacts::list_contacts,
            contacts::get_contact,
            contacts::set_contact_trust,
            contacts::block_contact,
            contacts::unblock_contact,
            contacts::delete_contact,
            contacts::create_contact,
            contacts::update_contact,
            commands::mark_notification_seen,
            commands::list_notifications,
            commands::mark_notification_read,
            commands::mark_all_notifications_read,
            commands::delete_notification,
            commands::delete_read_notifications,
            settings::get_notification_settings,
            settings::save_notification_settings,
            settings::save_embedding_settings,
            settings::test_embedding_provider,
            commands::list_memories,
            commands::update_memory,
            commands::delete_memory,
            commands::list_entities,
            commands::list_relations,
            commands::update_entity,
            commands::delete_entity,
            commands::delete_relation,
            commands::list_mcp_catalog,
            commands::enable_mcp,
            commands::disable_mcp,
            commands::list_pending_grants,
            commands::resolve_pending_grant,
            commands::list_arc_grants,
            commands::list_global_grants,
            commands::add_global_grant,
            commands::revoke_arc_grant,
            commands::revoke_global_grant,
            commands::list_agent_profiles,
            commands::set_arc_profile,
            commands::create_agent_profile,
            commands::update_agent_profile,
            commands::delete_agent_profile,
            commands::restore_agent_profile,
            commands::check_for_update,
            commands::install_update,
            commands::list_toolbox_packages,
            commands::clear_toolbox,
            commands::get_runtime_status,
            commands::install_runtime,
        ])
        .setup(|app| {
            use tauri::Manager;

            // Build the application state asynchronously (loads config, opens database).
            let mut state = tauri::async_runtime::block_on(AppState::new());

            // Register an agent so tasks can be dispatched.
            let agent_id = uuid::Uuid::new_v4();
            tauri::async_runtime::block_on(async {
                state
                    .coordinator
                    .dispatcher()
                    .register_agent(agent_id)
                    .await;
            });

            // Initialize the notification orchestrator (needs AppHandle for InApp channel).
            state.init_notifier(app.handle().clone());

            // Initialize the approval router (InApp + Telegram sinks). Must
            // come before start_telegram_monitor so the poll loop can pick
            // up the Telegram sink for callback resolution.
            state.init_approval_router(app.handle().clone());

            // Start background monitor tasks before managing state.
            state.start_email_monitor(app.handle().clone());
            state.start_calendar_monitor(app.handle().clone());
            state.start_telegram_monitor(app.handle().clone());

            // Start the autonomous-execution dispatch loop. Must come
            // after the agent has been registered with the coordinator's
            // dispatcher (above), otherwise dispatch_next_with_task
            // can't assign anything.
            state.start_dispatch_loop(app.handle().clone());

            app.manage(state);

            // Track window focus state for the notification orchestrator.
            // When the window loses focus, the orchestrator routes notifications
            // to external channels (Telegram) instead of in-app.
            // Also intercept window close: hide to tray instead of exiting,
            // so the Telegram poll loop and other background work keep
            // running. The tray menu provides a real Quit.
            let state_ref = app.state::<AppState>();
            let notifier_for_focus = state_ref.notifier.clone();
            if let Some(window) = app.get_webview_window("main") {
                let win_for_event = window.clone();
                window.on_window_event(move |event| match event {
                    tauri::WindowEvent::Focused(focused) => {
                        if let Some(ref notifier) = notifier_for_focus {
                            notifier.set_user_present(*focused);
                        }
                    }
                    tauri::WindowEvent::CloseRequested { api, .. } => {
                        api.prevent_close();
                        let _ = win_for_event.hide();
                    }
                    _ => {}
                });
            }

            // Tray icon: left-click toggles the window, right-click menu
            // exposes Show / Quit. `Quit` is the only path that actually
            // exits the process now that close-to-tray is wired.
            use tauri::menu::{MenuBuilder, MenuItemBuilder};
            use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};

            let show_item = MenuItemBuilder::with_id("show", "Show Athen").build(app)?;
            let quit_item = MenuItemBuilder::with_id("quit", "Quit").build(app)?;
            let tray_menu = MenuBuilder::new(app)
                .item(&show_item)
                .separator()
                .item(&quit_item)
                .build()?;

            let _tray = TrayIconBuilder::with_id("main")
                .icon(
                    app.default_window_icon()
                        .cloned()
                        .ok_or("missing default window icon")?,
                )
                .tooltip("Athen")
                .menu(&tray_menu)
                .show_menu_on_left_click(false)
                .on_menu_event(|app, event| match event.id().as_ref() {
                    "show" => {
                        if let Some(w) = app.get_webview_window("main") {
                            let _ = w.show();
                            let _ = w.unminimize();
                            let _ = w.set_focus();
                        }
                    }
                    "quit" => app.exit(0),
                    _ => {}
                })
                .on_tray_icon_event(|tray, event| {
                    if let TrayIconEvent::Click {
                        button: MouseButton::Left,
                        button_state: MouseButtonState::Up,
                        ..
                    } = event
                    {
                        let app = tray.app_handle();
                        if let Some(w) = app.get_webview_window("main") {
                            // Toggle: hide if visible+focused, show otherwise.
                            let visible = w.is_visible().unwrap_or(false);
                            if visible {
                                let _ = w.hide();
                            } else {
                                let _ = w.show();
                                let _ = w.unminimize();
                                let _ = w.set_focus();
                            }
                        }
                    }
                })
                .build(app)?;

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running Athen");
}
