#pragma once

#include "foundation/result.h"

#include <string>
#include <string_view>
#include <utility>

namespace pp::app {

struct DocumentExportFileTarget {
    std::string path;
    std::string suggested_name;
};

struct DocumentExportCollectionTarget {
    std::string directory;
    std::string stem_path;
};

struct DocumentExportStemTarget {
    std::string stem_path;
};

struct DocumentExportSuggestedName {
    std::string name;
};

enum class DocumentExportCollectionDestination {
    work_directory_collection,
    picked_directory_stem,
};

enum class DocumentExportStartDecision {
    start_now,
    show_license_disabled,
    unavailable_no_canvas,
};

enum class DocumentExportMenuKind {
    jpeg,
    png,
    layers,
    cube_faces,
    depth,
    animation_frames,
    animation_mp4,
    timelapse,
};

enum class DocumentExportMenuAction {
    show_jpeg_dialog,
    show_png_dialog,
    show_layers_dialog,
    show_cube_faces_dialog,
    show_depth_dialog,
    show_animation_frames_dialog,
    show_animation_mp4_dialog,
    show_timelapse_dialog,
    show_license_disabled,
    unavailable_no_canvas,
};

enum class DocumentExportCollectionKind {
    layers,
    animation_frames,
};

enum class DocumentVideoExportKind {
    animation_mp4,
    timelapse,
};

struct DocumentExportMenuPlan {
    DocumentExportMenuKind kind = DocumentExportMenuKind::jpeg;
    DocumentExportMenuAction action = DocumentExportMenuAction::show_jpeg_dialog;
    bool opens_dialog = true;
};

struct DocumentExportCollectionTargetPlan {
    DocumentExportCollectionKind kind = DocumentExportCollectionKind::layers;
    DocumentExportCollectionDestination destination = DocumentExportCollectionDestination::picked_directory_stem;
    std::string_view suffix;
};

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

    virtual void show_jpeg_dialog() = 0;
    virtual void show_png_dialog() = 0;
    virtual void show_layers_dialog() = 0;
    virtual void show_cube_faces_dialog() = 0;
    virtual void show_depth_dialog() = 0;
    virtual void show_animation_frames_dialog() = 0;
    virtual void show_animation_mp4_dialog() = 0;
    virtual void show_timelapse_dialog() = 0;
    virtual void show_license_disabled() = 0;
};

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

    virtual bool create_directory(std::string_view directory) = 0;
    virtual void export_equirectangular(const DocumentExportFileTarget& target) = 0;
    virtual void export_layers_to_stem(const DocumentExportStemTarget& target) = 0;
    virtual void export_layers_to_collection(const DocumentExportCollectionTarget& target) = 0;
    virtual void export_animation_frames_to_stem(const DocumentExportStemTarget& target) = 0;
    virtual void export_animation_frames_to_collection(const DocumentExportCollectionTarget& target) = 0;
    virtual void export_depth(std::string_view document_name) = 0;
    virtual void export_cube_faces(std::string_view document_name) = 0;
};

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

    virtual void export_animation_mp4(std::string_view path) = 0;
    virtual void export_timelapse_mp4(std::string_view path) = 0;
    virtual void show_animation_export_success(std::string_view path) = 0;
    virtual void show_timelapse_export_success(std::string_view path) = 0;
};

[[nodiscard]] constexpr DocumentExportStartDecision plan_document_export_start(
    bool requires_license,
    bool license_valid,
    bool has_canvas) noexcept
{
    if (requires_license && !license_valid) {
        return DocumentExportStartDecision::show_license_disabled;
    }

    return has_canvas
        ? DocumentExportStartDecision::start_now
        : DocumentExportStartDecision::unavailable_no_canvas;
}

[[nodiscard]] constexpr bool document_export_menu_requires_license(
    DocumentExportMenuKind kind) noexcept
{
    switch (kind) {
    case DocumentExportMenuKind::animation_mp4:
    case DocumentExportMenuKind::timelapse:
        return true;
    case DocumentExportMenuKind::jpeg:
    case DocumentExportMenuKind::png:
    case DocumentExportMenuKind::layers:
    case DocumentExportMenuKind::cube_faces:
    case DocumentExportMenuKind::depth:
    case DocumentExportMenuKind::animation_frames:
        return false;
    }

    return false;
}

[[nodiscard]] constexpr DocumentExportMenuAction document_export_menu_dialog_action(
    DocumentExportMenuKind kind) noexcept
{
    switch (kind) {
    case DocumentExportMenuKind::jpeg:
        return DocumentExportMenuAction::show_jpeg_dialog;
    case DocumentExportMenuKind::png:
        return DocumentExportMenuAction::show_png_dialog;
    case DocumentExportMenuKind::layers:
        return DocumentExportMenuAction::show_layers_dialog;
    case DocumentExportMenuKind::cube_faces:
        return DocumentExportMenuAction::show_cube_faces_dialog;
    case DocumentExportMenuKind::depth:
        return DocumentExportMenuAction::show_depth_dialog;
    case DocumentExportMenuKind::animation_frames:
        return DocumentExportMenuAction::show_animation_frames_dialog;
    case DocumentExportMenuKind::animation_mp4:
        return DocumentExportMenuAction::show_animation_mp4_dialog;
    case DocumentExportMenuKind::timelapse:
        return DocumentExportMenuAction::show_timelapse_dialog;
    }

    return DocumentExportMenuAction::show_jpeg_dialog;
}

[[nodiscard]] constexpr DocumentExportMenuPlan plan_document_export_menu_action(
    DocumentExportMenuKind kind,
    bool has_canvas,
    bool license_valid) noexcept
{
    DocumentExportMenuPlan plan;
    plan.kind = kind;
    plan.action = document_export_menu_dialog_action(kind);

    const auto start = plan_document_export_start(
        document_export_menu_requires_license(kind),
        license_valid,
        has_canvas);
    if (start == DocumentExportStartDecision::show_license_disabled) {
        plan.action = DocumentExportMenuAction::show_license_disabled;
        plan.opens_dialog = false;
    } else if (start == DocumentExportStartDecision::unavailable_no_canvas) {
        plan.action = DocumentExportMenuAction::unavailable_no_canvas;
        plan.opens_dialog = false;
    }

    return plan;
}

[[nodiscard]] constexpr std::string_view document_export_collection_suffix(
    DocumentExportCollectionKind kind) noexcept
{
    switch (kind) {
    case DocumentExportCollectionKind::layers:
        return "_layers";
    case DocumentExportCollectionKind::animation_frames:
        return "_frames";
    }

    return {};
}

[[nodiscard]] constexpr DocumentExportCollectionTargetPlan plan_document_export_collection_target(
    DocumentExportCollectionKind kind,
    bool use_work_directory_collection) noexcept
{
    return {
        kind,
        use_work_directory_collection
            ? DocumentExportCollectionDestination::work_directory_collection
            : DocumentExportCollectionDestination::picked_directory_stem,
        document_export_collection_suffix(kind),
    };
}

[[nodiscard]] inline pp::foundation::Result<DocumentExportFileTarget> make_document_export_file_target(
    std::string_view work_directory,
    std::string_view document_name,
    std::string_view extension)
{
    if (document_name.empty()) {
        return pp::foundation::Result<DocumentExportFileTarget>::failure(
            pp::foundation::Status::invalid_argument("document name must not be empty"));
    }

    if (extension.empty()) {
        return pp::foundation::Result<DocumentExportFileTarget>::failure(
            pp::foundation::Status::invalid_argument("extension must not be empty"));
    }

    DocumentExportFileTarget target;
    target.suggested_name.reserve(document_name.size() + extension.size());
    target.suggested_name += document_name;
    target.suggested_name += extension;
    target.path.reserve(work_directory.size() + target.suggested_name.size() + 1);
    target.path += work_directory;
    target.path += "/";
    target.path += target.suggested_name;
    return pp::foundation::Result<DocumentExportFileTarget>::success(std::move(target));
}

[[nodiscard]] inline pp::foundation::Result<DocumentExportCollectionTarget> make_document_export_collection_target(
    std::string_view work_directory,
    std::string_view document_name,
    std::string_view suffix)
{
    if (document_name.empty()) {
        return pp::foundation::Result<DocumentExportCollectionTarget>::failure(
            pp::foundation::Status::invalid_argument("document name must not be empty"));
    }

    DocumentExportCollectionTarget target;
    target.directory.reserve(work_directory.size() + document_name.size() + suffix.size() + 1);
    target.directory += work_directory;
    target.directory += "/";
    target.directory += document_name;
    target.directory += suffix;
    target.stem_path.reserve(target.directory.size() + document_name.size() + 1);
    target.stem_path += target.directory;
    target.stem_path += "/";
    target.stem_path += document_name;
    return pp::foundation::Result<DocumentExportCollectionTarget>::success(std::move(target));
}

[[nodiscard]] inline pp::foundation::Result<DocumentExportStemTarget> make_document_export_stem_target(
    std::string_view directory,
    std::string_view document_name)
{
    if (document_name.empty()) {
        return pp::foundation::Result<DocumentExportStemTarget>::failure(
            pp::foundation::Status::invalid_argument("document name must not be empty"));
    }

    DocumentExportStemTarget target;
    target.stem_path.reserve(directory.size() + document_name.size() + 1);
    target.stem_path += directory;
    target.stem_path += "/";
    target.stem_path += document_name;
    return pp::foundation::Result<DocumentExportStemTarget>::success(std::move(target));
}

[[nodiscard]] inline pp::foundation::Result<DocumentExportSuggestedName> make_document_export_suggested_name(
    std::string_view document_name,
    std::string_view suffix)
{
    if (document_name.empty()) {
        return pp::foundation::Result<DocumentExportSuggestedName>::failure(
            pp::foundation::Status::invalid_argument("document name must not be empty"));
    }

    DocumentExportSuggestedName target;
    target.name.reserve(document_name.size() + suffix.size());
    target.name += document_name;
    target.name += suffix;
    return pp::foundation::Result<DocumentExportSuggestedName>::success(std::move(target));
}

[[nodiscard]] inline pp::foundation::Status execute_document_export_file(
    const DocumentExportFileTarget& target,
    DocumentExportServices& services)
{
    if (target.path.empty() || target.suggested_name.empty()) {
        return pp::foundation::Status::invalid_argument("export file target requires a path and suggested name");
    }

    services.export_equirectangular(target);
    return pp::foundation::Status::success();
}

[[nodiscard]] inline pp::foundation::Status execute_document_export_stem(
    DocumentExportCollectionKind kind,
    const DocumentExportStemTarget& target,
    DocumentExportServices& services)
{
    if (target.stem_path.empty()) {
        return pp::foundation::Status::invalid_argument("export stem target requires a stem path");
    }

    switch (kind) {
    case DocumentExportCollectionKind::layers:
        services.export_layers_to_stem(target);
        return pp::foundation::Status::success();
    case DocumentExportCollectionKind::animation_frames:
        services.export_animation_frames_to_stem(target);
        return pp::foundation::Status::success();
    }

    return pp::foundation::Status::invalid_argument("unknown document export collection kind");
}

[[nodiscard]] inline pp::foundation::Status execute_document_export_collection(
    DocumentExportCollectionKind kind,
    const DocumentExportCollectionTarget& target,
    DocumentExportServices& services)
{
    if (target.directory.empty() || target.stem_path.empty()) {
        return pp::foundation::Status::invalid_argument("export collection target requires a directory and stem path");
    }

    if (!services.create_directory(target.directory)) {
        return pp::foundation::Status::success();
    }

    switch (kind) {
    case DocumentExportCollectionKind::layers:
        services.export_layers_to_collection(target);
        return pp::foundation::Status::success();
    case DocumentExportCollectionKind::animation_frames:
        services.export_animation_frames_to_collection(target);
        return pp::foundation::Status::success();
    }

    return pp::foundation::Status::invalid_argument("unknown document export collection kind");
}

[[nodiscard]] inline pp::foundation::Status execute_document_export_depth(
    std::string_view document_name,
    DocumentExportServices& services)
{
    if (document_name.empty()) {
        return pp::foundation::Status::invalid_argument("document name must not be empty");
    }

    services.export_depth(document_name);
    return pp::foundation::Status::success();
}

[[nodiscard]] inline pp::foundation::Status execute_document_export_cube_faces(
    std::string_view document_name,
    DocumentExportServices& services)
{
    if (document_name.empty()) {
        return pp::foundation::Status::invalid_argument("document name must not be empty");
    }

    services.export_cube_faces(document_name);
    return pp::foundation::Status::success();
}

[[nodiscard]] inline pp::foundation::Status execute_document_video_export(
    DocumentVideoExportKind kind,
    std::string_view path,
    DocumentVideoExportServices& services)
{
    if (path.empty()) {
        return pp::foundation::Status::invalid_argument("video export path must not be empty");
    }

    switch (kind) {
    case DocumentVideoExportKind::animation_mp4:
        services.export_animation_mp4(path);
        services.show_animation_export_success(path);
        return pp::foundation::Status::success();
    case DocumentVideoExportKind::timelapse:
        services.export_timelapse_mp4(path);
        services.show_timelapse_export_success(path);
        return pp::foundation::Status::success();
    }

    return pp::foundation::Status::invalid_argument("unknown document video export kind");
}

[[nodiscard]] inline pp::foundation::Status execute_document_export_menu_plan(
    const DocumentExportMenuPlan& plan,
    DocumentExportMenuServices& services)
{
    switch (plan.action) {
    case DocumentExportMenuAction::show_jpeg_dialog:
        services.show_jpeg_dialog();
        return pp::foundation::Status::success();
    case DocumentExportMenuAction::show_png_dialog:
        services.show_png_dialog();
        return pp::foundation::Status::success();
    case DocumentExportMenuAction::show_layers_dialog:
        services.show_layers_dialog();
        return pp::foundation::Status::success();
    case DocumentExportMenuAction::show_cube_faces_dialog:
        services.show_cube_faces_dialog();
        return pp::foundation::Status::success();
    case DocumentExportMenuAction::show_depth_dialog:
        services.show_depth_dialog();
        return pp::foundation::Status::success();
    case DocumentExportMenuAction::show_animation_frames_dialog:
        services.show_animation_frames_dialog();
        return pp::foundation::Status::success();
    case DocumentExportMenuAction::show_animation_mp4_dialog:
        services.show_animation_mp4_dialog();
        return pp::foundation::Status::success();
    case DocumentExportMenuAction::show_timelapse_dialog:
        services.show_timelapse_dialog();
        return pp::foundation::Status::success();
    case DocumentExportMenuAction::show_license_disabled:
        services.show_license_disabled();
        return pp::foundation::Status::success();
    case DocumentExportMenuAction::unavailable_no_canvas:
        return pp::foundation::Status::success();
    }

    return pp::foundation::Status::invalid_argument("unknown document export menu action");
}

}
