#pragma once

#include "foundation/result.h"

#include <cmath>
#include <string>
#include <string_view>

namespace pp::app {

enum class BrushUiTextureSlot {
    tip,
    pattern,
    dual,
};

enum class BrushUiOperation {
    set_tip_color,
    set_texture,
    replace_brush_from_preset,
    stroke_settings_changed,
};

struct BrushUiPlan {
    BrushUiOperation operation = BrushUiOperation::stroke_settings_changed;
    BrushUiTextureSlot texture_slot = BrushUiTextureSlot::tip;
    std::string path;
    std::string thumbnail_path;
    float r = 0.0F;
    float g = 0.0F;
    float b = 0.0F;
    float a = 1.0F;
    bool mutates_brush = false;
    bool preserves_existing_color = false;
    bool loads_brush_resources = false;
    bool update_color_ui = false;
    bool update_brush_ui = false;
};

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

    virtual void set_tip_color(float r, float g, float b, float a) = 0;
    virtual void set_texture(BrushUiTextureSlot slot, std::string_view path, std::string_view thumbnail_path) = 0;
    virtual void replace_brush_from_preset(bool preserve_existing_color, bool load_resources) = 0;
    virtual void refresh_brush_ui(bool update_color_ui, bool update_brush_ui) = 0;
};

[[nodiscard]] inline pp::foundation::Status validate_brush_ui_color_channel(float value) noexcept
{
    if (!std::isfinite(value) || value < 0.0F || value > 1.0F) {
        return pp::foundation::Status::out_of_range("brush color channels must be finite and within 0..1");
    }

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

[[nodiscard]] inline pp::foundation::Result<BrushUiPlan> plan_brush_ui_color(
    float r,
    float g,
    float b,
    float a)
{
    for (const auto value : { r, g, b, a }) {
        const auto channel_status = validate_brush_ui_color_channel(value);
        if (!channel_status.ok()) {
            return pp::foundation::Result<BrushUiPlan>::failure(channel_status);
        }
    }

    BrushUiPlan plan;
    plan.operation = BrushUiOperation::set_tip_color;
    plan.r = r;
    plan.g = g;
    plan.b = b;
    plan.a = a;
    plan.mutates_brush = true;
    plan.update_color_ui = true;
    return pp::foundation::Result<BrushUiPlan>::success(plan);
}

[[nodiscard]] inline pp::foundation::Result<BrushUiPlan> plan_brush_ui_texture(
    BrushUiTextureSlot slot,
    std::string_view path,
    std::string_view thumbnail_path)
{
    if (path.empty()) {
        return pp::foundation::Result<BrushUiPlan>::failure(
            pp::foundation::Status::invalid_argument("brush texture path must not be empty"));
    }

    BrushUiPlan plan;
    plan.operation = BrushUiOperation::set_texture;
    plan.texture_slot = slot;
    plan.path = std::string(path);
    plan.thumbnail_path = std::string(thumbnail_path);
    plan.mutates_brush = true;
    plan.loads_brush_resources = true;
    plan.update_color_ui = true;
    plan.update_brush_ui = true;
    return pp::foundation::Result<BrushUiPlan>::success(std::move(plan));
}

[[nodiscard]] inline pp::foundation::Result<BrushUiPlan> plan_brush_ui_preset_replace(bool has_preset_brush)
{
    if (!has_preset_brush) {
        return pp::foundation::Result<BrushUiPlan>::failure(
            pp::foundation::Status::invalid_argument("preset brush must be available"));
    }

    BrushUiPlan plan;
    plan.operation = BrushUiOperation::replace_brush_from_preset;
    plan.mutates_brush = true;
    plan.preserves_existing_color = true;
    plan.loads_brush_resources = true;
    plan.update_color_ui = true;
    plan.update_brush_ui = true;
    return pp::foundation::Result<BrushUiPlan>::success(plan);
}

[[nodiscard]] inline constexpr BrushUiPlan plan_brush_ui_stroke_settings_changed() noexcept
{
    BrushUiPlan plan;
    plan.operation = BrushUiOperation::stroke_settings_changed;
    plan.mutates_brush = true;
    plan.update_color_ui = true;
    plan.update_brush_ui = true;
    return plan;
}

[[nodiscard]] inline pp::foundation::Status execute_brush_ui_plan(
    const BrushUiPlan& plan,
    BrushUiServices& services)
{
    switch (plan.operation) {
    case BrushUiOperation::set_tip_color:
    {
        for (const auto value : { plan.r, plan.g, plan.b, plan.a }) {
            const auto channel_status = validate_brush_ui_color_channel(value);
            if (!channel_status.ok()) {
                return channel_status;
            }
        }
        services.set_tip_color(plan.r, plan.g, plan.b, plan.a);
        services.refresh_brush_ui(plan.update_color_ui, plan.update_brush_ui);
        return pp::foundation::Status::success();
    }

    case BrushUiOperation::set_texture:
        if (plan.path.empty()) {
            return pp::foundation::Status::invalid_argument("brush texture path must not be empty");
        }
        services.set_texture(plan.texture_slot, plan.path, plan.thumbnail_path);
        services.refresh_brush_ui(plan.update_color_ui, plan.update_brush_ui);
        return pp::foundation::Status::success();

    case BrushUiOperation::replace_brush_from_preset:
        services.replace_brush_from_preset(plan.preserves_existing_color, plan.loads_brush_resources);
        services.refresh_brush_ui(plan.update_color_ui, plan.update_brush_ui);
        return pp::foundation::Status::success();

    case BrushUiOperation::stroke_settings_changed:
        services.refresh_brush_ui(plan.update_color_ui, plan.update_brush_ui);
        return pp::foundation::Status::success();
    }

    return pp::foundation::Status::invalid_argument("unknown brush UI operation");
}

} // namespace pp::app
