#pragma once

#include "foundation/result.h"

#include <array>
#include <cmath>
#include <string_view>

namespace pp::app {

enum class CanvasToolOperation {
    select_mode,
    toggle_picking,
    toggle_touch_lock,
};

enum class CanvasToolMode {
    draw,
    erase,
    line,
    camera,
    grid,
    copy,
    cut,
    fill,
    mask_free,
    mask_line,
    flood_fill,
};

enum class CanvasToolTransformAction {
    none,
    copy,
    cut,
};

enum class CanvasToolToolbarAction {
    select_mode,
    toggle_picking,
    toggle_touch_lock,
};

enum class CanvasCursorVisibilityMode {
    never,
    small_brush,
    not_painting,
    always,
};

struct CanvasToolPlan {
    CanvasToolOperation operation = CanvasToolOperation::select_mode;
    CanvasToolMode mode = CanvasToolMode::draw;
    CanvasToolTransformAction transform_action = CanvasToolTransformAction::none;
    bool selects_toolbar_button = false;
    bool updates_canvas_mode = false;
    bool toggles_picking = false;
    bool toggles_touch_lock = false;
    bool requires_draw_mode = false;
    bool no_op = false;
};

struct CanvasToolButtonState {
    CanvasToolMode mode = CanvasToolMode::draw;
    bool pick_active = false;
    bool touch_lock_active = false;
    bool pen_active = false;
    bool erase_active = false;
    bool line_active = false;
    bool camera_active = false;
    bool grid_active = false;
    bool copy_active = false;
    bool cut_active = false;
    bool fill_active = false;
    bool mask_free_active = false;
    bool mask_line_active = false;
    bool flood_fill_active = false;
};

struct CanvasToolToolbarBinding {
    std::string_view button_id;
    CanvasToolToolbarAction action = CanvasToolToolbarAction::select_mode;
    CanvasToolMode mode = CanvasToolMode::draw;
    bool custom_button = true;
    bool applies_default_on_init = false;
};

struct CanvasToolToolbarPlan {
    std::array<CanvasToolToolbarBinding, 13> bindings {};
    CanvasToolMode default_mode = CanvasToolMode::draw;
};

struct CanvasCursorVisibilityInput {
    CanvasToolMode mode = CanvasToolMode::draw;
    CanvasCursorVisibilityMode visibility_mode = CanvasCursorVisibilityMode::never;
    bool has_current_brush = true;
    float brush_tip_size = 0.0F;
    bool pen_is_drawing = false;
    bool alt_down = false;
    bool pen_is_resizing = false;
    bool pen_is_picking = false;
};

struct CanvasCursorVisibilityPlan {
    bool visible = true;
    bool paint_mode = false;
    bool uses_brush_size = false;
    bool uses_pen_state = false;
    bool forced_visible_by_modifier_or_tool = false;
};

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

    virtual void select_toolbar_button(CanvasToolMode mode) = 0;
    virtual void set_transform_action(CanvasToolTransformAction action) = 0;
    virtual void set_canvas_mode(CanvasToolMode mode) = 0;
    virtual void toggle_picking() = 0;
    virtual void toggle_touch_lock() = 0;
};

[[nodiscard]] inline constexpr CanvasToolTransformAction transform_action_for_mode(CanvasToolMode mode) noexcept
{
    if (mode == CanvasToolMode::copy) {
        return CanvasToolTransformAction::copy;
    }
    if (mode == CanvasToolMode::cut) {
        return CanvasToolTransformAction::cut;
    }
    return CanvasToolTransformAction::none;
}

[[nodiscard]] inline constexpr CanvasToolPlan plan_canvas_tool_select(CanvasToolMode mode) noexcept
{
    CanvasToolPlan plan;
    plan.operation = CanvasToolOperation::select_mode;
    plan.mode = mode;
    plan.transform_action = transform_action_for_mode(mode);
    plan.selects_toolbar_button = true;
    plan.updates_canvas_mode = true;
    return plan;
}

[[nodiscard]] inline constexpr CanvasToolPlan plan_canvas_tool_pick_toggle(bool current_mode_is_draw) noexcept
{
    CanvasToolPlan plan;
    plan.operation = CanvasToolOperation::toggle_picking;
    plan.mode = CanvasToolMode::draw;
    plan.requires_draw_mode = true;
    plan.toggles_picking = current_mode_is_draw;
    plan.no_op = !current_mode_is_draw;
    return plan;
}

[[nodiscard]] inline constexpr CanvasToolPlan plan_canvas_tool_touch_lock_toggle() noexcept
{
    CanvasToolPlan plan;
    plan.operation = CanvasToolOperation::toggle_touch_lock;
    plan.toggles_touch_lock = true;
    return plan;
}

[[nodiscard]] inline constexpr CanvasToolToolbarPlan plan_canvas_tool_toolbar() noexcept
{
    return {
        std::array<CanvasToolToolbarBinding, 13> {
            CanvasToolToolbarBinding { "btn-pen", CanvasToolToolbarAction::select_mode, CanvasToolMode::draw, true, true },
            CanvasToolToolbarBinding { "btn-pick", CanvasToolToolbarAction::toggle_picking, CanvasToolMode::draw, true, false },
            CanvasToolToolbarBinding { "btn-touchlock", CanvasToolToolbarAction::toggle_touch_lock, CanvasToolMode::draw, true, false },
            CanvasToolToolbarBinding { "btn-erase", CanvasToolToolbarAction::select_mode, CanvasToolMode::erase, true, false },
            CanvasToolToolbarBinding { "btn-line", CanvasToolToolbarAction::select_mode, CanvasToolMode::line, true, false },
            CanvasToolToolbarBinding { "btn-cam", CanvasToolToolbarAction::select_mode, CanvasToolMode::camera, false, false },
            CanvasToolToolbarBinding { "btn-grid", CanvasToolToolbarAction::select_mode, CanvasToolMode::grid, false, false },
            CanvasToolToolbarBinding { "btn-copy", CanvasToolToolbarAction::select_mode, CanvasToolMode::copy, false, false },
            CanvasToolToolbarBinding { "btn-cut", CanvasToolToolbarAction::select_mode, CanvasToolMode::cut, false, false },
            CanvasToolToolbarBinding { "btn-fill", CanvasToolToolbarAction::select_mode, CanvasToolMode::fill, false, false },
            CanvasToolToolbarBinding { "btn-mask-free", CanvasToolToolbarAction::select_mode, CanvasToolMode::mask_free, true, false },
            CanvasToolToolbarBinding { "btn-mask-line", CanvasToolToolbarAction::select_mode, CanvasToolMode::mask_line, true, false },
            CanvasToolToolbarBinding { "btn-bucket", CanvasToolToolbarAction::select_mode, CanvasToolMode::flood_fill, true, false },
        },
        CanvasToolMode::draw,
    };
}

[[nodiscard]] inline constexpr CanvasToolPlan plan_canvas_tool_toolbar_binding_action(
    const CanvasToolToolbarBinding& binding,
    bool current_mode_is_draw) noexcept
{
    switch (binding.action) {
    case CanvasToolToolbarAction::select_mode:
        return plan_canvas_tool_select(binding.mode);
    case CanvasToolToolbarAction::toggle_picking:
        return plan_canvas_tool_pick_toggle(current_mode_is_draw);
    case CanvasToolToolbarAction::toggle_touch_lock:
        return plan_canvas_tool_touch_lock_toggle();
    }

    return plan_canvas_tool_select(CanvasToolMode::draw);
}

[[nodiscard]] inline constexpr CanvasToolButtonState plan_canvas_tool_button_state(
    CanvasToolMode mode,
    bool picking,
    bool touch_lock) noexcept
{
    CanvasToolButtonState state;
    state.mode = mode;
    state.pick_active = mode == CanvasToolMode::draw && picking;
    state.touch_lock_active = touch_lock;
    state.pen_active = mode == CanvasToolMode::draw;
    state.erase_active = mode == CanvasToolMode::erase;
    state.line_active = mode == CanvasToolMode::line;
    state.camera_active = mode == CanvasToolMode::camera;
    state.grid_active = mode == CanvasToolMode::grid;
    state.copy_active = mode == CanvasToolMode::copy;
    state.cut_active = mode == CanvasToolMode::cut;
    state.fill_active = mode == CanvasToolMode::fill;
    state.mask_free_active = mode == CanvasToolMode::mask_free;
    state.mask_line_active = mode == CanvasToolMode::mask_line;
    state.flood_fill_active = mode == CanvasToolMode::flood_fill;
    return state;
}

[[nodiscard]] inline constexpr bool canvas_tool_mode_is_paint(CanvasToolMode mode) noexcept
{
    return mode == CanvasToolMode::draw || mode == CanvasToolMode::erase;
}

[[nodiscard]] inline pp::foundation::Result<CanvasCursorVisibilityPlan> plan_canvas_cursor_visibility(
    const CanvasCursorVisibilityInput& input)
{
    CanvasCursorVisibilityPlan plan;
    plan.paint_mode = canvas_tool_mode_is_paint(input.mode);
    if (!plan.paint_mode) {
        plan.visible = true;
        return pp::foundation::Result<CanvasCursorVisibilityPlan>::success(plan);
    }

    switch (input.visibility_mode) {
    case CanvasCursorVisibilityMode::always:
        plan.visible = true;
        break;
    case CanvasCursorVisibilityMode::never:
        plan.visible = false;
        break;
    case CanvasCursorVisibilityMode::small_brush:
        if (!input.has_current_brush) {
            return pp::foundation::Result<CanvasCursorVisibilityPlan>::failure(
                pp::foundation::Status::invalid_argument("canvas cursor small-brush mode requires a current brush"));
        }
        if (!std::isfinite(input.brush_tip_size) || input.brush_tip_size < 0.0F) {
            return pp::foundation::Result<CanvasCursorVisibilityPlan>::failure(
                pp::foundation::Status::invalid_argument("canvas cursor brush size must be finite and non-negative"));
        }
        plan.visible = input.brush_tip_size < 10.0F;
        plan.uses_brush_size = true;
        break;
    case CanvasCursorVisibilityMode::not_painting:
        plan.visible = !input.pen_is_drawing;
        plan.uses_pen_state = true;
        break;
    }

    if (input.alt_down || input.pen_is_resizing || input.pen_is_picking) {
        plan.visible = true;
        plan.forced_visible_by_modifier_or_tool = true;
    }

    return pp::foundation::Result<CanvasCursorVisibilityPlan>::success(plan);
}

[[nodiscard]] inline pp::foundation::Status execute_canvas_tool_plan(
    const CanvasToolPlan& plan,
    CanvasToolServices& services)
{
    switch (plan.operation) {
    case CanvasToolOperation::select_mode:
        if (!plan.selects_toolbar_button || !plan.updates_canvas_mode) {
            return pp::foundation::Status::invalid_argument("canvas tool select plan must select toolbar and update mode");
        }
        if (plan.transform_action != transform_action_for_mode(plan.mode)) {
            return pp::foundation::Status::invalid_argument("canvas tool select plan has mismatched transform action");
        }
        services.select_toolbar_button(plan.mode);
        if (plan.transform_action != CanvasToolTransformAction::none) {
            services.set_transform_action(plan.transform_action);
        }
        services.set_canvas_mode(plan.mode);
        return pp::foundation::Status::success();

    case CanvasToolOperation::toggle_picking:
        if (!plan.requires_draw_mode) {
            return pp::foundation::Status::invalid_argument("canvas pick plan must require draw mode");
        }
        if (plan.no_op) {
            return pp::foundation::Status::success();
        }
        if (!plan.toggles_picking) {
            return pp::foundation::Status::invalid_argument("canvas pick plan must toggle picking or be a no-op");
        }
        services.toggle_picking();
        return pp::foundation::Status::success();

    case CanvasToolOperation::toggle_touch_lock:
        if (!plan.toggles_touch_lock || plan.no_op) {
            return pp::foundation::Status::invalid_argument("canvas touch-lock plan must toggle touch lock");
        }
        services.toggle_touch_lock();
        return pp::foundation::Status::success();
    }

    return pp::foundation::Status::invalid_argument("unknown canvas tool operation");
}

} // namespace pp::app
