#pragma once

#include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h>

#include <string>
#include <utility>

#include "core/rendering/shader_backend.hpp"

// PostFX uniforms pushed to fragment stage each frame.
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
struct PostFXUniforms {
    float vignette_strength;
    float chroma_strength;
    float scanline_strength;
    float screen_height;
    float mask_strength;
    float gamma_strength;
    float curvature;
    float bleeding;
    float pixel_scale;
    float time;
    float oversample;
    float flicker;
};

// CrtPi uniforms pushed to fragment stage each frame.
// 16 fields = 64 bytes — 4 × 16-byte alignment.
struct CrtPiUniforms {
    float scanline_weight;
    float scanline_gap_brightness;
    float bloom_factor;
    float input_gamma;
    float output_gamma;
    float mask_brightness;
    float curvature_x;
    float curvature_y;
    int mask_type;
    int enable_scanlines;
    int enable_multisample;
    int enable_gamma;
    int enable_curvature;
    int enable_sharper;
    float texture_width;
    float texture_height;
};

// Downscale uniforms for Lanczos downscale fragment stage.
// 1 int + 3 floats = 16 bytes.
struct DownscaleUniforms {
    int algorithm;
    float pad0;
    float pad1;
    float pad2;
};

namespace Rendering {

    /**
     * @brief Backend de shaders usando SDL3 GPU API (Metal en macOS, Vulkan/SPIR-V en Win/Linux)
     *
     * Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
     *           → [Upscale →] PostFX/CrtPi render pass → [Lanczos downscale →] swapchain → present
     */
    class SDL3GPUShader : public ShaderBackend {
       public:
        SDL3GPUShader() = default;
        ~SDL3GPUShader() override;

        auto init(SDL_Window* window,
            SDL_Texture* texture,
            const std::string& vertex_source,
            const std::string& fragment_source) -> bool override;

        void render() override;
        void setTextureSize(float width, float height) override {}
        void cleanup() final;
        void destroy();
        [[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
        [[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }

        void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; }
        void uploadPixels(const Uint32* pixels, int width, int height) override;
        void setPostFXParams(const PostFXParams& p) override;
        void setVSync(bool vsync) override;
        void setScaleMode(bool integer_scale) override;
        void setOversample(int factor) override;

        void setLinearUpscale(bool linear) override;
        [[nodiscard]] auto isLinearUpscale() const -> bool override { return linear_upscale_; }
        void setDownscaleAlgo(int algo) override;
        [[nodiscard]] auto getDownscaleAlgo() const -> int override { return downscale_algo_; }
        [[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;

        void setActiveShader(ShaderType type) override;
        void setCrtPiParams(const CrtPiParams& p) override;
        [[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; }

       private:
        static auto createShaderMSL(SDL_GPUDevice* device,
            const char* msl_source,
            const char* entrypoint,
            SDL_GPUShaderStage stage,
            Uint32 num_samplers,
            Uint32 num_uniform_buffers) -> SDL_GPUShader*;

        static auto createShaderSPIRV(SDL_GPUDevice* device,
            const uint8_t* spv_code,
            size_t spv_size,
            const char* entrypoint,
            SDL_GPUShaderStage stage,
            Uint32 num_samplers,
            Uint32 num_uniform_buffers) -> SDL_GPUShader*;

        auto createPipeline() -> bool;
        auto createCrtPiPipeline() -> bool;
        auto reinitTexturesAndBuffer() -> bool;
        auto recreateScaledTexture(int factor) -> bool;
        static auto calcSsFactor(float zoom) -> int;
        [[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;

        SDL_Window* window_ = nullptr;
        SDL_GPUDevice* device_ = nullptr;
        SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
        SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr;
        SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr;
        SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr;
        SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr;
        SDL_GPUTexture* scene_texture_ = nullptr;
        SDL_GPUTexture* scaled_texture_ = nullptr;
        SDL_GPUTexture* postfx_texture_ = nullptr;
        SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
        SDL_GPUSampler* sampler_ = nullptr;
        SDL_GPUSampler* linear_sampler_ = nullptr;

        PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .oversample = 1.0F};
        CrtPiUniforms crtpi_uniforms_{.scanline_weight = 6.0F, .scanline_gap_brightness = 0.12F, .bloom_factor = 3.5F, .input_gamma = 2.4F, .output_gamma = 2.2F, .mask_brightness = 0.80F, .curvature_x = 0.05F, .curvature_y = 0.10F, .mask_type = 2, .enable_scanlines = 1, .enable_multisample = 1, .enable_gamma = 1};
        ShaderType active_shader_ = ShaderType::POSTFX;

        int game_width_ = 0;
        int game_height_ = 0;
        int ss_factor_ = 0;
        int oversample_ = 1;
        int downscale_algo_ = 1;
        std::string driver_name_;
        std::string preferred_driver_;
        bool is_initialized_ = false;
        bool vsync_ = true;
        bool integer_scale_ = false;
        bool linear_upscale_ = false;
    };

}  // namespace Rendering
