#pragma once

#include "foundation/result.h"
#include "paint/blend.h"
#include "renderer_api/renderer_api.h"

#include <cstdint>
#include <span>

namespace pp::paint_renderer {

enum class StrokeCompositePath : std::uint8_t {
    fixed_function_blend,
    framebuffer_fetch,
    ping_pong_textures,
};

struct LayerCompositeView {
    std::span<const pp::paint::Rgba> pixels;
    float opacity = 1.0F;
    bool visible = true;
    pp::paint::BlendMode blend_mode = pp::paint::BlendMode::normal;
};

struct StrokeCompositeRequest {
    pp::renderer::Extent2D extent {};
    pp::renderer::TextureFormat target_format = pp::renderer::TextureFormat::rgba8;
    pp::renderer::TextureUsage target_usage = pp::renderer::TextureUsage::render_target
        | pp::renderer::TextureUsage::sampled
        | pp::renderer::TextureUsage::copy_source
        | pp::renderer::TextureUsage::copy_destination;
    pp::paint::BlendMode layer_blend_mode = pp::paint::BlendMode::normal;
    pp::paint::StrokeBlendMode stroke_blend_mode = pp::paint::StrokeBlendMode::normal;
    bool dual_brush_blend = false;
    bool pattern_blend = false;
};

struct StrokeCompositePlan {
    StrokeCompositePath path = StrokeCompositePath::fixed_function_blend;
    pp::renderer::PaintFeedbackPlan feedback {};
    pp::renderer::TextureDesc target_desc {};
    std::uint64_t target_bytes = 0;
    std::uint64_t auxiliary_bytes = 0;
    std::uint64_t estimated_working_bytes = 0;
    bool complex_blend = false;
    bool reads_destination_color = false;
    bool requires_auxiliary_texture = false;
    bool requires_texture_copy = false;
    bool requires_render_target_blit = false;
    bool requires_explicit_transition = false;
};

struct CanvasBlendGateRequest {
    pp::renderer::Extent2D extent {};
    std::span<const int> layer_blend_modes;
    bool has_stroke_blend_mode = false;
    int stroke_blend_mode = 0;
    bool dual_brush_blend = false;
    bool pattern_blend = false;
};

struct CanvasBlendGatePlan {
    bool shader_blend = false;
    bool complex_blend = false;
    bool compatibility_fallback = false;
    bool stroke_complex = false;
    bool dual_brush_complex = false;
    bool pattern_complex = false;
    int first_complex_layer_index = -1;
    StrokeCompositePath path = StrokeCompositePath::fixed_function_blend;
    bool reads_destination_color = false;
    bool requires_auxiliary_texture = false;
    bool requires_texture_copy = false;
    bool requires_render_target_blit = false;
};

struct CanvasStrokeFeedbackPlan {
    StrokeCompositePath path = StrokeCompositePath::fixed_function_blend;
    bool reads_destination_color = false;
    bool requires_auxiliary_texture = false;
    bool requires_texture_copy = false;
    bool requires_render_target_blit = false;
    bool compatibility_fallback = false;
};

[[nodiscard]] pp::foundation::Status composite_layer(
    std::span<pp::paint::Rgba> destination,
    pp::renderer::Extent2D extent,
    LayerCompositeView layer) noexcept;

[[nodiscard]] bool stroke_composite_requires_feedback(
    pp::paint::BlendMode layer_blend_mode,
    pp::paint::StrokeBlendMode stroke_blend_mode,
    bool dual_brush_blend,
    bool pattern_blend) noexcept;

[[nodiscard]] pp::foundation::Result<StrokeCompositePlan> plan_stroke_composite(
    pp::renderer::RenderDeviceFeatures features,
    StrokeCompositeRequest request) noexcept;

[[nodiscard]] pp::foundation::Result<CanvasBlendGatePlan> plan_canvas_blend_gate(
    pp::renderer::RenderDeviceFeatures features,
    CanvasBlendGateRequest request) noexcept;

[[nodiscard]] pp::foundation::Result<CanvasStrokeFeedbackPlan> plan_canvas_stroke_feedback(
    pp::renderer::RenderDeviceFeatures features,
    pp::renderer::Extent2D extent) noexcept;

[[nodiscard]] const char* stroke_composite_path_name(StrokeCompositePath path) noexcept;

}
