use crate::services::{
    doll_editor::open_doll_editor_window,
    scene::{get_scene_interactive, set_pet_menu_state, set_scene_interactive},
};
use commands::app::{quit_app, restart_app, retry_connection};
use commands::app_state::{
    get_active_doll_sprite_base64, get_app_data, get_app_state, get_neko_positions,
    get_friend_active_doll_sprites_base64, get_modules, refresh_app_data,
    set_scene_setup_nekos_opacity, set_scene_setup_nekos_position, set_scene_setup_nekos_scale,
};
use commands::auth::{logout_and_restart, start_discord_auth, start_google_auth};
use commands::config::{get_client_config, open_client_config, save_client_config};
use commands::dolls::{
    create_doll, delete_doll, get_doll, get_dolls, remove_active_doll, set_active_doll, update_doll,
};
use commands::friends::{
    accept_friend_request, deny_friend_request, list_friends, received_friend_requests,
    search_users, send_friend_request, sent_friend_requests, unfriend,
};
use commands::interaction::send_interaction_cmd;
use commands::petpet::encode_pet_doll_gif_base64;
use commands::sprite::recolor_gif_base64;
use specta_typescript::Typescript;
use tauri::async_runtime;
use tauri_specta::{collect_commands, collect_events, Builder as SpectaBuilder, ErrorHandlingMode};

use crate::services::app_events::{
    ActiveDollSpriteChanged, AppDataRefreshed, AppStateChanged, AuthFlowUpdated, CreateDoll,
    EditDoll, FriendActiveDollChanged, FriendActiveDollSpritesUpdated, FriendDisconnected,
    FriendRequestAccepted, FriendRequestDenied, FriendRequestReceived, FriendUserStatusChanged,
    InteractionDeliveryFailed, InteractionReceived, NekoPositionsUpdated,
    SceneInteractiveChanged, SetInteractionOverlay, Unfriended, UserStatusChanged,
};

static APP_HANDLE: std::sync::OnceLock<tauri::AppHandle<tauri::Wry>> = std::sync::OnceLock::new();

mod commands;
mod init;
mod models;
mod remotes;
mod services;
mod state;
mod system_tray;
mod utilities;

/// Tauri app handle
pub fn get_app_handle<'a>() -> &'a tauri::AppHandle<tauri::Wry> {
    APP_HANDLE
        .get()
        .expect("get_app_handle called but app is still not initialized")
}

fn register_app_events(event: tauri::RunEvent) {
    if let tauri::RunEvent::ExitRequested { api, code, .. } = event {
        if code.is_none() {
            api.prevent_exit();
        } else {
            println!("exit code: {:?}", code);
        }
    }
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    let specta_builder = SpectaBuilder::<tauri::Wry>::new()
        .error_handling(ErrorHandlingMode::Throw)
        .commands(collect_commands![
            get_app_data,
            get_app_state,
            get_neko_positions,
            get_active_doll_sprite_base64,
            get_friend_active_doll_sprites_base64,
            refresh_app_data,
            list_friends,
            search_users,
            send_friend_request,
            received_friend_requests,
            sent_friend_requests,
            accept_friend_request,
            deny_friend_request,
            unfriend,
            get_dolls,
            get_doll,
            create_doll,
            update_doll,
            delete_doll,
            set_active_doll,
            remove_active_doll,
            recolor_gif_base64,
            encode_pet_doll_gif_base64,
            quit_app,
            restart_app,
            retry_connection,
            get_client_config,
            save_client_config,
            open_client_config,
            open_doll_editor_window,
            get_scene_interactive,
            set_scene_interactive,
            set_pet_menu_state,
            start_google_auth,
            start_discord_auth,
            logout_and_restart,
            send_interaction_cmd,
            get_modules,
            set_scene_setup_nekos_position,
            set_scene_setup_nekos_opacity,
            set_scene_setup_nekos_scale
        ])
        .events(collect_events![
            SceneInteractiveChanged,
            AppDataRefreshed,
            AppStateChanged,
            NekoPositionsUpdated,
            ActiveDollSpriteChanged,
            SetInteractionOverlay,
            EditDoll,
            CreateDoll,
            UserStatusChanged,
            FriendDisconnected,
            FriendActiveDollChanged,
            FriendActiveDollSpritesUpdated,
            FriendUserStatusChanged,
            InteractionReceived,
            InteractionDeliveryFailed,
            FriendRequestReceived,
            FriendRequestAccepted,
            FriendRequestDenied,
            Unfriended,
            AuthFlowUpdated
        ]);

    #[cfg(debug_assertions)]
    specta_builder
        .export(Typescript::default(), "../src/lib/bindings.ts")
        .expect("Failed to export TypeScript bindings");

    tauri::Builder::default()
        .plugin(tauri_plugin_updater::Builder::new().build())
        .plugin(tauri_plugin_opener::init())
        .plugin(tauri_plugin_positioner::init())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_process::init())
        .invoke_handler(specta_builder.invoke_handler())
        .setup(move |app| {
            specta_builder.mount_events(app);
            APP_HANDLE
                .set(app.handle().to_owned())
                .expect("Failed to init app handle.");
            async_runtime::spawn(async move { init::launch_app().await });
            Ok(())
        })
        .build(tauri::generate_context!())
        .expect("error while running tauri application")
        .run(|_, event| register_app_events(event));
}
