#pragma once

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

#include <array>
#include <cstddef>
#include <span>
#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 DocumentCubeFaceExportFileTarget {
    std::string face_name;
    std::string path;
};

struct DocumentCubeFaceExportTarget {
    std::array<DocumentCubeFaceExportFileTarget, pp::document::cube_face_count> faces;
    std::size_t face_count = 0;
};

struct DocumentDepthExportTarget {
    std::string image_path;
    std::string depth_path;
};

struct DocumentCubeFaceExportPayload {
    std::span<const std::byte> bytes;
};

struct DocumentDepthExportPayload {
    std::span<const std::byte> image_bytes;
    std::span<const std::byte> depth_bytes;
};

struct DocumentExportFilePayload {
    std::span<const std::byte> bytes;
};

struct DocumentExportCollectionPngPayload {
    std::string path_suffix;
    std::span<const std::byte> bytes;
};

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,
};

enum class DocumentExportSuccessKind {
    equirectangular,
    layers,
    animation_frames,
    depth,
    cube_faces,
    animation_mp4,
    timelapse,
};

enum class DocumentExportSuccessDestination {
    suppressed,
    photos,
    pictures_panopainter,
    files_panopainter,
    work_directory,
    path,
    generic_success,
};

enum class DocumentExportExecutionKind {
    equirectangular_file,
    layers_collection,
    layers_stem,
    animation_frames_collection,
    animation_frames_stem,
    depth,
    cube_faces,
    animation_mp4,
    timelapse,
};

enum class DocumentExportSnapshotRouteAction {
    use_document_snapshot_writer,
    use_legacy_export,
};

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;
};

struct DocumentExportSuccessDialogPlan {
    DocumentExportSuccessKind kind = DocumentExportSuccessKind::equirectangular;
    DocumentExportSuccessDestination destination = DocumentExportSuccessDestination::suppressed;
    AppMessageDialogPlan dialog;
    bool show_dialog = false;
};

struct DocumentExportSnapshotRoutePlan {
    DocumentExportExecutionKind kind = DocumentExportExecutionKind::equirectangular_file;
    DocumentExportSnapshotRouteAction action = DocumentExportSnapshotRouteAction::use_legacy_export;
    bool payload_complete = false;
    bool target_supported = false;
    bool platform_supported = false;
    bool uses_document_snapshot_writer = false;
    std::string_view fallback_reason;
};

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;
};

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

    virtual pp::foundation::Status write_binary_file(
        std::string_view path,
        std::span<const std::byte> bytes) = 0;
    virtual void publish_exported_image(std::string_view path) = 0;
};

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

    virtual pp::foundation::Status write_binary_file(
        std::string_view path,
        std::span<const std::byte> bytes) = 0;
    virtual void publish_exported_image(std::string_view path) = 0;
};

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

    virtual pp::foundation::Status write_binary_file(
        std::string_view path,
        std::span<const std::byte> bytes) = 0;
    virtual void publish_exported_image(std::string_view path) = 0;
};

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

    virtual pp::foundation::Status write_binary_file(
        std::string_view path,
        std::span<const std::byte> bytes) = 0;
    virtual void publish_exported_image(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 DocumentExportSuccessDestination document_export_equirectangular_platform_destination() noexcept
{
#if defined(__IOS__)
    return DocumentExportSuccessDestination::photos;
#elif defined(__OSX__)
    return DocumentExportSuccessDestination::pictures_panopainter;
#elif defined(_WIN32)
    return DocumentExportSuccessDestination::work_directory;
#else
    return DocumentExportSuccessDestination::suppressed;
#endif
}

[[nodiscard]] constexpr DocumentExportSuccessDestination document_export_media_platform_destination() noexcept
{
#if defined(__IOS__)
    return DocumentExportSuccessDestination::files_panopainter;
#elif defined(__OSX__)
    return DocumentExportSuccessDestination::pictures_panopainter;
#elif defined(_WIN32)
    return DocumentExportSuccessDestination::work_directory;
#else
    return DocumentExportSuccessDestination::suppressed;
#endif
}

[[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 DocumentExportSuccessKind document_export_collection_success_kind(
    DocumentExportCollectionKind kind) noexcept
{
    switch (kind) {
    case DocumentExportCollectionKind::layers:
        return DocumentExportSuccessKind::layers;
    case DocumentExportCollectionKind::animation_frames:
        return DocumentExportSuccessKind::animation_frames;
    }

    return DocumentExportSuccessKind::layers;
}

[[nodiscard]] constexpr const char* document_export_failure_dialog_title(
    DocumentExportSuccessKind kind) noexcept
{
    switch (kind) {
    case DocumentExportSuccessKind::equirectangular:
        return "Export Equirectangular";
    case DocumentExportSuccessKind::layers:
    case DocumentExportSuccessKind::animation_frames:
        return "Export Layers";
    case DocumentExportSuccessKind::depth:
        return "Export 3D View + Depth";
    case DocumentExportSuccessKind::cube_faces:
        return "Export Cube Faces";
    case DocumentExportSuccessKind::animation_mp4:
        return "Export Animation";
    case DocumentExportSuccessKind::timelapse:
        return "Export Timelapse";
    }

    return "Export";
}

[[nodiscard]] constexpr const char* document_export_execution_log_message(
    DocumentExportExecutionKind kind) noexcept
{
    switch (kind) {
    case DocumentExportExecutionKind::equirectangular_file:
        return "Document export file action failed";
    case DocumentExportExecutionKind::layers_collection:
        return "Document layer collection export failed";
    case DocumentExportExecutionKind::layers_stem:
        return "Document layer stem export failed";
    case DocumentExportExecutionKind::animation_frames_collection:
        return "Document animation frame collection export failed";
    case DocumentExportExecutionKind::animation_frames_stem:
        return "Document animation frame stem export failed";
    case DocumentExportExecutionKind::depth:
        return "Document depth export failed";
    case DocumentExportExecutionKind::cube_faces:
        return "Document cube-face export failed";
    case DocumentExportExecutionKind::animation_mp4:
        return "Document animation export failed";
    case DocumentExportExecutionKind::timelapse:
        return "Document timelapse export failed";
    }

    return "Document export failed";
}

[[nodiscard]] constexpr bool ascii_iequals(std::string_view left, std::string_view right) noexcept
{
    if (left.size() != right.size()) {
        return false;
    }

    for (std::size_t i = 0; i < left.size(); ++i) {
        auto lhs = left[i];
        if (lhs >= 'A' && lhs <= 'Z') {
            lhs = static_cast<char>(lhs - 'A' + 'a');
        }
        auto rhs = right[i];
        if (rhs >= 'A' && rhs <= 'Z') {
            rhs = static_cast<char>(rhs - 'A' + 'a');
        }
        if (lhs != rhs) {
            return false;
        }
    }

    return true;
}

[[nodiscard]] constexpr bool document_export_path_has_extension(
    std::string_view path,
    std::string_view extension) noexcept
{
    return path.size() >= extension.size()
        && ascii_iequals(path.substr(path.size() - extension.size()), extension);
}

[[nodiscard]] constexpr bool document_export_path_is_png_target(std::string_view path) noexcept
{
    return document_export_path_has_extension(path, ".png");
}

[[nodiscard]] constexpr bool document_export_path_is_jpeg_target(std::string_view path) noexcept
{
    return document_export_path_has_extension(path, ".jpg")
        || document_export_path_has_extension(path, ".jpeg");
}

[[nodiscard]] constexpr bool document_export_snapshot_target_supported(
    DocumentExportExecutionKind kind,
    std::string_view target_path = {}) noexcept
{
    switch (kind) {
    case DocumentExportExecutionKind::equirectangular_file:
        return document_export_path_is_png_target(target_path)
            || document_export_path_is_jpeg_target(target_path);
    case DocumentExportExecutionKind::layers_collection:
    case DocumentExportExecutionKind::layers_stem:
    case DocumentExportExecutionKind::animation_frames_collection:
    case DocumentExportExecutionKind::animation_frames_stem:
    case DocumentExportExecutionKind::depth:
    case DocumentExportExecutionKind::cube_faces:
        return true;
    case DocumentExportExecutionKind::animation_mp4:
    case DocumentExportExecutionKind::timelapse:
        return false;
    }

    return false;
}

[[nodiscard]] constexpr bool document_export_snapshot_platform_supported() noexcept
{
#if __WEB__
    return false;
#else
    return true;
#endif
}

[[nodiscard]] constexpr DocumentExportSnapshotRoutePlan plan_document_export_snapshot_route(
    DocumentExportExecutionKind kind,
    DocumentCanvasSaveSnapshotReport report,
    bool target_supported,
    bool platform_supported) noexcept
{
    DocumentExportSnapshotRoutePlan plan;
    plan.kind = kind;
    plan.payload_complete = report.payload_complete;
    plan.target_supported = target_supported;
    plan.platform_supported = platform_supported;

    if (!platform_supported) {
        plan.fallback_reason = "document snapshot export is disabled on this platform";
        return plan;
    }

    if (!target_supported) {
        plan.fallback_reason = "document snapshot export does not support this target";
        return plan;
    }

    if (!report.payload_complete) {
        plan.fallback_reason = "document snapshot still requires renderer payload readback";
        return plan;
    }

    plan.action = DocumentExportSnapshotRouteAction::use_document_snapshot_writer;
    plan.uses_document_snapshot_writer = true;
    return plan;
}

[[nodiscard]] constexpr DocumentExportSnapshotRoutePlan plan_document_export_snapshot_route_for_target(
    DocumentExportExecutionKind kind,
    DocumentCanvasSaveSnapshotReport report,
    std::string_view target_path,
    bool platform_supported) noexcept
{
    return plan_document_export_snapshot_route(
        kind,
        report,
        document_export_snapshot_target_supported(kind, target_path),
        platform_supported);
}

[[nodiscard]] constexpr DocumentExportSnapshotRoutePlan plan_document_export_snapshot_route_for_current_platform(
    DocumentExportExecutionKind kind,
    DocumentCanvasSaveSnapshotReport report,
    std::string_view target_path = {}) noexcept
{
    return plan_document_export_snapshot_route_for_target(
        kind,
        report,
        target_path,
        document_export_snapshot_platform_supported());
}

[[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 AppMessageDialogPlan plan_document_export_failure_dialog(
    DocumentExportSuccessKind kind,
    std::string_view status_message)
{
    return plan_app_message_dialog(
        document_export_failure_dialog_title(kind),
        status_message,
        false);
}

[[nodiscard]] inline AppMessageDialogPlan plan_document_export_license_disabled_dialog()
{
    return plan_app_message_dialog(
        "License",
        "This function is disabled in demo mode.",
        false);
}

[[nodiscard]] inline DocumentExportSuccessDialogPlan plan_document_export_success_dialog(
    DocumentExportSuccessKind kind,
    DocumentExportSuccessDestination destination,
    std::string_view detail = {})
{
    DocumentExportSuccessDialogPlan plan;
    plan.kind = kind;
    plan.destination = destination;
    if (destination == DocumentExportSuccessDestination::suppressed) {
        return plan;
    }

    std::string message;
    switch (kind) {
    case DocumentExportSuccessKind::equirectangular:
        plan.dialog.title = "Export Equirectangular";
        switch (destination) {
        case DocumentExportSuccessDestination::photos:
            message = "Image exported to Photos";
            break;
        case DocumentExportSuccessDestination::pictures_panopainter:
            message = "Image exported to Pictures/PanoPainter folder";
            break;
        case DocumentExportSuccessDestination::work_directory:
            message = "Image exported to ";
            message += detail;
            break;
        case DocumentExportSuccessDestination::suppressed:
        case DocumentExportSuccessDestination::files_panopainter:
        case DocumentExportSuccessDestination::path:
        case DocumentExportSuccessDestination::generic_success:
            break;
        }
        break;
    case DocumentExportSuccessKind::layers:
    case DocumentExportSuccessKind::animation_frames:
        plan.dialog.title = "Export Layers";
        if (destination == DocumentExportSuccessDestination::files_panopainter) {
            message = "Image layers exported to Files/PanoPainter";
        } else if (destination == DocumentExportSuccessDestination::path) {
            message = "Layers exported to: ";
            message += detail;
        }
        break;
    case DocumentExportSuccessKind::depth:
        plan.dialog.title = "Export 3D View + Depth";
        if (destination == DocumentExportSuccessDestination::files_panopainter) {
            message = "Image and depth exported to Files/PanoPainter";
        } else if (destination == DocumentExportSuccessDestination::pictures_panopainter) {
            message = "Image and depth exported to Pictures/PanoPainter folder";
        } else if (destination == DocumentExportSuccessDestination::work_directory) {
            message = "Image and depth exported to ";
            message += detail;
        }
        break;
    case DocumentExportSuccessKind::cube_faces:
        plan.dialog.title = "Export Cube Faces";
        if (destination == DocumentExportSuccessDestination::files_panopainter) {
            message = "Image and depth exported to Files/PanoPainter";
        } else if (destination == DocumentExportSuccessDestination::pictures_panopainter) {
            message = "Image and depth exported to Pictures/PanoPainter folder";
        } else if (destination == DocumentExportSuccessDestination::work_directory) {
            message = "Image and depth exported to ";
            message += detail;
        }
        break;
    case DocumentExportSuccessKind::animation_mp4:
        plan.dialog.title = "Export Animation";
        if (destination == DocumentExportSuccessDestination::path) {
            message = "Animation exported to: ";
            message += detail;
        } else if (destination == DocumentExportSuccessDestination::generic_success) {
            message = "Animation exported successfully.";
        }
        break;
    case DocumentExportSuccessKind::timelapse:
        plan.dialog.title = "Export Timelapse";
        if (destination == DocumentExportSuccessDestination::path) {
            message = "Timelapse exported to: ";
            message += detail;
        } else if (destination == DocumentExportSuccessDestination::generic_success) {
            message = "Timelapse exported successfully.";
        }
        break;
    }

    if (!plan.dialog.title.empty() && !message.empty()) {
        plan.dialog.message = std::move(message);
        plan.show_dialog = true;
    }
    return plan;
}

[[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]] constexpr std::array<std::string_view, pp::document::cube_face_count>
document_cube_face_export_names() noexcept
{
    return {
        "front",
        "right",
        "back",
        "left",
        "top",
        "bottom",
    };
}

[[nodiscard]] inline pp::foundation::Result<DocumentCubeFaceExportTarget> make_document_cube_face_export_target(
    std::string_view work_directory,
    std::string_view document_name)
{
    if (work_directory.empty()) {
        return pp::foundation::Result<DocumentCubeFaceExportTarget>::failure(
            pp::foundation::Status::invalid_argument("work directory must not be empty"));
    }

    if (document_name.empty()) {
        return pp::foundation::Result<DocumentCubeFaceExportTarget>::failure(
            pp::foundation::Status::invalid_argument("document name must not be empty"));
    }

    DocumentCubeFaceExportTarget target;
    const auto face_names = document_cube_face_export_names();
    target.face_count = face_names.size();
    for (std::size_t face_index = 0; face_index < face_names.size(); ++face_index) {
        auto& face = target.faces[face_index];
        face.face_name = face_names[face_index];
        face.path.reserve(work_directory.size() + document_name.size() + face_names[face_index].size() + 6);
        face.path += work_directory;
        face.path += "/";
        face.path += document_name;
        face.path += "-";
        face.path += face_names[face_index];
        face.path += ".png";
    }

    return pp::foundation::Result<DocumentCubeFaceExportTarget>::success(std::move(target));
}

[[nodiscard]] inline pp::foundation::Result<DocumentDepthExportTarget> make_document_depth_export_target(
    std::string_view work_directory,
    std::string_view document_name)
{
    if (work_directory.empty()) {
        return pp::foundation::Result<DocumentDepthExportTarget>::failure(
            pp::foundation::Status::invalid_argument("work directory must not be empty"));
    }

    if (document_name.empty()) {
        return pp::foundation::Result<DocumentDepthExportTarget>::failure(
            pp::foundation::Status::invalid_argument("document name must not be empty"));
    }

    DocumentDepthExportTarget target;
    target.image_path.reserve(work_directory.size() + document_name.size() + 5U);
    target.image_path += work_directory;
    target.image_path += "/";
    target.image_path += document_name;
    target.image_path += ".png";
    target.depth_path.reserve(work_directory.size() + document_name.size() + 11U);
    target.depth_path += work_directory;
    target.depth_path += "/";
    target.depth_path += document_name;
    target.depth_path += "_depth.png";
    return pp::foundation::Result<DocumentDepthExportTarget>::success(std::move(target));
}

[[nodiscard]] inline std::string document_export_two_digit_index(std::size_t index)
{
    auto value = std::to_string(index);
    if (value.size() < 2U) {
        value.insert(value.begin(), '0');
    }
    return value;
}

[[nodiscard]] inline std::string make_document_layer_export_path_suffix(
    std::size_t layer_index,
    std::string_view layer_name)
{
    std::string suffix;
    const auto index = document_export_two_digit_index(layer_index);
    suffix.reserve(10U + index.size() + layer_name.size());
    suffix += "-layer";
    suffix += index;
    suffix += "-";
    suffix += layer_name;
    suffix += ".png";
    return suffix;
}

[[nodiscard]] inline std::string make_document_animation_frame_export_path_suffix(std::size_t frame_index)
{
    std::string suffix;
    const auto index = document_export_two_digit_index(frame_index);
    suffix.reserve(5U + index.size());
    suffix += "-";
    suffix += index;
    suffix += ".png";
    return suffix;
}

[[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_cube_face_export_write(
    const DocumentCubeFaceExportTarget& target,
    std::span<const DocumentCubeFaceExportPayload> face_payloads,
    DocumentCubeFaceExportWriteServices& services)
{
    if (target.face_count != pp::document::cube_face_count) {
        return pp::foundation::Status::invalid_argument("cube face export target must contain all cube faces");
    }

    if (face_payloads.size() != target.face_count) {
        return pp::foundation::Status::invalid_argument("cube face export payload count must match target face count");
    }

    for (std::size_t face_index = 0; face_index < target.face_count; ++face_index) {
        const auto& face = target.faces[face_index];
        if (face.path.empty()) {
            return pp::foundation::Status::invalid_argument("cube face export path must not be empty");
        }
        if (face_payloads[face_index].bytes.empty()) {
            return pp::foundation::Status::invalid_argument("cube face export payload must not be empty");
        }

        const auto write_status = services.write_binary_file(face.path, face_payloads[face_index].bytes);
        if (!write_status.ok()) {
            return write_status;
        }
        services.publish_exported_image(face.path);
    }

    return pp::foundation::Status::success();
}

[[nodiscard]] inline pp::foundation::Status execute_document_depth_export_write(
    const DocumentDepthExportTarget& target,
    DocumentDepthExportPayload payload,
    DocumentDepthExportWriteServices& services)
{
    if (target.image_path.empty() || target.depth_path.empty()) {
        return pp::foundation::Status::invalid_argument("depth export target requires image and depth paths");
    }
    if (payload.image_bytes.empty() || payload.depth_bytes.empty()) {
        return pp::foundation::Status::invalid_argument("depth export payload requires image and depth bytes");
    }

    const auto image_status = services.write_binary_file(target.image_path, payload.image_bytes);
    if (!image_status.ok()) {
        return image_status;
    }
    services.publish_exported_image(target.image_path);

    const auto depth_status = services.write_binary_file(target.depth_path, payload.depth_bytes);
    if (!depth_status.ok()) {
        return depth_status;
    }
    services.publish_exported_image(target.depth_path);

    return pp::foundation::Status::success();
}

[[nodiscard]] inline pp::foundation::Status execute_document_export_file_write(
    const DocumentExportFileTarget& target,
    DocumentExportFilePayload payload,
    DocumentExportFileWriteServices& services)
{
    if (target.path.empty()) {
        return pp::foundation::Status::invalid_argument("export file target requires a path");
    }
    if (payload.bytes.empty()) {
        return pp::foundation::Status::invalid_argument("export file payload must not be empty");
    }

    const auto write_status = services.write_binary_file(target.path, payload.bytes);
    if (!write_status.ok()) {
        return write_status;
    }
    services.publish_exported_image(target.path);

    return pp::foundation::Status::success();
}

[[nodiscard]] inline pp::foundation::Status execute_document_export_collection_write(
    const DocumentExportCollectionTarget& target,
    std::span<const DocumentExportCollectionPngPayload> payloads,
    DocumentExportCollectionWriteServices& services)
{
    if (target.stem_path.empty()) {
        return pp::foundation::Status::invalid_argument("export collection target requires a stem path");
    }

    if (payloads.empty()) {
        return pp::foundation::Status::invalid_argument("export collection payloads must not be empty");
    }

    for (const auto& payload : payloads) {
        if (payload.path_suffix.empty()) {
            return pp::foundation::Status::invalid_argument("export collection payload suffix must not be empty");
        }
        if (payload.bytes.empty()) {
            return pp::foundation::Status::invalid_argument("export collection payload must not be empty");
        }

        std::string path;
        path.reserve(target.stem_path.size() + payload.path_suffix.size());
        path += target.stem_path;
        path += payload.path_suffix;
        const auto write_status = services.write_binary_file(path, payload.bytes);
        if (!write_status.ok()) {
            return write_status;
        }
        services.publish_exported_image(path);
    }

    return pp::foundation::Status::success();
}

[[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");
}

}
