#pragma once

#include "renderer_api/recording_renderer.h"
#include "renderer_gl/opengl_capabilities.h"

#include <cstddef>
#include <cstdint>
#include <span>
#include <vector>

namespace pp::renderer::gl {

enum class OpenGlPlannedCommandKind : std::uint8_t {
    unknown,
    begin_render_pass,
    set_viewport,
    set_scissor,
    set_blend_state,
    set_depth_state,
    bind_shader,
    set_shader_uniform,
    bind_texture,
    bind_sampler,
    bind_mesh,
    draw,
    upload_texture,
    generate_mipmaps,
    transition_texture,
    copy_texture,
    read_texture,
    capture_frame,
    blit_render_target,
    end_render_pass,
    trace,
    passthrough,
};

struct OpenGlPlannedCommand {
    OpenGlPlannedCommandKind kind = OpenGlPlannedCommandKind::unknown;
    std::uint32_t clear_mask = 0;
    OpenGlClearValues clear_values;
    OpenGlViewportRect viewport;
    OpenGlScissorRect scissor;
    OpenGlBlendState blend;
    OpenGlDepthState depth;
    OpenGlSamplerState sampler;
    OpenGlRendererTextureFormat texture_format;
    OpenGlRendererTextureFormat source_texture_format;
    OpenGlRendererTextureFormat destination_texture_format;
    OpenGlEnumMapping blit_filter;
    pp::renderer::TextureState before_state = pp::renderer::TextureState::undefined;
    pp::renderer::TextureState after_state = pp::renderer::TextureState::undefined;
    pp::renderer::ReadbackRegion readback_region;
    pp::renderer::ReadbackRegion source_region;
    pp::renderer::ReadbackRegion destination_region;
    std::uint32_t texture_slot = 0;
    std::uint32_t sampler_slot = 0;
    std::uint64_t upload_bytes = 0;
    std::uint32_t generated_mip_levels = 0;
    std::uint64_t generated_mip_bytes = 0;
    std::uint64_t copy_source_bytes = 0;
    std::uint64_t copy_destination_bytes = 0;
    std::uint64_t readback_bytes = 0;
    std::uint64_t capture_bytes = 0;
    std::uint64_t blit_source_bytes = 0;
    std::uint64_t blit_destination_bytes = 0;
    std::uint64_t uniform_bytes = 0;
    std::uint32_t primitive_mode = 0;
    std::uint32_t draw_vertex_count = 0;
    std::uint32_t draw_index_count = 0;
    const char* name = "";
    bool requires_render_pass = false;
    bool supported = false;
};

struct OpenGlCommandPlan {
    static constexpr std::size_t npos = static_cast<std::size_t>(-1);

    std::vector<OpenGlPlannedCommand> commands;
    std::uint32_t render_pass_count = 0;
    std::uint32_t draw_command_count = 0;
    std::uint32_t shader_bind_command_count = 0;
    std::uint32_t uniform_command_count = 0;
    std::uint32_t texture_bind_command_count = 0;
    std::uint32_t sampler_bind_command_count = 0;
    std::uint32_t upload_command_count = 0;
    std::uint32_t mipmap_command_count = 0;
    std::uint32_t transition_command_count = 0;
    std::uint32_t copy_command_count = 0;
    std::uint32_t readback_command_count = 0;
    std::uint32_t capture_command_count = 0;
    std::uint32_t passthrough_command_count = 0;
    std::uint32_t trace_command_count = 0;
    std::uint32_t unsupported_command_count = 0;
    std::uint32_t render_pass_order_error_count = 0;
    std::uint32_t dependency_error_count = 0;
    std::size_t first_unsupported_command = npos;
    std::size_t first_render_pass_order_error = npos;
    std::size_t first_dependency_error = npos;
    bool ended_in_render_pass = false;
    bool supported = true;
};

[[nodiscard]] const char* planned_command_kind_name(OpenGlPlannedCommandKind kind) noexcept;
[[nodiscard]] OpenGlPlannedCommand plan_recorded_render_command(
    pp::renderer::RecordedRenderCommand command) noexcept;
[[nodiscard]] OpenGlCommandPlan plan_recorded_render_commands(
    std::span<const pp::renderer::RecordedRenderCommand> commands);

}
