#pragma once

#include "foundation/result.h"

#include <cmath>
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

namespace pp::app {

inline constexpr std::size_t document_layer_name_max_length = 128;
inline constexpr int document_layer_legacy_blend_mode_count = 5;

enum class DocumentLayerRenameAction {
    no_op_same_name,
    rename_and_record_undo,
};

enum class DocumentLayerOperation {
    add,
    duplicate,
    select,
    reorder,
    remove,
    set_opacity,
    set_visibility,
    set_alpha_lock,
    set_blend_mode,
    set_highlight,
};

enum class DocumentLayerMenuCommand {
    clear,
    rename,
    merge_down,
};

enum class DocumentLayerMenuAction {
    clear_current_layer,
    show_rename_dialog,
    merge_with_lower_layer,
    show_merge_animated_not_supported,
    no_op_select_layer,
    no_op_select_upper_layer,
};

struct DocumentLayerRenamePlan {
    std::string old_name;
    std::string new_name;
    DocumentLayerRenameAction action = DocumentLayerRenameAction::no_op_same_name;
};

struct DocumentLayerOperationPlan {
    DocumentLayerOperation operation = DocumentLayerOperation::select;
    int index = 0;
    int from_index = 0;
    int to_index = 0;
    int insert_index = 0;
    int source_index = 0;
    std::string name;
    float opacity = 1.0F;
    bool flag = false;
    int blend_mode = 0;
    bool mutates_document = false;
    bool marks_unsaved = false;
    bool reloads_animation_layers = false;
    bool updates_title = false;
};

struct DocumentLayerMenuPlan {
    DocumentLayerMenuCommand command = DocumentLayerMenuCommand::clear;
    DocumentLayerMenuAction action = DocumentLayerMenuAction::clear_current_layer;
    std::string label;
    int from_index = 0;
    int to_index = 0;
};

struct DocumentLayerMergePlan {
    int from_index = 0;
    int to_index = 0;
    bool create_history = true;
};

struct DocumentLayerPanelInput {
    int layer_index = 0;
    std::string name;
    float opacity = 1.0F;
    bool visible = true;
    bool alpha_locked = false;
    int blend_mode = 0;
};

struct DocumentLayerPanelLayerView {
    int layer_index = 0;
    std::string name;
    float opacity = 1.0F;
    bool visible = true;
    bool alpha_locked = false;
    int blend_mode = 0;
    bool current = false;
};

struct DocumentLayerPanelView {
    int current_index = 0;
    float current_opacity = 1.0F;
    bool current_alpha_locked = false;
    int current_blend_mode = 0;
    std::vector<DocumentLayerPanelLayerView> layers;
};

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

    virtual void clear_current_layer() = 0;
    virtual void show_rename_dialog() = 0;
    virtual void merge_with_lower_layer(int from_index, int to_index) = 0;
    virtual void show_merge_animated_not_supported() = 0;
};

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

    virtual void record_layer_rename_undo(std::string_view old_name, std::string_view new_name) = 0;
    virtual void set_current_layer_name(std::string_view new_name) = 0;
    virtual void finish_layer_rename() = 0;
};

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

    virtual void add_layer(std::string_view name, int insert_index) = 0;
    virtual void duplicate_layer(int source_index, int insert_index) = 0;
    virtual void select_layer(int index) = 0;
    virtual void reorder_layer(int from_index, int to_index) = 0;
    virtual void remove_layer(int index) = 0;
    virtual void set_layer_opacity(int index, float opacity) = 0;
    virtual void set_layer_visibility(int index, bool visible) = 0;
    virtual void set_layer_alpha_lock(int index, bool locked) = 0;
    virtual void set_layer_blend_mode(int index, int blend_mode) = 0;
    virtual void set_layer_highlight(int index, bool highlighted) = 0;
    virtual void mark_unsaved() = 0;
    virtual void reload_animation_layers() = 0;
    virtual void update_title() = 0;
};

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

    virtual void merge_layers(int from_index, int to_index, bool create_history) = 0;
};

[[nodiscard]] inline bool document_layer_rename_records_history(
    const DocumentLayerRenamePlan& plan) noexcept
{
    return plan.action == DocumentLayerRenameAction::rename_and_record_undo;
}

[[nodiscard]] inline bool document_layer_operation_records_history(
    const DocumentLayerOperationPlan& plan) noexcept
{
    switch (plan.operation) {
    case DocumentLayerOperation::add:
    case DocumentLayerOperation::duplicate:
    case DocumentLayerOperation::remove:
    case DocumentLayerOperation::set_opacity:
    case DocumentLayerOperation::set_visibility:
    case DocumentLayerOperation::set_alpha_lock:
    case DocumentLayerOperation::set_blend_mode:
        return plan.mutates_document;
    case DocumentLayerOperation::reorder:
        return plan.mutates_document;
    case DocumentLayerOperation::select:
    case DocumentLayerOperation::set_highlight:
        return false;
    }

    return false;
}

[[nodiscard]] inline bool document_layer_merge_records_history(
    const DocumentLayerMergePlan& plan) noexcept
{
    return plan.create_history;
}

[[nodiscard]] inline pp::foundation::Status validate_layer_index(
    int layer_count,
    int index) noexcept
{
    if (layer_count <= 0) {
        return pp::foundation::Status::invalid_argument("document must contain at least one layer");
    }

    if (index < 0 || index >= layer_count) {
        return pp::foundation::Status::out_of_range("layer index is outside the document");
    }

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

[[nodiscard]] inline pp::foundation::Status validate_layer_insert_index(
    int layer_count,
    int index) noexcept
{
    if (layer_count < 0) {
        return pp::foundation::Status::invalid_argument("layer count must not be negative");
    }

    if (index < 0 || index > layer_count) {
        return pp::foundation::Status::out_of_range("layer insert index is outside the document");
    }

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

[[nodiscard]] inline pp::foundation::Result<DocumentLayerPanelView> plan_document_layer_panel_view(
    const std::vector<DocumentLayerPanelInput>& layers,
    int current_index)
{
    if (layers.empty()) {
        return pp::foundation::Result<DocumentLayerPanelView>::failure(
            pp::foundation::Status::invalid_argument("layer panel requires at least one layer"));
    }

    const auto current_status = validate_layer_index(static_cast<int>(layers.size()), current_index);
    if (!current_status.ok()) {
        return pp::foundation::Result<DocumentLayerPanelView>::failure(current_status);
    }

    DocumentLayerPanelView view;
    view.current_index = current_index;
    view.layers.reserve(layers.size());

    for (const auto& input : layers) {
        const auto index_status = validate_layer_index(static_cast<int>(layers.size()), input.layer_index);
        if (!index_status.ok()) {
            return pp::foundation::Result<DocumentLayerPanelView>::failure(index_status);
        }

        if (!std::isfinite(input.opacity) || input.opacity < 0.0F || input.opacity > 1.0F) {
            return pp::foundation::Result<DocumentLayerPanelView>::failure(
                pp::foundation::Status::out_of_range("layer opacity must be finite and within 0..1"));
        }

        if (input.blend_mode < 0 || input.blend_mode >= document_layer_legacy_blend_mode_count) {
            return pp::foundation::Result<DocumentLayerPanelView>::failure(
                pp::foundation::Status::out_of_range("layer blend mode is outside the supported range"));
        }

        const bool current = input.layer_index == current_index;
        DocumentLayerPanelLayerView layer;
        layer.layer_index = input.layer_index;
        layer.name = input.name;
        layer.opacity = input.opacity;
        layer.visible = input.visible;
        layer.alpha_locked = input.alpha_locked;
        layer.blend_mode = input.blend_mode;
        layer.current = current;
        if (current) {
            view.current_opacity = input.opacity;
            view.current_alpha_locked = input.alpha_locked;
            view.current_blend_mode = input.blend_mode;
        }
        view.layers.push_back(std::move(layer));
    }

    return pp::foundation::Result<DocumentLayerPanelView>::success(std::move(view));
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerRenamePlan> plan_document_layer_rename(
    std::string_view old_name,
    std::string_view requested_name)
{
    if (requested_name.empty()) {
        return pp::foundation::Result<DocumentLayerRenamePlan>::failure(
            pp::foundation::Status::invalid_argument("layer name must not be empty"));
    }

    if (requested_name.size() > document_layer_name_max_length) {
        return pp::foundation::Result<DocumentLayerRenamePlan>::failure(
            pp::foundation::Status::out_of_range("layer name length exceeds the configured limit"));
    }

    DocumentLayerRenamePlan plan;
    plan.old_name = std::string(old_name);
    plan.new_name = std::string(requested_name);
    plan.action = old_name == requested_name
        ? DocumentLayerRenameAction::no_op_same_name
        : DocumentLayerRenameAction::rename_and_record_undo;
    return pp::foundation::Result<DocumentLayerRenamePlan>::success(std::move(plan));
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_add(
    int layer_count,
    int insert_index,
    std::string_view name)
{
    const auto index_status = validate_layer_insert_index(layer_count, insert_index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
    }

    const auto rename = plan_document_layer_rename({}, name);
    if (!rename) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(rename.status());
    }

    DocumentLayerOperationPlan plan;
    plan.operation = DocumentLayerOperation::add;
    plan.insert_index = insert_index;
    plan.name = std::string(name);
    plan.mutates_document = true;
    plan.marks_unsaved = true;
    plan.reloads_animation_layers = true;
    plan.updates_title = true;
    return pp::foundation::Result<DocumentLayerOperationPlan>::success(std::move(plan));
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_duplicate(
    int layer_count,
    int source_index)
{
    const auto index_status = validate_layer_index(layer_count, source_index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
    }

    DocumentLayerOperationPlan plan;
    plan.operation = DocumentLayerOperation::duplicate;
    plan.source_index = source_index;
    plan.insert_index = source_index + 1;
    plan.mutates_document = true;
    plan.marks_unsaved = true;
    plan.reloads_animation_layers = true;
    plan.updates_title = true;
    return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_select(
    int layer_count,
    int index)
{
    const auto index_status = validate_layer_index(layer_count, index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
    }

    DocumentLayerOperationPlan plan;
    plan.operation = DocumentLayerOperation::select;
    plan.index = index;
    plan.reloads_animation_layers = true;
    return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_reorder(
    int layer_count,
    int from_index,
    int to_index)
{
    auto index_status = validate_layer_index(layer_count, from_index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
    }

    index_status = validate_layer_index(layer_count, to_index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
    }

    DocumentLayerOperationPlan plan;
    plan.operation = DocumentLayerOperation::reorder;
    plan.from_index = from_index;
    plan.to_index = to_index;
    plan.mutates_document = from_index != to_index;
    plan.marks_unsaved = plan.mutates_document;
    plan.reloads_animation_layers = plan.mutates_document;
    plan.updates_title = plan.mutates_document;
    return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_remove(
    int layer_count,
    int index)
{
    const auto index_status = validate_layer_index(layer_count, index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
    }

    if (layer_count <= 1) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(
            pp::foundation::Status::invalid_argument("document must keep at least one layer"));
    }

    DocumentLayerOperationPlan plan;
    plan.operation = DocumentLayerOperation::remove;
    plan.index = index;
    plan.mutates_document = true;
    plan.marks_unsaved = true;
    plan.reloads_animation_layers = true;
    plan.updates_title = true;
    return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_opacity(
    int layer_count,
    int index,
    float opacity)
{
    const auto index_status = validate_layer_index(layer_count, index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
    }

    if (!std::isfinite(opacity) || opacity < 0.0F || opacity > 1.0F) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(
            pp::foundation::Status::out_of_range("layer opacity must be finite and within 0..1"));
    }

    DocumentLayerOperationPlan plan;
    plan.operation = DocumentLayerOperation::set_opacity;
    plan.index = index;
    plan.opacity = opacity;
    plan.mutates_document = true;
    plan.marks_unsaved = true;
    plan.updates_title = true;
    return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_visibility(
    int layer_count,
    int index,
    bool visible)
{
    const auto index_status = validate_layer_index(layer_count, index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
    }

    DocumentLayerOperationPlan plan;
    plan.operation = DocumentLayerOperation::set_visibility;
    plan.index = index;
    plan.flag = visible;
    plan.mutates_document = true;
    plan.marks_unsaved = true;
    plan.reloads_animation_layers = true;
    plan.updates_title = true;
    return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_alpha_lock(
    int layer_count,
    int index,
    bool locked)
{
    const auto index_status = validate_layer_index(layer_count, index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
    }

    DocumentLayerOperationPlan plan;
    plan.operation = DocumentLayerOperation::set_alpha_lock;
    plan.index = index;
    plan.flag = locked;
    plan.mutates_document = true;
    plan.marks_unsaved = true;
    plan.updates_title = true;
    return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_blend_mode(
    int layer_count,
    int index,
    int blend_mode)
{
    const auto index_status = validate_layer_index(layer_count, index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
    }

    if (blend_mode < 0 || blend_mode >= document_layer_legacy_blend_mode_count) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(
            pp::foundation::Status::out_of_range("layer blend mode is outside the supported range"));
    }

    DocumentLayerOperationPlan plan;
    plan.operation = DocumentLayerOperation::set_blend_mode;
    plan.index = index;
    plan.blend_mode = blend_mode;
    plan.mutates_document = true;
    plan.marks_unsaved = true;
    plan.updates_title = true;
    return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_highlight(
    int layer_count,
    int index,
    bool highlight)
{
    const auto index_status = validate_layer_index(layer_count, index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
    }

    DocumentLayerOperationPlan plan;
    plan.operation = DocumentLayerOperation::set_highlight;
    plan.index = index;
    plan.flag = highlight;
    return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerMenuPlan> plan_document_layer_menu(
    DocumentLayerMenuCommand command,
    bool has_current_layer,
    int current_index,
    int animation_duration,
    std::string_view current_layer_name,
    std::string_view lower_layer_name)
{
    if (current_index < 0) {
        return pp::foundation::Result<DocumentLayerMenuPlan>::failure(
            pp::foundation::Status::out_of_range("current layer index must not be negative"));
    }
    if (animation_duration < 0) {
        return pp::foundation::Result<DocumentLayerMenuPlan>::failure(
            pp::foundation::Status::out_of_range("animation duration must not be negative"));
    }

    DocumentLayerMenuPlan plan;
    plan.command = command;
    plan.from_index = current_index;
    plan.to_index = current_index > 0 ? current_index - 1 : 0;

    switch (command) {
    case DocumentLayerMenuCommand::clear:
        plan.action = has_current_layer
            ? DocumentLayerMenuAction::clear_current_layer
            : DocumentLayerMenuAction::no_op_select_layer;
        plan.label = has_current_layer
            ? "Clear Layer " + std::string(current_layer_name)
            : "Clear Layer (Select a layer)";
        break;
    case DocumentLayerMenuCommand::rename:
        plan.action = has_current_layer
            ? DocumentLayerMenuAction::show_rename_dialog
            : DocumentLayerMenuAction::no_op_select_layer;
        plan.label = has_current_layer
            ? "Rename Layer " + std::string(current_layer_name)
            : "Rename Layer (Select a layer)";
        break;
    case DocumentLayerMenuCommand::merge_down:
        if (!has_current_layer) {
            plan.action = DocumentLayerMenuAction::no_op_select_layer;
            plan.label = "Merge Layer (Select a layer)";
        } else if (animation_duration > 1) {
            plan.action = DocumentLayerMenuAction::show_merge_animated_not_supported;
            plan.label = "Merge Layer (Animation not supported)";
        } else if (current_index <= 0) {
            plan.action = DocumentLayerMenuAction::no_op_select_upper_layer;
            plan.label = "Merge Layer (Select upper layers)";
        } else {
            plan.action = DocumentLayerMenuAction::merge_with_lower_layer;
            plan.label = "Merge with " + std::string(lower_layer_name);
        }
        break;
    }

    return pp::foundation::Result<DocumentLayerMenuPlan>::success(std::move(plan));
}

[[nodiscard]] inline pp::foundation::Result<DocumentLayerMergePlan> plan_document_layer_merge(
    int layer_count,
    int from_index,
    int to_index,
    int animation_duration,
    bool create_history = true)
{
    auto index_status = validate_layer_index(layer_count, from_index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerMergePlan>::failure(index_status);
    }

    index_status = validate_layer_index(layer_count, to_index);
    if (!index_status.ok()) {
        return pp::foundation::Result<DocumentLayerMergePlan>::failure(index_status);
    }

    if (animation_duration < 0) {
        return pp::foundation::Result<DocumentLayerMergePlan>::failure(
            pp::foundation::Status::out_of_range("animation duration must not be negative"));
    }

    if (animation_duration > 1) {
        return pp::foundation::Result<DocumentLayerMergePlan>::failure(
            pp::foundation::Status::invalid_argument("animated layer merge is not supported"));
    }

    if (from_index <= to_index) {
        return pp::foundation::Result<DocumentLayerMergePlan>::failure(
            pp::foundation::Status::invalid_argument("layer merge source must be above the destination"));
    }

    DocumentLayerMergePlan plan;
    plan.from_index = from_index;
    plan.to_index = to_index;
    plan.create_history = create_history;
    return pp::foundation::Result<DocumentLayerMergePlan>::success(plan);
}

[[nodiscard]] inline pp::foundation::Status execute_document_layer_rename_plan(
    const DocumentLayerRenamePlan& plan,
    DocumentLayerRenameServices& services)
{
    switch (plan.action) {
    case DocumentLayerRenameAction::no_op_same_name:
        services.finish_layer_rename();
        return pp::foundation::Status::success();
    case DocumentLayerRenameAction::rename_and_record_undo:
        if (plan.new_name.empty()) {
            return pp::foundation::Status::invalid_argument("layer rename plan must include a new name");
        }
        if (plan.old_name == plan.new_name) {
            return pp::foundation::Status::invalid_argument("layer rename plan must change the name");
        }
        if (!document_layer_rename_records_history(plan)) {
            return pp::foundation::Status::invalid_argument(
                "layer rename plan must record history when the name changes");
        }
        services.record_layer_rename_undo(plan.old_name, plan.new_name);
        services.set_current_layer_name(plan.new_name);
        services.finish_layer_rename();
        return pp::foundation::Status::success();
    }

    return pp::foundation::Status::invalid_argument("unknown document layer rename action");
}

inline void execute_document_layer_operation_side_effects(
    const DocumentLayerOperationPlan& plan,
    DocumentLayerOperationServices& services)
{
    if (plan.marks_unsaved)
        services.mark_unsaved();
    if (plan.reloads_animation_layers)
        services.reload_animation_layers();
    if (plan.updates_title)
        services.update_title();
}

[[nodiscard]] inline pp::foundation::Status execute_document_layer_operation_plan(
    const DocumentLayerOperationPlan& plan,
    DocumentLayerOperationServices& services)
{
    switch (plan.operation) {
    case DocumentLayerOperation::add:
        if (!plan.mutates_document || plan.name.empty()) {
            return pp::foundation::Status::invalid_argument("layer add plan must mutate with a name");
        }
        services.add_layer(plan.name, plan.insert_index);
        break;
    case DocumentLayerOperation::duplicate:
        if (!plan.mutates_document) {
            return pp::foundation::Status::invalid_argument("layer duplicate plan must mutate the document");
        }
        services.duplicate_layer(plan.source_index, plan.insert_index);
        break;
    case DocumentLayerOperation::select:
        services.select_layer(plan.index);
        break;
    case DocumentLayerOperation::reorder:
        if (plan.mutates_document)
            services.reorder_layer(plan.from_index, plan.to_index);
        break;
    case DocumentLayerOperation::remove:
        if (!plan.mutates_document) {
            return pp::foundation::Status::invalid_argument("layer remove plan must mutate the document");
        }
        services.remove_layer(plan.index);
        break;
    case DocumentLayerOperation::set_opacity:
        if (!plan.mutates_document) {
            return pp::foundation::Status::invalid_argument("layer opacity plan must mutate the document");
        }
        services.set_layer_opacity(plan.index, plan.opacity);
        break;
    case DocumentLayerOperation::set_visibility:
        if (!plan.mutates_document) {
            return pp::foundation::Status::invalid_argument("layer visibility plan must mutate the document");
        }
        services.set_layer_visibility(plan.index, plan.flag);
        break;
    case DocumentLayerOperation::set_alpha_lock:
        if (!plan.mutates_document) {
            return pp::foundation::Status::invalid_argument("layer alpha-lock plan must mutate the document");
        }
        services.set_layer_alpha_lock(plan.index, plan.flag);
        break;
    case DocumentLayerOperation::set_blend_mode:
        if (!plan.mutates_document) {
            return pp::foundation::Status::invalid_argument("layer blend-mode plan must mutate the document");
        }
        services.set_layer_blend_mode(plan.index, plan.blend_mode);
        break;
    case DocumentLayerOperation::set_highlight:
        services.set_layer_highlight(plan.index, plan.flag);
        break;
    }

    execute_document_layer_operation_side_effects(plan, services);
    return pp::foundation::Status::success();
}

[[nodiscard]] inline pp::foundation::Status execute_document_layer_merge_plan(
    const DocumentLayerMergePlan& plan,
    DocumentLayerMergeServices& services)
{
    if (plan.from_index <= plan.to_index) {
        return pp::foundation::Status::invalid_argument(
            "layer merge source must be above the destination");
    }

    services.merge_layers(plan.from_index, plan.to_index, plan.create_history);
    return pp::foundation::Status::success();
}

[[nodiscard]] inline pp::foundation::Status execute_document_layer_menu_plan(
    const DocumentLayerMenuPlan& plan,
    DocumentLayerMenuServices& services)
{
    switch (plan.action) {
    case DocumentLayerMenuAction::clear_current_layer:
        services.clear_current_layer();
        return pp::foundation::Status::success();
    case DocumentLayerMenuAction::show_rename_dialog:
        services.show_rename_dialog();
        return pp::foundation::Status::success();
    case DocumentLayerMenuAction::merge_with_lower_layer:
        services.merge_with_lower_layer(plan.from_index, plan.to_index);
        return pp::foundation::Status::success();
    case DocumentLayerMenuAction::show_merge_animated_not_supported:
        services.show_merge_animated_not_supported();
        return pp::foundation::Status::success();
    case DocumentLayerMenuAction::no_op_select_layer:
    case DocumentLayerMenuAction::no_op_select_upper_layer:
        return pp::foundation::Status::success();
    }

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

}
