// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

pub mod cli;
mod cover_cache;
mod library_analysis_backfill;
mod lib_commands;
mod theme_import;
pub mod theme_animation;

pub use psysonic_integration::discord;

pub use psysonic_core::logging;
pub use psysonic_core::{app_eprintln, app_deprintln};
pub use psysonic_core::user_agent::{
    default_subsonic_wire_user_agent, runtime_subsonic_wire_user_agent, subsonic_wire_user_agent,
};
pub use psysonic_analysis::{analysis_cache, analysis_runtime};
pub use psysonic_audio as audio;
pub use psysonic_syncfs::{sync_cancel_flags, DownloadSemaphore};
#[cfg(target_os = "windows")]
mod taskbar_win;
mod tray_runtime;

pub(crate) use tray_runtime::*;

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

use tauri::{Emitter, Manager};
use lib_commands::*;

/// Tracks which user-configured shortcuts are currently registered (shortcut_str → action).
/// Prevents on_shortcut() accumulating duplicate handlers across JS reloads (HMR / StrictMode).
type ShortcutMap = Mutex<HashMap<String, String>>;

/// Maximum number of offline track downloads that can run concurrently.
/// The frontend queues more tasks than this; Rust is the real throttle.
const MAX_DL_CONCURRENCY: usize = 4;

/// Shared handle to OS media controls (MPRIS2 on Linux, Now Playing on macOS, SMTC on Windows).
/// `None` if souvlaki failed to initialize (e.g. no D-Bus session on Linux).
type MprisControls = Mutex<Option<souvlaki::MediaControls>>;

/// Release builds only: focus or CLI-hand off when a second instance is launched.
#[cfg(not(debug_assertions))]
fn on_second_instance<R: tauri::Runtime>(
    app: &tauri::AppHandle<R>,
    argv: Vec<String>,
    _cwd: String,
) {
    if !crate::cli::handle_cli_on_primary_instance(app, &argv) {
        let window = app.get_webview_window("main").expect("no main window");
        // The window may have been hidden via the close-to-tray path,
        // which injects PAUSE_RENDERING_JS (sets `__psyHidden=true`,
        // pauses CSS animations). Tray-icon restore mirrors this with
        // RESUME_RENDERING_JS — second-launch restore must do the same,
        // otherwise the webview comes back with rendering still paused
        // and navigation looks blank (issue #497).
        let _ = window.eval(RESUME_RENDERING_JS);
        let _ = window.show();
        let _ = window.unminimize();
        let _ = window.set_focus();
    }
}

pub fn run() {
    // Linux: second `psysonic --player …` forwards over D-Bus before heavy startup.
    #[cfg(target_os = "linux")]
    {
        let argv: Vec<String> = std::env::args().collect();
        if crate::cli::parse_cli_command(&argv).is_some() {
            match crate::cli::linux_try_forward_player_cli_secondary(&argv) {
                Ok(crate::cli::LinuxPlayerForwardResult::Forwarded) => std::process::exit(0),
                Ok(crate::cli::LinuxPlayerForwardResult::ContinueStartup) => {}
                Err(msg) => {
                    crate::app_eprintln!("NOT OK: {msg}");
                    std::process::exit(1);
                }
            }
        }
    }

    let (audio_engine, _audio_thread) = audio::create_engine();

    let builder = tauri::Builder::default()
        .manage(audio_engine)
        .manage(ShortcutMap::default())
        .manage(discord::DiscordState::new())
        .manage(Arc::new(tokio::sync::Semaphore::new(MAX_DL_CONCURRENCY)) as DownloadSemaphore)
        .manage(TrayState::default())
        .manage(TrayTooltip::default())
        .manage(TrayPlaybackState::default())
        .manage(TrayMenuItemsState::default())
        .manage(TrayMenuLabelsState::default())
        .plugin(tauri_plugin_process::init())
        .plugin(tauri_plugin_updater::Builder::new().build())
        .plugin(
            tauri_plugin_window_state::Builder::default()
                .with_state_flags(
                    tauri_plugin_window_state::StateFlags::all()
                        & !tauri_plugin_window_state::StateFlags::VISIBLE,
                )
                .with_denylist(&["mini"])
                .build()
        )
        .plugin(tauri_plugin_shell::init())
        .plugin(tauri_plugin_global_shortcut::Builder::new().build())
        .plugin(tauri_plugin_store::Builder::default().build())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_fs::init());

    #[cfg(not(debug_assertions))]
    let builder = builder.plugin(tauri_plugin_single_instance::init(on_second_instance));

    builder
        .setup(|app| {
            #[cfg(debug_assertions)]
            if let Some(window) = app.get_webview_window("main") {
                let _ = window.set_title("Psysonic (Dev)");
            }

            // ── Dev: `--theme-watch <theme.css>` live theme reload ─────────
            // Poll a local theme.css and push it into the running app on save,
            // so theme authors get a live loop without re-importing a zip. The
            // frontend (dev only) installs it under the id in its
            // `[data-theme='<id>']` selector and applies it. Dev-builds only.
            #[cfg(debug_assertions)]
            {
                let args: Vec<String> = std::env::args().collect();
                if let Some(i) = args.iter().position(|a| a == "--theme-watch") {
                    match args.get(i + 1).cloned() {
                        Some(path) => {
                            eprintln!("[theme-watch] watching {path}");
                            let handle = app.handle().clone();
                            std::thread::spawn(move || {
                                let p = std::path::PathBuf::from(&path);
                                let mut last_css = String::new();
                                loop {
                                    if let Ok(css) = std::fs::read_to_string(&p) {
                                        if css != last_css {
                                            last_css = css.clone();
                                            let _ = handle.emit("theme-watch:css", css);
                                        }
                                    }
                                    std::thread::sleep(std::time::Duration::from_millis(300));
                                }
                            });
                        }
                        None => eprintln!("[theme-watch] usage: --theme-watch <path/to/theme.css>"),
                    }
                }
            }

            // ── Analysis cache (SQLite) ───────────────────────────────────
            {
                let cache = analysis_cache::AnalysisCache::init(app.handle())
                    .map_err(|e| format!("analysis cache init failed: {e}"))?;
                app.manage(cache);
            }

            cover_cache::init_cover_cache(app.handle())
                .map_err(|e| format!("cover cache init failed: {e}"))?;

            library_analysis_backfill::init_library_analysis_backfill(app.handle())
                .map_err(|e| format!("library analysis backfill init failed: {e}"))?;

            // ── Library track store (psysonic-library, PR-5a + PR-5b) ─────
            // PR-5a brought up the read-only Tauri surface + LibraryRuntime.
            // PR-5b adds the mutating commands, sync session map, current-job
            // tracker, and the 30-second background scheduler tick task below
            // — which sweeps every bound session through
            // `BackgroundScheduler::tick` while honouring the runtime's
            // `scheduler_cancel` flag.
            {
                let store = psysonic_library::store::LibraryStore::init(app.handle())
                    .map_err(|e| format!("library store init failed: {e}"))?;
                let runtime = psysonic_library::LibraryRuntime::new(std::sync::Arc::new(store));
                app.manage(runtime);

                let app_for_sched = app.handle().clone();
                tauri::async_runtime::spawn(async move {
                    use std::sync::atomic::Ordering;
                    use std::time::Duration;
                    use tokio::time::MissedTickBehavior;

                    let mut interval = tokio::time::interval(Duration::from_secs(30));
                    interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
                    loop {
                        interval.tick().await;
                        let Some(state) = app_for_sched
                            .try_state::<psysonic_library::LibraryRuntime>()
                        else {
                            break;
                        };
                        if state.scheduler_cancel.load(Ordering::SeqCst) {
                            break;
                        }
                        let sessions = state.snapshot_sessions();
                        if sessions.is_empty() {
                            continue;
                        }
                        let hint = state.current_playback_hint();
                        let now_ms = std::time::SystemTime::now()
                            .duration_since(std::time::UNIX_EPOCH)
                            .map(|d| d.as_millis().min(i64::MAX as u128) as i64)
                            .unwrap_or(0);
                        for session in sessions {
                            let scope = session.library_scope.clone().unwrap_or_default();
                            let flags_bits = psysonic_library::repos::SyncStateRepository::new(
                                &state.store,
                            )
                            .get_capability_flags(&session.server_id, &scope)
                            .ok()
                            .flatten()
                            .unwrap_or(0);
                            let flags = psysonic_library::sync::capability::CapabilityFlags::new(
                                flags_bits,
                            );
                            let subsonic = psysonic_integration::subsonic::SubsonicClient::new(
                                session.base_url.clone(),
                                session.username.clone(),
                                session.password.clone(),
                            );
                            let mut sched =
                                psysonic_library::sync::scheduler::BackgroundScheduler::new(
                                    &state.store,
                                    &subsonic,
                                    session.server_id.clone(),
                                    scope.clone(),
                                    flags,
                                )
                                .with_playback_hint(hint);
                            if let Some(tok) = session.navidrome_token.clone() {
                                sched = sched.with_navidrome_credentials(
                                    psysonic_library::sync::capability::NavidromeProbeCredentials {
                                        server_url: session.base_url.clone(),
                                        bearer_token: tok,
                                    },
                                );
                            }
                            let foreground_job = state
                                .current_job()
                                .is_some_and(|j| j.server_id == session.server_id);
                            if foreground_job {
                                sched = sched.with_foreground_sync_job_active(true);
                            }
                            let _ = sched.tick(now_ms).await;
                            // Background ticks stay silent in PR-5b — Tauri
                            // emit for the scheduler path lands when the
                            // Settings panel needs it (PR-5c). Manual
                            // `library_sync_start` already emits via its
                            // own orchestrator.
                        }
                    }
                });
            }

            audio::cleanup_orphan_stream_spill_dir(app.handle());

            // ── Playback-query port (analysis → audio back-edge) ──────────
            // Two closures, each capturing an AppHandle, so analysis_runtime
            // can ask AudioEngine playback questions without depending on the
            // audio crate.
            {
                let app_is_playing = app.handle().clone();
                let app_defer = app.handle().clone();
                let handle = psysonic_core::ports::PlaybackQueryHandle::new(
                    move |track_id| {
                        app_is_playing
                            .try_state::<crate::audio::AudioEngine>()
                            .is_some_and(|e| crate::audio::analysis_track_id_is_current_playback(&e, track_id))
                    },
                    move |track_id| {
                        app_defer
                            .try_state::<crate::audio::AudioEngine>()
                            .is_some_and(|e| crate::audio::ranged_loudness_backfill_should_defer(&e, track_id))
                    },
                );
                app.manage(handle);
            }

            app.manage(psysonic_analysis::analysis_runtime::PlaybackPriorityHints::default());

            // ── Content-hash sink (analysis → library E2 back-edge) ───────
            // After a seed the analysis pipeline records the playback-derived
            // md5_16kb as `track.content_hash` so id-remap can rebind a track
            // when the server reassigns ids. Decoupled from psysonic-library
            // via a psysonic-core port; a no-op when the library has no row for
            // the (server_id, track_id) — i.e. the index is off for that server.
            {
                let app_for_hash = app.handle().clone();
                let sink = psysonic_core::ports::ContentHashSink::new(
                    move |server_id: &str, track_id: &str, md5: &str| {
                        if let Some(runtime) =
                            app_for_hash.try_state::<psysonic_library::LibraryRuntime>()
                        {
                            let _ = psysonic_library::commands::patch_content_hash(
                                &runtime, server_id, track_id, md5,
                            );
                        }
                    },
                );
                app.manage(sink);
            }

            // ── Analysis-readiness query (library → analysis E3 back-edge) ──
            // `library_get_track` enrichment asks whether waveform/loudness are
            // cached for (server_id, track_id, content_hash). Read-only probe:
            // exact key then legacy '' fallback, no re-tag. Decoupled from
            // psysonic-analysis via a psysonic-core port.
            {
                let app_for_readiness = app.handle().clone();
                let query = psysonic_core::ports::AnalysisReadinessQuery::new(
                    move |server_id: &str, track_id: &str, md5: &str| {
                        let Some(cache) = app_for_readiness
                            .try_state::<analysis_cache::AnalysisCache>()
                        else {
                            return (false, false);
                        };
                        let probe = |sid: &str| {
                            let key = analysis_cache::TrackKey {
                                server_id: sid.to_string(),
                                track_id: track_id.to_string(),
                                md5_16kb: md5.to_string(),
                            };
                            let wf = cache.get_waveform(&key).ok().flatten().is_some();
                            let ld = cache.loudness_row_exists_for_key(&key).unwrap_or(false);
                            (wf, ld)
                        };
                        let (wf, ld) = probe(server_id);
                        // Legacy '' fallback for rows analysed before E1 wiring.
                        let wf = wf || (!server_id.is_empty() && probe("").0);
                        let ld = ld || (!server_id.is_empty() && probe("").1);
                        (wf, ld)
                    },
                );
                app.manage(query);
            }

            // ── Analysis needs-work probe (library → analysis batch scan) ──
            {
                use psysonic_core::ports::TrackAnalysisNeedsWorkQuery;
                let app_for_needs_work = app.handle().clone();
                let needs_work = TrackAnalysisNeedsWorkQuery::new(
                    move |server_id: &str, track_id: &str| {
                        psysonic_analysis::analysis_runtime::track_analysis_needs_work(
                            &app_for_needs_work,
                            server_id,
                            track_id,
                        )
                    },
                );
                app.manage(needs_work);
            }

            // ── Track enrichment port (analysis → library facts) ───────────
            {
                use psysonic_core::track_enrichment::{TrackEnrichmentPlan, TrackEnrichmentPort};
                use std::time::{SystemTime, UNIX_EPOCH};

                fn enrichment_now_unix_ms() -> i64 {
                    SystemTime::now()
                        .duration_since(UNIX_EPOCH)
                        .map(|d| d.as_millis() as i64)
                        .unwrap_or(0)
                }

                let app_for_enrichment_plan = app.handle().clone();
                let app_for_enrichment_store = app.handle().clone();
                let port = TrackEnrichmentPort::new(
                    move |server_id: &str, track_id: &str, content_hash: &str| {
                        let Some(runtime) =
                            app_for_enrichment_plan.try_state::<psysonic_library::LibraryRuntime>()
                        else {
                            return TrackEnrichmentPlan::default();
                        };
                        match psysonic_library::enrichment::plan_track_enrichment(
                            &runtime.store,
                            server_id,
                            track_id,
                            content_hash,
                            enrichment_now_unix_ms(),
                        ) {
                            Ok(plan) => plan,
                            Err(e) => {
                                eprintln!(
                                    "[enrichment] plan failed server_id={server_id} track_id={track_id}: {e}"
                                );
                                TrackEnrichmentPlan {
                                    need_bpm: true,
                                    need_valence: true,
                                    need_arousal: true,
                                    need_moods: true,
                                }
                            }
                        }
                    },
                    move |server_id: &str,
                          track_id: &str,
                          content_hash: &str,
                          facts: &psysonic_core::track_enrichment::TrackEnrichmentFacts| {
                        let Some(runtime) =
                            app_for_enrichment_store.try_state::<psysonic_library::LibraryRuntime>()
                        else {
                            return Err("library runtime unavailable".into());
                        };
                        psysonic_library::enrichment::store_track_enrichment_facts(
                            &runtime.store,
                            server_id,
                            track_id,
                            content_hash,
                            facts,
                            enrichment_now_unix_ms(),
                        )
                    },
                );
                app.manage(port);
            }

            // Periodic analysis queue sizes (debug logging mode only).
            tauri::async_runtime::spawn(psysonic_analysis::analysis_runtime::analysis_queue_snapshot_loop());

            // ── Custom title bar on Linux ─────────────────────────────────
            // Remove OS window decorations on all Linux so the React TitleBar
            // can take over.  The frontend checks is_tiling_wm() to decide
            // whether to actually render the TitleBar (hidden on tiling WMs).
            #[cfg(target_os = "linux")]
            {
                use tauri::Manager;
                let handle = app.handle().clone();
                sync_wayland_text_profile_cache_from_disk(&handle);
                if let Some(win) = app.get_webview_window("main") {
                    let _ = win.set_decorations(false);
                    let _ = linux_webkit_apply_wayland_gpu_font_tuning(&win);
                    let _ = linux_webkit_reapply_cached_wayland_text_render_profile(&win);
                    // Suppress WebKit's own MPRIS player so radio (HTML <audio>)
                    // doesn't duplicate the souvlaki one (issue #1048).
                    let _ = linux_webkit_disable_media_session(&win);
                }
            }

            // ── System tray ───────────────────────────────────────────────
            // Always build on startup when possible; the frontend calls toggle_tray_icon(false)
            // immediately after load if the user has disabled the tray icon.
            // May be skipped if Ayatana/AppIndicator libraries are missing (no panic).
            {
                if let Some(tray) = try_build_tray_icon(app.handle()) {
                    *app.state::<TrayState>().lock().unwrap() = Some(tray);
                }
            }

            // ── MPRIS2 / OS media controls via souvlaki ──────────────────
            // Release only: debug builds share the D-Bus name / SMTC slot with prod.
            #[cfg(not(debug_assertions))]
            {
                use souvlaki::{MediaControlEvent, MediaControls, PlatformConfig};

                // Collect pre-conditions and the platform-specific HWND.
                // Returns None early (with a log) on any unrecoverable condition
                // so app.manage() always executes exactly once at the bottom.
                let maybe_controls: Option<MediaControls> = (|| {
                    // Linux: requires a live D-Bus session.
                    #[cfg(target_os = "linux")]
                    {
                        let dbus_ok = std::env::var("DBUS_SESSION_BUS_ADDRESS")
                            .map(|v| !v.is_empty())
                            .unwrap_or(false);
                        if !dbus_ok {
                            crate::app_eprintln!("[Psysonic] No D-Bus session — MPRIS media controls disabled");
                            return None;
                        }
                    }

                    // Windows: souvlaki SMTC must hook into the existing Win32
                    // message loop rather than spinning up its own. Pass the
                    // main window's HWND so it can do so. If we can't get one,
                    // skip init (no crash, just no media overlay).
                    #[cfg(target_os = "windows")]
                    let hwnd = {
                        use tauri::Manager;
                        let h = app.get_webview_window("main")
                            .and_then(|w| w.hwnd().ok())
                            .map(|h| h.0);
                        if h.is_none() {
                            crate::app_eprintln!("[Psysonic] Could not get HWND — Windows media controls disabled");
                            return None;
                        }
                        h
                    };
                    #[cfg(not(target_os = "windows"))]
                    let hwnd: Option<*mut std::ffi::c_void> = None;

                    let config = PlatformConfig {
                        dbus_name: "psysonic",
                        display_name: "Psysonic",
                        hwnd,
                    };

                    match MediaControls::new(config) {
                        Ok(mut controls) => {
                            let app_handle = app.handle().clone();
                            if let Err(e) = controls.attach(move |event: MediaControlEvent| {
                                match event {
                                    MediaControlEvent::Toggle
                                    | MediaControlEvent::Play
                                    | MediaControlEvent::Pause => {
                                        let _ = app_handle.emit("media:play-pause", ());
                                    }
                                    MediaControlEvent::Next => {
                                        let _ = app_handle.emit("media:next", ());
                                    }
                                    MediaControlEvent::Previous => {
                                        let _ = app_handle.emit("media:prev", ());
                                    }
                                    MediaControlEvent::Seek(direction) => {
                                        use souvlaki::SeekDirection;
                                        let delta: f64 = match direction {
                                            SeekDirection::Forward  =>  5.0,
                                            SeekDirection::Backward => -5.0,
                                        };
                                        let _ = app_handle.emit("media:seek-relative", delta);
                                    }
                                    MediaControlEvent::SetPosition(pos) => {
                                        let secs = pos.0.as_secs_f64();
                                        let _ = app_handle.emit("media:seek-absolute", secs);
                                    }
                                    _ => {}
                                }
                            }) {
                                crate::app_eprintln!("[Psysonic] Failed to attach media controls: {e:?}");
                            }
                            Some(controls)
                        }
                        Err(e) => {
                            crate::app_eprintln!("[Psysonic] Could not create media controls: {e:?}");
                            None
                        }
                    }
                })();

                app.manage(MprisControls::new(maybe_controls));
            }
            #[cfg(debug_assertions)]
            {
                app.manage(MprisControls::new(None));
            }

            // ── Windows Taskbar Thumbnail Toolbar ────────────────────────
            #[cfg(all(target_os = "windows", not(debug_assertions)))]
            {
                use tauri::Manager;
                if let Some(w) = app.get_webview_window("main") {
                    if let Ok(hwnd) = w.hwnd() {
                        taskbar_win::init(app.handle(), hwnd.0 as isize);
                    }
                }
            }

            // ── Audio device-change watcher ───────────────────────────────
            {
                use tauri::Manager;
                let engine = app.state::<audio::AudioEngine>();
                audio::start_device_watcher(&engine, app.handle().clone());
                audio::start_stream_idle_watcher(app.handle().clone());
            }

            // ── Reopen output after system sleep/resume (WASAPI / PipeWire etc.)
            audio::register_post_sleep_audio_recovery(app.handle().clone());

            // ── Pre-create mini player window (Windows) ──────────────────
            // Creating the second WebView2 webview lazily from an invoke
            // handler on Windows reliably stalls the Tauri event loop —
            // the mini shows a blank white window, neither main nor mini
            // can be closed, and the user has to kill the process via
            // Task Manager. Building it at startup (hidden) avoids the
            // runtime-creation code path entirely; later `open_mini_player`
            // calls are pure show/hide.
            #[cfg(target_os = "windows")]
            {
                if let Err(e) = build_mini_player_window(app.handle(), false) {
                    crate::app_eprintln!("[psysonic] Failed to pre-create mini window: {e}");
                }
            }

            // Cold start with `--player …`: defer emit so the webview can register listeners.
            crate::cli::spawn_deferred_cli_argv_handler(app.handle());

            Ok(())
        })
        .on_window_event(|window, event| {
            // Persist mini player position whenever the user drags it.
            if window.label() == "mini" {
                if let tauri::WindowEvent::Moved(pos) = event {
                    persist_mini_pos_throttled(window.app_handle(), pos.x, pos.y);
                }
            }

            if let tauri::WindowEvent::CloseRequested { api, .. } = event {
                if window.label() == "main" {
                    api.prevent_close();

                    #[cfg(target_os = "macos")]
                    {
                        // On macOS the red close button quits the app entirely.
                        // Route through JS so playback position + Orbit state get
                        // flushed; exit_app on the way back stops the audio engine.
                        let _ = window.emit("app:force-quit", ());
                    }

                    #[cfg(not(target_os = "macos"))]
                    {
                        // Pause rendering before JS decides whether to hide to tray or exit.
                        if let Some(w) = window.app_handle().get_webview_window("main") {
                            let _ = w.eval(PAUSE_RENDERING_JS);
                        }
                        // Let JS decide: minimize to tray or exit, based on user setting.
                        let _ = window.emit("window:close-requested", ());
                    }
                } else if window.label() == "mini" {
                    // Native close on the mini: hide instead of destroying so
                    // state is preserved, and restore the main window.
                    api.prevent_close();
                    if let Some(w) = window.app_handle().get_webview_window("mini") {
                        let _ = w.eval(PAUSE_RENDERING_JS);
                    }
                    let _ = window.hide();
                    if let Some(main) = window.app_handle().get_webview_window("main") {
                        let _ = main.unminimize();
                        let _ = main.show();
                        let _ = main.set_focus();
                    }
                }
            }
        })
        .on_page_load(|webview, payload| {
            if webview.label() != "main" {
                return;
            }

            match payload.event() {
                tauri::webview::PageLoadEvent::Started => {
                    let window = webview.window().clone();
                    std::thread::spawn(move || {
                        std::thread::sleep(std::time::Duration::from_millis(48));
                        let _ = window.show();
                    });
                }
                tauri::webview::PageLoadEvent::Finished => {
                    let _ = webview.window().show();
                }
            }
        })
        .invoke_handler(tauri::generate_handler![
            greet,
            theme_import::import_theme_zip,
            backup_export_library_db,
            backup_import_library_db,
            backup_export_full,
            backup_import_full,
            migration_inspect,
            migration_run,
            resolve_host_addresses,
            psysonic_syncfs::sync::batch::calculate_sync_payload,
            exit_app,
            cli_publish_player_snapshot,
            cli_publish_library_list,
            cli_publish_server_list,
            cli_publish_search_results,
            set_window_decorations,
            set_linux_webkit_smooth_scrolling,
            linux_wayland_gpu_font_tuning_active,
            linux_wayland_text_render_settings_available,
            set_linux_wayland_text_render_profile,
            set_logging_mode,
            get_logging_mode,
            tail_runtime_logs,
            export_runtime_logs,
            frontend_debug_log,
            performance_cpu_snapshot,
            set_subsonic_wire_user_agent,
            no_compositing_mode,
            theme_animation_risk,
            linux_xdg_session_type,
            is_tiling_wm_cmd,
            open_mini_player,
            preload_mini_player,
            close_mini_player,
            set_mini_player_always_on_top,
            resize_mini_player,
            show_main_window,
            pause_rendering,
            resume_rendering,
            register_global_shortcut,
            unregister_global_shortcut,
            mpris_set_metadata,
            mpris_set_playback,
            audio::commands::audio_play,
            audio::transport_commands::audio_pause,
            audio::transport_commands::audio_resume,
            audio::transport_commands::audio_stop,
            audio::transport_commands::audio_seek,
            audio::mix_commands::audio_set_volume,
            audio::mix_commands::audio_update_replay_gain,
            audio::mix_commands::audio_set_eq,
            audio::mix_commands::audio_set_playback_rate,
            audio::autoeq_commands::autoeq_entries,
            audio::autoeq_commands::autoeq_fetch_profile,
            audio::preload_commands::audio_preload,
            audio::radio_commands::audio_play_radio,
            audio::preview::audio_preview_play,
            audio::preview::audio_preview_stop,
            audio::preview::audio_preview_stop_silent,
            audio::preview::audio_preview_set_volume,
            audio::mix_commands::audio_set_crossfade,
            audio::mix_commands::audio_set_gapless,
            audio::mix_commands::audio_set_normalization,
            audio::device_commands::audio_list_devices,
            audio::device_commands::audio_canonicalize_selected_device,
            audio::device_commands::audio_default_output_device_name,
            audio::device_commands::audio_set_device,
            audio::commands::audio_chain_preload,
            psysonic_integration::discord::discord_update_presence,
            psysonic_integration::discord::discord_clear_presence,
            psysonic_integration::remote::audioscrobbler_request,
            psysonic_integration::remote::listenbrainz_request,
            psysonic_integration::remote::maloja_request,
            psysonic_integration::navidrome::covers::upload_playlist_cover,
            psysonic_integration::navidrome::covers::upload_radio_cover,
            psysonic_integration::navidrome::covers::upload_artist_image,
            psysonic_integration::navidrome::covers::delete_radio_cover,
            psysonic_integration::navidrome::users::navidrome_login,
            psysonic_integration::navidrome::users::nd_list_users,
            psysonic_integration::navidrome::users::nd_create_user,
            psysonic_integration::navidrome::users::nd_update_user,
            psysonic_integration::navidrome::users::nd_delete_user,
            psysonic_integration::navidrome::queries::nd_list_libraries,
            psysonic_integration::navidrome::queries::nd_list_songs,
            psysonic_integration::navidrome::queries::nd_list_artists_by_role,
            psysonic_integration::navidrome::queries::nd_list_albums_by_artist_role,
            psysonic_integration::navidrome::queries::nd_set_user_libraries,
            psysonic_integration::navidrome::playlists::nd_list_playlists,
            psysonic_integration::navidrome::playlists::nd_create_playlist,
            psysonic_integration::navidrome::playlists::nd_update_playlist,
            psysonic_integration::navidrome::playlists::nd_get_playlist,
            psysonic_integration::navidrome::playlists::nd_delete_playlist,
            psysonic_integration::navidrome::queries::nd_get_song_path,
            psysonic_integration::remote::search_radio_browser,
            psysonic_integration::remote::get_top_radio_stations,
            psysonic_integration::remote::fetch_url_bytes,
            psysonic_integration::remote::fetch_json_url,
            psysonic_integration::remote::fetch_icy_metadata,
            psysonic_integration::remote::resolve_stream_url,
            psysonic_analysis::commands::analysis_get_waveform,
            psysonic_analysis::commands::analysis_get_waveform_for_track,
            psysonic_analysis::commands::analysis_get_loudness_for_track,
            psysonic_analysis::commands::analysis_delete_loudness_for_track,
            psysonic_analysis::commands::analysis_delete_waveform_for_track,
            psysonic_analysis::commands::analysis_delete_all_waveforms,
            psysonic_analysis::commands::analysis_delete_all_for_server,
            psysonic_analysis::commands::analysis_get_failed_track_count,
            psysonic_analysis::commands::analysis_list_failed_tracks,
            psysonic_analysis::commands::analysis_clear_failed_tracks,
            psysonic_analysis::commands::analysis_migrate_server_index_keys,
            psysonic_analysis::commands::analysis_enqueue_seed_from_url,
            psysonic_analysis::commands::analysis_set_playback_priority_hints,
            psysonic_analysis::commands::analysis_set_pipeline_parallelism,
            psysonic_analysis::commands::analysis_get_pipeline_queue_stats,
            psysonic_analysis::commands::analysis_get_backfill_queue_stats,
            psysonic_analysis::commands::analysis_prune_pending_to_track_ids,
            psysonic_library::commands::library_get_status,
            psysonic_library::commands::library_search,
            psysonic_library::commands::library_live_search,
            psysonic_library::commands::library_advanced_search,
            psysonic_library::commands::library_list_lossless_albums,
            psysonic_library::commands::library_list_albums_by_genre,
            psysonic_library::commands::library_genre_tags_inspect,
            psysonic_library::commands::library_genre_tags_run,
            psysonic_library::commands::library_get_artist_lossless_browse,
            psysonic_library::commands::library_search_cross_server,
            psysonic_library::commands::library_get_track,
            psysonic_library::commands::library_get_tracks_batch,
            psysonic_library::commands::library_get_tracks_by_album,
            psysonic_library::commands::library_upsert_songs_from_api,
            psysonic_library::commands::library_get_artifact,
            psysonic_library::commands::library_get_facts,
            psysonic_library::commands::library_get_offline_path,
            psysonic_library::commands::library_analysis_progress,
            psysonic_library::commands::library_count_live_tracks,
            psysonic_library::commands::library_sync_bind_session,
            psysonic_library::commands::library_sync_clear_session,
            psysonic_library::commands::library_set_playback_hint,
            psysonic_library::commands::library_get_playback_hint,
            psysonic_library::commands::library_sync_start,
            psysonic_library::commands::library_sync_verify_integrity,
            psysonic_library::commands::library_sync_cancel,
            psysonic_library::commands::library_patch_track,
            psysonic_library::browse_support::library_reconcile_album_stars,
            psysonic_library::browse_support::library_get_catalog_year_bounds,
            psysonic_library::browse_support::library_get_genre_album_counts,
            psysonic_library::commands::library_put_artifact,
            psysonic_library::commands::library_put_fact,
            psysonic_library::commands::library_record_play_session,
            psysonic_library::commands::library_get_player_stats_year_summary,
            psysonic_library::commands::library_get_player_stats_heatmap,
            psysonic_library::commands::library_get_player_stats_day_detail,
            psysonic_library::commands::library_get_player_stats_year_bounds,
            psysonic_library::commands::library_get_player_stats_recent_days,
            psysonic_library::commands::library_purge_server,
            psysonic_library::commands::library_migrate_server_index_keys,
            psysonic_library::commands::library_delete_server_data,
            psysonic_library::commands::library_analysis_backfill_batch,
            library_analysis_backfill::library_analysis_backfill_configure,
            psysonic_library::commands::library_resolve_cover_entry,
            cover_cache::cover_cache_peek_batch,
            cover_cache::cover_cache_ensure,
            cover_cache::cover_cache_ensure_batch,
            cover_cache::cover_cache_stats,
            cover_cache::cover_cache_evict_tick,
            cover_cache::cover_cache_configure,
            cover_cache::cover_cache_clear,
            cover_cache::cover_cache_clear_server,
            cover_cache::cover_cache_rename_server_bucket,
            cover_cache::cover_cache_stats_server,
            cover_cache::cover_cache_get_pipeline_queue_stats,
            cover_cache::library_cover_backfill_batch,
            cover_cache::library_cover_progress,
            cover_cache::library_cover_catalog_size,
            cover_cache::library_cover_clear_fetch_failures,
            cover_cache::library_cover_backfill_configure,
            cover_cache::library_cover_backfill_set_base_url,
            cover_cache::library_cover_backfill_pulse,
            cover_cache::library_cover_backfill_reset_cursor,
            cover_cache::library_cover_backfill_set_ui_priority,
            cover_cache::library_cover_backfill_set_parallel,
            cover_cache::library_cover_backfill_run_full_pass,
            cover_cache::cover_revalidate_enqueue,
            cover_cache::cover_revalidate_tick,
            cover_cache::cover_revalidate_batch,
            psysonic_syncfs::cache::offline::download_track_offline,
            psysonic_syncfs::cache::offline::cancel_offline_downloads,
            psysonic_syncfs::cache::offline::clear_offline_cancel,
            psysonic_syncfs::cache::offline::delete_offline_track,
            psysonic_syncfs::cache::offline::get_offline_cache_size,
            psysonic_syncfs::cache::local::download_track_local,
            psysonic_syncfs::cache::local::probe_library_track_local,
            psysonic_syncfs::cache::local::discover_library_tier_on_disk,
            psysonic_syncfs::cache::local::prune_orphan_library_tier_files,
            psysonic_syncfs::cache::local::prune_orphan_ephemeral_cache_files,
            psysonic_syncfs::cache::local::evict_ephemeral_cache_orphans_to_fit,
            psysonic_syncfs::cache::local::probe_media_files,
            psysonic_syncfs::cache::local::get_media_tier_size,
            psysonic_syncfs::cache::local::purge_media_tier,
            psysonic_syncfs::cache::local::delete_media_file,
            psysonic_syncfs::cache::local::prune_empty_media_tier_dirs,
            psysonic_syncfs::cache::local::promote_stream_cache_to_local,
            psysonic_syncfs::cache::local::migrate_legacy_offline_disk,
            psysonic_syncfs::cache::hot::download_track_hot_cache,
            psysonic_syncfs::cache::hot::promote_stream_cache_to_hot_cache,
            psysonic_syncfs::cache::hot::get_hot_cache_size,
            psysonic_syncfs::cache::hot::delete_hot_cache_track,
            psysonic_syncfs::cache::hot::purge_hot_cache,
            psysonic_syncfs::sync::device::sync_track_to_device,
            psysonic_syncfs::sync::batch::sync_batch_to_device,
            psysonic_syncfs::sync::batch::cancel_device_sync,
            psysonic_syncfs::sync::device::compute_sync_paths,
            psysonic_syncfs::sync::batch::list_device_dir_files,
            psysonic_syncfs::sync::batch::delete_device_file,
            psysonic_syncfs::sync::batch::delete_device_files,
            psysonic_syncfs::sync::device::get_removable_drives,
            psysonic_syncfs::sync::device::write_device_manifest,
            psysonic_syncfs::sync::device::read_device_manifest,
            psysonic_syncfs::sync::device::write_playlist_m3u8,
            psysonic_syncfs::sync::device::rename_device_files,
            toggle_tray_icon,
            set_tray_tooltip,
            set_tray_menu_labels,
            check_dir_accessible,
            psysonic_syncfs::cache::downloads::download_zip,
            psysonic_syncfs::cache::downloads::check_arch_linux,
            psysonic_syncfs::cache::downloads::download_update,
            psysonic_syncfs::cache::downloads::open_folder,
            psysonic_syncfs::cache::downloads::get_embedded_lyrics,
            psysonic_syncfs::cache::downloads::fetch_netease_lyrics,
            psysonic_integration::bandsintown::fetch_bandsintown_events,
            #[cfg(target_os = "windows")]
            taskbar_win::update_taskbar_icon,
        ])
        .run(tauri::generate_context!())
        .expect("error while running Psysonic");
}
