#pragma once

#include "app_core/app_dialog.h"
#include "foundation/result.h"

#include <cstdio>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <string>
#include <string_view>

namespace pp::app {

enum class CloudUploadAction {
    unavailable_no_canvas,
    show_save_required_warning,
    prompt_publish,
};

enum class CloudBrowseAction {
    unavailable_no_canvas,
    show_browser,
};

enum class CloudDownloadSelectionAction {
    wait_for_selection,
    start_download,
};

enum class CloudTransferDirection {
    download,
    upload,
};

enum class CloudTransferAction {
    reject_missing_source,
    reject_missing_destination,
    start_transfer,
};

struct CloudUploadPlan {
    CloudUploadAction action = CloudUploadAction::unavailable_no_canvas;
    bool save_before_upload = false;
};

struct CloudBulkUploadPlan {
    std::size_t file_count = 0;
    int progress_total = 0;
    bool show_progress = false;
};

struct CloudDownloadRequest {
    std::string selected_file;
    std::string selected_path;
    std::string selected_name;
};

struct CloudTransferPlan {
    CloudTransferDirection direction = CloudTransferDirection::download;
    CloudTransferAction action = CloudTransferAction::reject_missing_source;
    bool enable_progress = false;
    bool disable_tls_verification = false;
};

struct CloudTransferProgressPlan {
    bool notify = false;
    float fraction = 0.0F;
};

[[nodiscard]] inline AppMessageDialogPlan plan_cloud_save_required_prompt()
{
    return plan_app_message_dialog(
        "Warning",
        "This document needs to be saved before upload.",
        false);
}

[[nodiscard]] inline AppMessageDialogPlan plan_cloud_publish_prompt()
{
    return plan_app_message_dialog(
        "Publish document",
        "Would you like to upload to the public domain?",
        true,
        "Yes",
        "No");
}

[[nodiscard]] inline AppMessageDialogPlan plan_cloud_upload_success_prompt()
{
    return plan_app_message_dialog(
        "Success",
        "This document has been succesfully uploaded.",
        false);
}

[[nodiscard]] inline AppProgressDialogPlan plan_cloud_upload_progress_dialog()
{
    return plan_app_progress_dialog("Uploading", 0);
}

[[nodiscard]] inline AppProgressDialogPlan plan_cloud_bulk_upload_progress_dialog(int progress_total)
{
    return plan_app_progress_dialog("Export Pano Image", progress_total);
}

[[nodiscard]] inline AppMessageDialogPlan plan_cloud_download_progress_prompt()
{
    return plan_app_message_dialog(
        "Downloading",
        "Download in progress",
        true);
}

[[nodiscard]] inline std::string format_cloud_download_progress_message(float progress_fraction)
{
    char buffer[64] {};
    std::snprintf(
        buffer,
        sizeof(buffer),
        "Download in progress %.2f%%",
        progress_fraction * 100.0F);
    return buffer;
}

class CloudServices {
public:
    virtual ~CloudServices() = default;

    virtual void show_save_required_warning() = 0;
    virtual void prompt_publish(bool save_before_upload) = 0;
    virtual void begin_bulk_upload(int progress_total, bool show_progress) = 0;
    virtual void upload_all_bulk_files() = 0;
    virtual void end_bulk_upload() = 0;
    virtual void show_browser() = 0;
    virtual void start_download(const CloudDownloadRequest& request) = 0;
};

[[nodiscard]] constexpr CloudUploadPlan plan_cloud_upload(
    bool has_canvas,
    bool is_new_document,
    bool has_unsaved_changes) noexcept
{
    if (!has_canvas) {
        return { CloudUploadAction::unavailable_no_canvas, false };
    }

    if (is_new_document) {
        return { CloudUploadAction::show_save_required_warning, false };
    }

    return { CloudUploadAction::prompt_publish, has_unsaved_changes };
}

[[nodiscard]] constexpr CloudBrowseAction plan_cloud_browse(bool has_canvas) noexcept
{
    return has_canvas
        ? CloudBrowseAction::show_browser
        : CloudBrowseAction::unavailable_no_canvas;
}

[[nodiscard]] constexpr CloudDownloadSelectionAction plan_cloud_download_selection(
    std::string_view selected_file) noexcept
{
    return selected_file.empty()
        ? CloudDownloadSelectionAction::wait_for_selection
        : CloudDownloadSelectionAction::start_download;
}

[[nodiscard]] constexpr CloudBulkUploadPlan plan_cloud_bulk_upload(
    std::size_t file_count,
    bool progress_ui_available) noexcept
{
    const auto max_progress_total = static_cast<std::size_t>(std::numeric_limits<int>::max());
    return {
        file_count,
        file_count > max_progress_total ? std::numeric_limits<int>::max() : static_cast<int>(file_count),
        progress_ui_available,
    };
}

[[nodiscard]] constexpr CloudTransferPlan plan_cloud_download_transfer(
    std::string_view url,
    std::string_view destination_path,
    bool has_progress_callback,
    bool disables_tls_verification) noexcept
{
    if (url.empty()) {
        return {
            CloudTransferDirection::download,
            CloudTransferAction::reject_missing_source,
            false,
            false,
        };
    }

    if (destination_path.empty()) {
        return {
            CloudTransferDirection::download,
            CloudTransferAction::reject_missing_destination,
            false,
            false,
        };
    }

    return {
        CloudTransferDirection::download,
        CloudTransferAction::start_transfer,
        has_progress_callback,
        disables_tls_verification,
    };
}

[[nodiscard]] constexpr CloudTransferPlan plan_cloud_upload_transfer(
    std::string_view filename,
    bool has_progress_callback,
    bool disables_tls_verification) noexcept
{
    if (filename.empty()) {
        return {
            CloudTransferDirection::upload,
            CloudTransferAction::reject_missing_source,
            false,
            false,
        };
    }

    return {
        CloudTransferDirection::upload,
        CloudTransferAction::start_transfer,
        has_progress_callback,
        disables_tls_verification,
    };
}

[[nodiscard]] constexpr CloudTransferProgressPlan plan_cloud_transfer_progress(
    std::int64_t total,
    std::int64_t current) noexcept
{
    if (total <= 0) {
        return {};
    }

    const auto clamped_current = current < 0
        ? std::int64_t { 0 }
        : (current > total ? total : current);
    return {
        true,
        static_cast<float>(static_cast<double>(clamped_current) / static_cast<double>(total)),
    };
}

[[nodiscard]] inline pp::foundation::Status execute_cloud_upload_plan(
    const CloudUploadPlan& plan,
    CloudServices& services)
{
    switch (plan.action) {
    case CloudUploadAction::unavailable_no_canvas:
        return pp::foundation::Status::success();
    case CloudUploadAction::show_save_required_warning:
        services.show_save_required_warning();
        return pp::foundation::Status::success();
    case CloudUploadAction::prompt_publish:
        services.prompt_publish(plan.save_before_upload);
        return pp::foundation::Status::success();
    }

    return pp::foundation::Status::invalid_argument("unknown cloud upload action");
}

[[nodiscard]] inline pp::foundation::Status execute_cloud_bulk_upload_plan(
    const CloudBulkUploadPlan& plan,
    CloudServices& services)
{
    services.begin_bulk_upload(plan.progress_total, plan.show_progress);
    services.upload_all_bulk_files();
    services.end_bulk_upload();
    return pp::foundation::Status::success();
}

[[nodiscard]] inline pp::foundation::Status execute_cloud_browse_action(
    CloudBrowseAction action,
    CloudServices& services)
{
    switch (action) {
    case CloudBrowseAction::unavailable_no_canvas:
        return pp::foundation::Status::success();
    case CloudBrowseAction::show_browser:
        services.show_browser();
        return pp::foundation::Status::success();
    }

    return pp::foundation::Status::invalid_argument("unknown cloud browse action");
}

[[nodiscard]] inline pp::foundation::Status execute_cloud_download_selection_action(
    CloudDownloadSelectionAction action,
    CloudServices& services,
    const CloudDownloadRequest& request)
{
    switch (action) {
    case CloudDownloadSelectionAction::wait_for_selection:
        return pp::foundation::Status::success();
    case CloudDownloadSelectionAction::start_download:
        if (request.selected_file.empty()) {
            return pp::foundation::Status::invalid_argument("cloud download requires a selected file");
        }
        services.start_download(request);
        return pp::foundation::Status::success();
    }

    return pp::foundation::Status::invalid_argument("unknown cloud download selection action");
}

}
