//! Sync command workflows for the desktop application.
//!
//! This module owns user-facing sync actions: status display, remote-deck
//! selection, login/logout, token invalidation, upload, and download. It bridges
//! UI dialogs to sync runtime/workflow helpers while keeping sync protocol and
//! bundle mechanics outside `main.rs`.

use std::cell::RefCell;
use std::path::{Path, PathBuf};
use std::rc::Rc;

use cosimo::{HttpSyncClient, SqliteReviewStore, SyncDownloadKind};
use wxdragon::id::ID_OK;
use wxdragon::prelude::*;

use crate::app_config::{
    CONFIG_PATH, clear_sync_auth_session, forget_local_deck_sync_state_for_uuid, load_app_config,
    persist_sync_auth_session,
};
use crate::app_state::AppState;
use crate::deck_data::default_deck_path;
use crate::dialog_support::{
    show_error_dialog_with_parent, show_info_dialog_with_parent, show_yes_no_dialog_with_parent,
};
use crate::file_ops::{default_backup_dir, launch_default_browser_for_url, portable_path};
use crate::localization::Localizer;
use crate::review_dialogs::{
    SyncRemoteDecksDialogResult, copy_text_to_clipboard, show_card_details_dialog,
    show_sync_remote_decks_list_dialog, show_sync_status_dialog,
};
use crate::review_ui::ReviewUi;
use crate::runtime::current_time_and_seed;
use crate::sync_identity::display_owner_identity;
use crate::sync_menu::{
    SyncUploadConflictChoice, show_sync_upload_conflict_dialog, sync_auth_menu_state,
};
use crate::sync_remote_decks_model::{RemoteDeckListEntry, sync_deck_identifier_from_text};
use crate::sync_runtime::{
    HttpSyncRemoteDeckSettings, SyncUploadPreflight, current_deck_sync_download_plan,
    display_optional_owner_identity, http_quota_status_for_config, http_remote_status_for_metadata,
    http_sync_remote_deck_settings, http_sync_runtime_settings, local_sync_revision_for_upload,
    remote_decks_for_runtime, sync_owner_and_token_for_config,
    sync_upload_preflight_for_remote_owner,
};
use crate::sync_status_model::{
    RemoteSyncQuotaOverride, RemoteSyncStatusOverride, sync_status_text,
    sync_status_text_with_remote, sync_status_text_with_remote_and_quota,
};
use crate::sync_workflow::{
    HttpSyncDownloadLaunch, HttpSyncUploadLaunch, SyncDownloadLocalTargetStatus,
    SyncUploadBundleKind, SyncUploadExpectedRevision, ensure_state_deck_uuid,
    show_sync_progress_dialog, spawn_sync_download, spawn_sync_upload,
    sync_download_local_target_status,
};

type LoadSelectedDeckCallback<'a> = dyn FnMut(Localizer, Result<AppState, String>) + 'a;

#[derive(Clone, Copy)]
enum SyncBrowserAuthFlow {
    DeviceAuthorization,
    AccountRegistration,
}

pub(crate) fn open_sync_status_from_ui(ui: &ReviewUi, state: &Rc<RefCell<AppState>>) {
    let (localizer, metadata) = {
        let state_ref = state.borrow();
        (state_ref.localizer, state_ref.deck_metadata.clone())
    };
    let config = load_app_config(portable_path(CONFIG_PATH)).unwrap_or_default();
    let config_path = portable_path(CONFIG_PATH);
    let mut display_config = config.clone();
    let mut remote_status = http_remote_status_for_metadata(&metadata, &config);
    let mut quota_status = http_quota_status_for_config(&config);
    if config.sync_bearer_token.is_some()
        && (remote_status_authentication_failed(remote_status.as_ref())
            || quota_status_authentication_failed(quota_status.as_ref()))
    {
        if let Err(error) = clear_sync_auth_session(&config_path, localizer) {
            show_error_dialog(
                ui,
                localizer.could_not_log_out_of_sync_server_caption(),
                &error,
            );
            return;
        }
        display_config.sync_owner_identity = None;
        display_config.sync_bearer_token = None;
        remote_status = remote_status.map(remote_status_after_clearing_authentication_failure);
        quota_status = quota_status.and_then(quota_status_after_clearing_authentication_failure);
        sync_auth_menu_state(ui, localizer);
    }
    let details = match (remote_status, quota_status) {
        (Some(remote_status), Some(quota_status)) => sync_status_text_with_remote_and_quota(
            localizer,
            &metadata,
            &display_config,
            Some(remote_status),
            Some(quota_status),
        ),
        (Some(remote_status), None) => {
            sync_status_text_with_remote(localizer, &metadata, &display_config, Some(remote_status))
        }
        (None, Some(quota_status)) => sync_status_text_with_remote_and_quota(
            localizer,
            &metadata,
            &display_config,
            None,
            Some(quota_status),
        ),
        (None, None) => sync_status_text(localizer, &metadata, &display_config),
    };

    show_sync_status_dialog(ui.frame, localizer, &details);
}

fn remote_status_authentication_failed(status: Option<&RemoteSyncStatusOverride>) -> bool {
    matches!(
        status,
        Some(
            RemoteSyncStatusOverride::AuthenticationFailed
                | RemoteSyncStatusOverride::FoundWithOwnerAuthenticationFailed { .. }
        )
    )
}

fn quota_status_authentication_failed(status: Option<&RemoteSyncQuotaOverride>) -> bool {
    matches!(status, Some(RemoteSyncQuotaOverride::AuthenticationFailed))
}

fn remote_status_after_clearing_authentication_failure(
    status: RemoteSyncStatusOverride,
) -> RemoteSyncStatusOverride {
    match status {
        RemoteSyncStatusOverride::AuthenticationFailed => RemoteSyncStatusOverride::NotFound,
        RemoteSyncStatusOverride::FoundWithOwnerAuthenticationFailed { status } => {
            RemoteSyncStatusOverride::Found(status)
        }
        other => other,
    }
}

fn quota_status_after_clearing_authentication_failure(
    status: RemoteSyncQuotaOverride,
) -> Option<RemoteSyncQuotaOverride> {
    match status {
        RemoteSyncQuotaOverride::AuthenticationFailed => None,
        other => Some(other),
    }
}

pub(crate) fn open_sync_remote_decks_from_ui(
    ui: &ReviewUi,
    state: &Rc<RefCell<AppState>>,
    on_load_selected_deck: &mut LoadSelectedDeckCallback<'_>,
) {
    let localizer = state.borrow().localizer;
    let sync_settings = match http_sync_remote_deck_settings(localizer) {
        Ok(settings) => settings,
        Err(error) => {
            show_error_dialog(ui, localizer.sync_settings_missing_caption(), &error);
            return;
        }
    };
    let remote_decks = match remote_decks_for_runtime(&sync_settings) {
        Ok(remote_decks) => remote_decks,
        Err(error) => {
            show_error_dialog(ui, localizer.could_not_sync_deck_caption(), &error);
            return;
        }
    };
    if remote_decks.authentication_failed {
        let config_path = portable_path(CONFIG_PATH);
        if let Err(error) = clear_sync_auth_session(&config_path, localizer) {
            show_error_dialog(
                ui,
                localizer.could_not_log_out_of_sync_server_caption(),
                &error,
            );
            return;
        }
        sync_auth_menu_state(ui, localizer);
    }
    let mut entries = remote_decks.entries;

    let mut selected_index = None;
    loop {
        match show_sync_remote_decks_list_dialog(ui.frame, localizer, &entries, selected_index) {
            SyncRemoteDecksDialogResult::Close => break,
            SyncRemoteDecksDialogResult::Details(index) => {
                selected_index = Some(index);
                if let Some(entry) = entries.get(index) {
                    show_card_details_dialog(
                        ui.frame,
                        localizer,
                        &entry.details_text(localizer, &sync_settings.server_url),
                    );
                }
            }
            SyncRemoteDecksDialogResult::CopyId(index) => {
                selected_index = Some(index);
                if let Some(entry) = entries.get(index) {
                    if copy_text_to_clipboard(entry.deck_uuid().as_str()) {
                        show_info_dialog(
                            ui,
                            localizer.sync_remote_deck_id_copied_caption(),
                            localizer.sync_remote_deck_id_copied_message(),
                        );
                    } else {
                        show_error_dialog(
                            ui,
                            localizer.sync_remote_deck_id_copy_failed_caption(),
                            localizer.sync_remote_deck_id_copy_failed_message(),
                        );
                    }
                }
            }
            SyncRemoteDecksDialogResult::FindById => {
                selected_index = find_remote_sync_deck_by_id_from_ui(
                    ui,
                    localizer,
                    &sync_settings,
                    &mut entries,
                );
            }
            SyncRemoteDecksDialogResult::Download(index) => {
                if let Some(entry) = entries.get(index) {
                    download_remote_deck_sync_from_ui(
                        ui,
                        state,
                        localizer,
                        &sync_settings,
                        entry,
                        on_load_selected_deck,
                    );
                }
                break;
            }
            SyncRemoteDecksDialogResult::Delete(index) => {
                selected_index = Some(index);
                let Some(entry) = entries.get(index) else {
                    continue;
                };
                if delete_remote_deck_sync_from_ui(ui, localizer, &sync_settings, entry) {
                    entries.remove(index);
                    selected_index = if entries.is_empty() {
                        None
                    } else {
                        Some(index.min(entries.len() - 1))
                    };
                }
            }
        }
    }
}

pub(crate) fn log_in_to_sync_server_from_ui(ui: &ReviewUi, state: &Rc<RefCell<AppState>>) {
    complete_sync_browser_auth_from_ui(ui, state, SyncBrowserAuthFlow::DeviceAuthorization);
}

pub(crate) fn register_sync_account_from_ui(ui: &ReviewUi, state: &Rc<RefCell<AppState>>) {
    complete_sync_browser_auth_from_ui(ui, state, SyncBrowserAuthFlow::AccountRegistration);
}

fn complete_sync_browser_auth_from_ui(
    ui: &ReviewUi,
    state: &Rc<RefCell<AppState>>,
    flow: SyncBrowserAuthFlow,
) {
    let localizer = state.borrow().localizer;
    let config_path = portable_path(CONFIG_PATH);
    let config = match load_app_config(&config_path) {
        Ok(config) => config,
        Err(error) => {
            show_error_dialog(ui, sync_browser_auth_error_caption(localizer, flow), &error);
            return;
        }
    };
    let Some(server_url) = config.sync_server_url.clone() else {
        show_error_dialog(
            ui,
            localizer.sync_settings_missing_caption(),
            localizer.sync_settings_missing_message(),
        );
        return;
    };
    let client = match HttpSyncClient::new(&server_url) {
        Ok(client) => client,
        Err(error) => {
            show_error_dialog(
                ui,
                sync_browser_auth_error_caption(localizer, flow),
                &error.to_string(),
            );
            return;
        }
    };
    let oauth_start_result = match flow {
        SyncBrowserAuthFlow::DeviceAuthorization => client.start_fedi_browser_device_login(),
        SyncBrowserAuthFlow::AccountRegistration => client.start_fedi_browser_login(),
    };
    let oauth_start = match oauth_start_result {
        Ok(start) => start,
        Err(error) => {
            show_error_dialog(
                ui,
                sync_browser_auth_error_caption(localizer, flow),
                &error.to_string(),
            );
            return;
        }
    };
    let browser_url = oauth_start
        .registration_url()
        .unwrap_or(oauth_start.authorization_url());
    if launch_default_browser_for_url(browser_url).is_err() {
        show_info_dialog(
            ui,
            sync_browser_auth_caption(localizer, flow),
            &sync_browser_auth_browser_open_failed_message(localizer, flow, browser_url),
        );
    }
    show_info_dialog(
        ui,
        sync_browser_auth_caption(localizer, flow),
        sync_browser_auth_browser_completion_message(localizer, flow),
    );

    let session = match client.poll_fedi_browser_login(oauth_start.login_token()) {
        Ok(Some(session)) => session,
        Ok(None) => {
            show_error_dialog(
                ui,
                sync_browser_auth_error_caption(localizer, flow),
                sync_browser_auth_browser_not_completed_message(localizer, flow),
            );
            return;
        }
        Err(error) => {
            show_error_dialog(
                ui,
                sync_browser_auth_error_caption(localizer, flow),
                &error.to_string(),
            );
            return;
        }
    };
    if let Err(error) = persist_sync_auth_session(&config_path, &server_url, &session, localizer) {
        show_error_dialog(ui, sync_browser_auth_error_caption(localizer, flow), &error);
        return;
    }

    show_info_dialog(
        ui,
        sync_browser_auth_finished_caption(localizer, flow),
        &sync_browser_auth_finished_message(
            localizer,
            flow,
            &display_owner_identity(session.owner_identity()),
        ),
    );
}

fn sync_browser_auth_caption(localizer: Localizer, flow: SyncBrowserAuthFlow) -> &'static str {
    match flow {
        SyncBrowserAuthFlow::DeviceAuthorization => {
            localizer.sync_login_authorization_code_caption()
        }
        SyncBrowserAuthFlow::AccountRegistration => localizer.sync_register_account_caption(),
    }
}

fn sync_browser_auth_error_caption(
    localizer: Localizer,
    flow: SyncBrowserAuthFlow,
) -> &'static str {
    match flow {
        SyncBrowserAuthFlow::DeviceAuthorization => {
            localizer.could_not_log_in_to_sync_server_caption()
        }
        SyncBrowserAuthFlow::AccountRegistration => {
            localizer.could_not_register_sync_account_caption()
        }
    }
}

fn sync_browser_auth_browser_open_failed_message(
    localizer: Localizer,
    flow: SyncBrowserAuthFlow,
    browser_url: &str,
) -> String {
    match flow {
        SyncBrowserAuthFlow::DeviceAuthorization => {
            localizer.sync_login_browser_open_failed_message(browser_url)
        }
        SyncBrowserAuthFlow::AccountRegistration => {
            localizer.sync_register_account_browser_open_failed_message(browser_url)
        }
    }
}

fn sync_browser_auth_browser_completion_message(
    localizer: Localizer,
    flow: SyncBrowserAuthFlow,
) -> &'static str {
    match flow {
        SyncBrowserAuthFlow::DeviceAuthorization => {
            localizer.sync_login_browser_completion_message()
        }
        SyncBrowserAuthFlow::AccountRegistration => {
            localizer.sync_register_account_browser_completion_message()
        }
    }
}

fn sync_browser_auth_browser_not_completed_message(
    localizer: Localizer,
    flow: SyncBrowserAuthFlow,
) -> &'static str {
    match flow {
        SyncBrowserAuthFlow::DeviceAuthorization => {
            localizer.sync_login_browser_not_completed_message()
        }
        SyncBrowserAuthFlow::AccountRegistration => {
            localizer.sync_register_account_browser_not_completed_message()
        }
    }
}

fn sync_browser_auth_finished_caption(
    localizer: Localizer,
    flow: SyncBrowserAuthFlow,
) -> &'static str {
    match flow {
        SyncBrowserAuthFlow::DeviceAuthorization => localizer.sync_login_finished_caption(),
        SyncBrowserAuthFlow::AccountRegistration => {
            localizer.sync_register_account_finished_caption()
        }
    }
}

fn sync_browser_auth_finished_message(
    localizer: Localizer,
    flow: SyncBrowserAuthFlow,
    owner_identity: &str,
) -> String {
    match flow {
        SyncBrowserAuthFlow::DeviceAuthorization => {
            localizer.sync_login_finished_message(owner_identity)
        }
        SyncBrowserAuthFlow::AccountRegistration => {
            localizer.sync_register_account_finished_message(owner_identity)
        }
    }
}

pub(crate) fn open_sync_account_deletion_from_ui(ui: &ReviewUi, state: &Rc<RefCell<AppState>>) {
    let localizer = state.borrow().localizer;
    let config = match load_app_config(portable_path(CONFIG_PATH)) {
        Ok(config) => config,
        Err(error) => {
            show_error_dialog(
                ui,
                localizer.could_not_open_sync_account_deletion_caption(),
                &error,
            );
            return;
        }
    };
    let Some(server_url) = config.sync_server_url.as_deref() else {
        show_error_dialog(
            ui,
            localizer.sync_settings_missing_caption(),
            localizer.sync_settings_missing_message(),
        );
        return;
    };
    let client = match HttpSyncClient::new(server_url) {
        Ok(client) => client,
        Err(error) => {
            show_error_dialog(
                ui,
                localizer.could_not_open_sync_account_deletion_caption(),
                &error.to_string(),
            );
            return;
        }
    };
    let deletion_url = client.account_deletion_url();
    if launch_default_browser_for_url(&deletion_url).is_err() {
        show_info_dialog(
            ui,
            localizer.could_not_open_sync_account_deletion_caption(),
            &localizer.sync_account_deletion_browser_open_failed_message(&deletion_url),
        );
    }
}

pub(crate) fn log_out_of_sync_server_from_ui(ui: &ReviewUi, state: &Rc<RefCell<AppState>>) {
    let localizer = state.borrow().localizer;
    let config_path = portable_path(CONFIG_PATH);
    let config = match load_app_config(&config_path) {
        Ok(config) => config,
        Err(error) => {
            show_error_dialog(
                ui,
                localizer.could_not_invalidate_sync_token_caption(),
                &error,
            );
            return;
        }
    };
    let previous_owner = config.sync_owner_identity.clone();

    if let Err(error) = clear_sync_auth_session(&config_path, localizer) {
        show_error_dialog(
            ui,
            localizer.could_not_invalidate_sync_token_caption(),
            &error,
        );
        return;
    }

    sync_auth_menu_state(ui, localizer);
    let previous_owner_display = display_optional_owner_identity(previous_owner.as_deref());
    let message = localizer.sync_logout_finished_message(previous_owner_display.as_deref());
    show_info_dialog(ui, localizer.sync_logout_finished_caption(), &message);
}

pub(crate) fn invalidate_sync_token_from_ui(ui: &ReviewUi, state: &Rc<RefCell<AppState>>) {
    let localizer = state.borrow().localizer;
    let config_path = portable_path(CONFIG_PATH);
    let config = match load_app_config(&config_path) {
        Ok(config) => config,
        Err(error) => {
            show_error_dialog(
                ui,
                localizer.could_not_invalidate_sync_tokens_caption(),
                &error,
            );
            return;
        }
    };
    let previous_owner = config.sync_owner_identity.clone();
    let server_revocation_error = match (
        config.sync_server_url.as_deref(),
        config.sync_bearer_token.as_deref(),
    ) {
        (Some(server_url), Some(bearer_token)) => HttpSyncClient::new(server_url)
            .map(|client| client.with_bearer_token(bearer_token))
            .and_then(|client| client.revoke_auth_session())
            .err()
            .map(|error| error.to_string()),
        (None, _) => Some(localizer.sync_settings_missing_message().to_string()),
        (_, None) => Some(localizer.sync_login_missing_message().to_string()),
    };

    if let Err(error) = clear_sync_auth_session(&config_path, localizer) {
        show_error_dialog(
            ui,
            localizer.could_not_log_out_of_sync_server_caption(),
            &error,
        );
        return;
    }

    sync_auth_menu_state(ui, localizer);
    let previous_owner_display = display_optional_owner_identity(previous_owner.as_deref());
    let mut message =
        localizer.sync_token_invalidated_finished_message(previous_owner_display.as_deref());
    if let Some(error) = server_revocation_error {
        message.push_str("\n\n");
        message.push_str(&localizer.sync_logout_revocation_failed_message(&error));
    }
    show_info_dialog(ui, localizer.sync_logout_finished_caption(), &message);
}

pub(crate) fn invalidate_all_sync_tokens_from_ui(ui: &ReviewUi, state: &Rc<RefCell<AppState>>) {
    let localizer = state.borrow().localizer;
    let config_path = portable_path(CONFIG_PATH);
    let config = match load_app_config(&config_path) {
        Ok(config) => config,
        Err(error) => {
            show_error_dialog(
                ui,
                localizer.could_not_log_out_of_sync_server_caption(),
                &error,
            );
            return;
        }
    };
    let Some(owner_identity) = config.sync_owner_identity.as_deref() else {
        show_error_dialog(
            ui,
            localizer.could_not_invalidate_sync_tokens_caption(),
            localizer.sync_login_missing_message(),
        );
        return;
    };
    if !show_yes_no_dialog_with_parent(
        &ui.frame,
        localizer.sync_invalidate_all_tokens_confirmation_caption(),
        &localizer.sync_invalidate_all_tokens_confirmation_message(&display_owner_identity(
            owner_identity,
        )),
    ) {
        return;
    }
    let result: Result<(), String> = match (
        config.sync_server_url.as_deref(),
        config.sync_bearer_token.as_deref(),
    ) {
        (Some(server_url), Some(bearer_token)) => HttpSyncClient::new(server_url)
            .map(|client| client.with_bearer_token(bearer_token))
            .and_then(|client| client.revoke_all_auth_sessions())
            .map_err(|error| error.to_string()),
        (None, _) => Err(localizer.sync_settings_missing_message().to_string()),
        (_, None) => Err(localizer.sync_login_missing_message().to_string()),
    };
    if let Err(error) = result {
        show_error_dialog(
            ui,
            localizer.could_not_invalidate_sync_tokens_caption(),
            &error,
        );
        return;
    }

    let previous_owner = config.sync_owner_identity.clone();
    if let Err(error) = clear_sync_auth_session(&config_path, localizer) {
        show_error_dialog(
            ui,
            localizer.could_not_invalidate_sync_tokens_caption(),
            &error,
        );
        return;
    }

    sync_auth_menu_state(ui, localizer);
    let previous_owner_display = display_optional_owner_identity(previous_owner.as_deref());
    show_info_dialog(
        ui,
        localizer.sync_tokens_invalidated_finished_caption(),
        &localizer.sync_all_tokens_invalidated_finished_message(previous_owner_display.as_deref()),
    );
}

pub(crate) fn upload_deck_sync_from_ui(
    ui: &ReviewUi,
    state: &Rc<RefCell<AppState>>,
    on_load_selected_deck: &mut LoadSelectedDeckCallback<'_>,
) {
    let localizer = state.borrow().localizer;
    let sync_settings = match http_sync_runtime_settings(localizer) {
        Ok(settings) => settings,
        Err(error) => {
            show_error_dialog(ui, localizer.sync_settings_missing_caption(), &error);
            return;
        }
    };
    let config_path = portable_path(CONFIG_PATH);
    let (mut launch, metadata) = {
        let mut state_ref = state.borrow_mut();
        let deck_uuid = match ensure_state_deck_uuid(&mut state_ref) {
            Ok(deck_uuid) => deck_uuid,
            Err(error) => {
                show_error_dialog(ui, localizer.could_not_sync_deck_caption(), &error);
                return;
            }
        };
        (
            HttpSyncUploadLaunch {
                config_path: config_path.clone(),
                server_url: sync_settings.server_url.clone(),
                bearer_token: sync_settings.bearer_token.clone(),
                temp_dir: portable_path("sync-temp"),
                deck_path: state_ref.deck_path.clone(),
                review_db_path: state_ref.review_db_path.clone(),
                deck_uuid,
                owner_identity: sync_settings.owner_identity.clone(),
                device_identity: sync_settings.device_identity.clone(),
                visibility: state_ref.deck_metadata.deck_visibility(),
                bundle_kind: SyncUploadBundleKind::OwnerStudyHistory,
                expected_revision: SyncUploadExpectedRevision::FromLocalState,
                localizer,
            },
            state_ref.deck_metadata.clone(),
        )
    };
    let config = match load_app_config(&config_path) {
        Ok(config) => config,
        Err(error) => {
            show_error_dialog(ui, localizer.could_not_sync_deck_caption(), &error);
            return;
        }
    };
    let local_expected_revision = local_sync_revision_for_upload(
        &config,
        &launch.deck_uuid,
        &sync_settings.server_url,
        &sync_settings.owner_identity,
    );
    let remote_status = http_remote_status_for_metadata(&metadata, &config);
    let remote_deck_not_found = matches!(remote_status, Some(RemoteSyncStatusOverride::NotFound));
    match sync_upload_preflight_for_remote_owner(
        localizer,
        remote_status,
        local_expected_revision.as_deref(),
        &sync_settings.owner_identity,
    ) {
        Ok(SyncUploadPreflight::Ready) => {
            if remote_deck_not_found {
                launch.expected_revision = SyncUploadExpectedRevision::UseRemoteRevision(None);
            }
        }
        Ok(SyncUploadPreflight::Conflict(conflict)) => {
            match show_sync_upload_conflict_dialog(&ui.frame, localizer, &conflict) {
                SyncUploadConflictChoice::ForceUpload => {
                    launch.expected_revision =
                        SyncUploadExpectedRevision::UseRemoteRevision(conflict.remote_revision);
                }
                SyncUploadConflictChoice::DownloadInstead => {
                    download_sync_deck_from_ui(
                        ui,
                        state,
                        localizer,
                        SyncDeckDownloadRequest {
                            server_url: &sync_settings.server_url,
                            bearer_token: sync_settings.bearer_token.as_deref(),
                            sync_target: &sync_settings.target_for_message(),
                            deck_uuid: launch.deck_uuid,
                            download_kind: SyncDownloadKind::OwnerStudyHistory,
                            requester_owner_identity: Some(sync_settings.owner_identity.clone()),
                        },
                        on_load_selected_deck,
                    );
                    return;
                }
                SyncUploadConflictChoice::Cancel => return,
            }
        }
        Err(error) => {
            show_error_dialog(ui, localizer.could_not_sync_deck_caption(), &error);
            return;
        }
    }
    let result = show_sync_progress_dialog(&ui.frame, localizer, spawn_sync_upload(launch));
    match result {
        Ok(summary) => show_info_dialog(
            ui,
            localizer.sync_upload_finished_caption(),
            &localizer.sync_upload_finished_message(
                summary.deck_uuid.as_str(),
                &summary.revision,
                &sync_settings.target_for_message(),
            ),
        ),
        Err(error) => show_error_dialog(ui, localizer.could_not_sync_deck_caption(), &error),
    }
}

pub(crate) fn download_deck_sync_from_ui(
    ui: &ReviewUi,
    state: &Rc<RefCell<AppState>>,
    on_load_selected_deck: &mut LoadSelectedDeckCallback<'_>,
) {
    let (localizer, metadata) = {
        let state_ref = state.borrow();
        (state_ref.localizer, state_ref.deck_metadata.clone())
    };
    let Some(deck_uuid) = metadata.deck_uuid() else {
        show_error_dialog(
            ui,
            localizer.could_not_sync_deck_caption(),
            localizer.sync_deck_uuid_missing_message(),
        );
        return;
    };
    let config = match load_app_config(portable_path(CONFIG_PATH)) {
        Ok(config) => config,
        Err(error) => {
            show_error_dialog(ui, localizer.sync_settings_missing_caption(), &error);
            return;
        }
    };
    let Some(server_url) = config.sync_server_url.as_deref() else {
        show_error_dialog(
            ui,
            localizer.sync_settings_missing_caption(),
            localizer.sync_settings_missing_message(),
        );
        return;
    };
    let sync_auth = sync_owner_and_token_for_config(&config);
    let (owner_identity, bearer_token) = match sync_auth {
        Some((owner_identity, bearer_token)) => (Some(owner_identity), bearer_token),
        None => (None, None),
    };
    let remote_status = http_remote_status_for_metadata(&metadata, &config);
    let download_plan = match current_deck_sync_download_plan(
        localizer,
        remote_status,
        owner_identity.as_deref(),
    ) {
        Ok(plan) => plan,
        Err(error) => {
            show_error_dialog(ui, localizer.could_not_sync_deck_caption(), &error);
            return;
        }
    };
    download_sync_deck_from_ui(
        ui,
        state,
        localizer,
        SyncDeckDownloadRequest {
            server_url,
            bearer_token: bearer_token.as_deref(),
            sync_target: server_url,
            deck_uuid,
            download_kind: download_plan.download_kind,
            requester_owner_identity: download_plan.requester_owner_identity,
        },
        on_load_selected_deck,
    );
}

fn find_remote_sync_deck_by_id_from_ui(
    ui: &ReviewUi,
    localizer: Localizer,
    settings: &HttpSyncRemoteDeckSettings,
    entries: &mut Vec<RemoteDeckListEntry>,
) -> Option<usize> {
    let entered = prompt_text_entry(
        &ui.frame,
        localizer.sync_remote_deck_find_by_id_caption(),
        localizer.sync_remote_deck_find_by_id_message(),
        "",
    )?;
    let Some(deck_uuid) = sync_deck_identifier_from_text(&entered) else {
        show_error_dialog(
            ui,
            localizer.could_not_sync_deck_caption(),
            localizer.sync_remote_deck_invalid_id_message(),
        );
        return None;
    };
    if let Some(index) = entries
        .iter()
        .position(|entry| entry.deck_uuid() == &deck_uuid)
    {
        return Some(index);
    }

    let client = match HttpSyncClient::new(&settings.server_url) {
        Ok(client) => client,
        Err(error) => {
            show_error_dialog(
                ui,
                localizer.could_not_sync_deck_caption(),
                &error.to_string(),
            );
            return None;
        }
    };
    match client.inspect_shared_deck(&deck_uuid) {
        Ok(status) => {
            entries.push(RemoteDeckListEntry::Shared(status));
            Some(entries.len() - 1)
        }
        Err(error) => {
            show_error_dialog(
                ui,
                localizer.could_not_sync_deck_caption(),
                &error.to_string(),
            );
            None
        }
    }
}

fn download_remote_deck_sync_from_ui(
    ui: &ReviewUi,
    state: &Rc<RefCell<AppState>>,
    localizer: Localizer,
    settings: &HttpSyncRemoteDeckSettings,
    entry: &RemoteDeckListEntry,
    on_load_selected_deck: &mut LoadSelectedDeckCallback<'_>,
) {
    let requester_owner_identity = match entry.download_kind() {
        SyncDownloadKind::OwnerStudyHistory => settings.owner_identity.clone(),
        SyncDownloadKind::SharedContent => None,
    };
    download_sync_deck_from_ui(
        ui,
        state,
        localizer,
        SyncDeckDownloadRequest {
            server_url: &settings.server_url,
            bearer_token: settings.bearer_token.as_deref(),
            sync_target: &settings.target_for_message(),
            deck_uuid: entry.deck_uuid().clone(),
            download_kind: entry.download_kind(),
            requester_owner_identity,
        },
        on_load_selected_deck,
    );
}

fn delete_remote_deck_sync_from_ui(
    ui: &ReviewUi,
    localizer: Localizer,
    settings: &HttpSyncRemoteDeckSettings,
    entry: &RemoteDeckListEntry,
) -> bool {
    if !entry.is_owned() {
        show_error_dialog(
            ui,
            localizer.could_not_sync_deck_caption(),
            localizer.sync_delete_owner_required_message(),
        );
        return false;
    }
    let Some(bearer_token) = settings.bearer_token.as_deref() else {
        show_error_dialog(
            ui,
            localizer.sync_settings_missing_caption(),
            localizer.sync_login_missing_message(),
        );
        return false;
    };
    let deck_label = entry.label(localizer);
    if !show_yes_no_dialog_with_parent(
        &ui.frame,
        localizer.sync_delete_confirm_caption(),
        &localizer.sync_delete_confirm_message(&deck_label),
    ) {
        return false;
    }

    let result = HttpSyncClient::new(&settings.server_url)
        .map(|client| client.with_bearer_token(bearer_token))
        .and_then(|client| client.delete_deck(entry.deck_uuid()));
    match result {
        Ok(()) => {
            if let Err(error) = forget_local_deck_sync_state_for_uuid(
                portable_path(CONFIG_PATH),
                entry.deck_uuid(),
                localizer,
            ) {
                show_error_dialog(
                    ui,
                    localizer.could_not_clear_local_sync_state_caption(),
                    &error,
                );
            }
            show_info_dialog(
                ui,
                localizer.sync_delete_finished_caption(),
                &localizer.sync_delete_finished_message(&deck_label),
            );
            true
        }
        Err(error) => {
            show_error_dialog(
                ui,
                localizer.could_not_sync_deck_caption(),
                &error.to_string(),
            );
            false
        }
    }
}

struct SyncDeckDownloadRequest<'a> {
    server_url: &'a str,
    bearer_token: Option<&'a str>,
    sync_target: &'a str,
    deck_uuid: cosimo::SyncUuid,
    download_kind: SyncDownloadKind,
    requester_owner_identity: Option<String>,
}

fn download_sync_deck_from_ui(
    ui: &ReviewUi,
    state: &Rc<RefCell<AppState>>,
    localizer: Localizer,
    request: SyncDeckDownloadRequest<'_>,
    on_load_selected_deck: &mut LoadSelectedDeckCallback<'_>,
) {
    let (settings, current_deck_path, current_deck_uuid) = {
        let state_ref = state.borrow();
        (
            state_ref.settings,
            state_ref.deck_path.clone(),
            state_ref.deck_metadata.deck_uuid(),
        )
    };
    let decks_dir = default_decks_dir();
    let backup_dir = default_backup_dir();
    let recent_deck_paths = load_app_config(portable_path(CONFIG_PATH))
        .map(|config| config.recent_deck_paths)
        .unwrap_or_default();
    let local_target_status = match sync_download_local_target_status(
        &request.deck_uuid,
        &current_deck_path,
        current_deck_uuid.as_ref(),
        &recent_deck_paths,
        &decks_dir,
    ) {
        Ok(status) => status,
        Err(error) => {
            show_error_dialog(
                ui,
                localizer.could_not_sync_deck_caption(),
                &error.to_string(),
            );
            return;
        }
    };
    let confirm_message = match local_target_status {
        SyncDownloadLocalTargetStatus::NewDeck => {
            localizer.sync_download_confirm_new_deck_message(request.sync_target)
        }
        SyncDownloadLocalTargetStatus::ReplacesExistingDeck(path) => localizer
            .sync_download_confirm_replace_deck_message(
                request.sync_target,
                &path.display().to_string(),
                matches!(request.download_kind, SyncDownloadKind::OwnerStudyHistory),
            ),
    };
    if !show_yes_no_dialog_with_parent(
        &ui.frame,
        localizer.sync_download_confirm_caption(),
        &confirm_message,
    ) {
        return;
    }

    match SqliteReviewStore::open_in_memory() {
        Ok(memory_store) => {
            state.borrow_mut().store = memory_store;
        }
        Err(error) => {
            show_error_dialog(
                ui,
                localizer.could_not_sync_deck_caption(),
                &error.to_string(),
            );
            return;
        }
    }

    let now = current_time_and_seed()
        .map(|(time, _)| time)
        .unwrap_or_default();
    let launch = HttpSyncDownloadLaunch {
        config_path: portable_path(CONFIG_PATH),
        server_url: request.server_url.to_string(),
        bearer_token: request.bearer_token.map(ToString::to_string),
        temp_dir: portable_path("sync-temp"),
        deck_uuid: request.deck_uuid,
        download_kind: request.download_kind,
        requester_owner_identity: request.requester_owner_identity,
        current_deck_path: current_deck_path.clone(),
        current_deck_uuid,
        recent_deck_paths,
        decks_dir,
        backup_dir,
        timestamp_unix_seconds: now,
        localizer,
    };
    state.borrow_mut().suspend_ready_due_notifications();
    let result = show_sync_progress_dialog(&ui.frame, localizer, spawn_sync_download(launch));
    state.borrow_mut().resume_ready_due_notifications();

    match result {
        Ok(summary) => match AppState::load_deck_with_localizer(
            &summary.restored_deck_path,
            localizer,
            settings,
        ) {
            Ok(new_state) => {
                let backup_path = summary
                    .backup_path
                    .as_ref()
                    .map(|path| path.display().to_string());
                on_load_selected_deck(localizer, Ok(new_state));
                show_info_dialog(
                    ui,
                    localizer.sync_download_finished_caption(),
                    &localizer.sync_download_finished_message(
                        &summary.restored_deck_path.display().to_string(),
                        &summary.revision,
                        summary.has_review_database,
                        backup_path.as_deref(),
                    ),
                );
            }
            Err(error) => {
                show_error_dialog(ui, localizer.could_not_open_deck_caption(), &error);
            }
        },
        Err(error) => {
            let reloaded =
                AppState::load_deck_with_localizer(&current_deck_path, localizer, settings);
            on_load_selected_deck(localizer, reloaded);
            show_error_dialog(ui, localizer.could_not_sync_deck_caption(), &error);
        }
    }
}

fn default_decks_dir() -> PathBuf {
    default_deck_path()
        .parent()
        .unwrap_or_else(|| Path::new("."))
        .to_path_buf()
}

fn prompt_text_entry(
    parent: &dyn WxWidget,
    caption: &str,
    message: &str,
    default_value: &str,
) -> Option<String> {
    let dialog = TextEntryDialog::builder(parent, message, caption)
        .with_default_value(default_value)
        .build();
    if dialog.show_modal() == ID_OK {
        dialog.get_value()
    } else {
        None
    }
}

fn show_error_dialog(ui: &ReviewUi, caption: &str, message: &str) {
    show_error_dialog_with_parent(&ui.frame, caption, message);
}

fn show_info_dialog(ui: &ReviewUi, caption: &str, message: &str) {
    show_info_dialog_with_parent(&ui.frame, caption, message);
}
