#pragma once

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

#include "core/rendering/shader_backend.hpp"

// PostFX uniforms pushed to fragment stage each frame.
// Must match the MSL struct and GLSL uniform block layout.
// 12 floats = 48 bytes — meets Metal/Vulkan 16-byte alignment requirement.
struct PostFXUniforms {
    float vignette_strength;  // 0 = none, ~0.8 = subtle
    float chroma_strength;    // 0 = off, ~0.2 = subtle chromatic aberration
    float scanline_strength;  // 0 = off, 1 = full
    float screen_height;      // logical height in pixels (used by bleeding effect)
    float mask_strength;      // 0 = off, 1 = full phosphor dot mask
    float gamma_strength;     // 0 = off, 1 = full gamma 2.4/2.2 correction
    float curvature;          // 0 = flat, 1 = max barrel distortion
    float bleeding;           // 0 = off, 1 = max NTSC chrominance bleeding
    float pixel_scale;        // physical pixels per logical pixel (vh / tex_height_)
    float time;               // seconds since SDL init (SDL_GetTicks() / 1000.0f)
    float oversample;         // supersampling factor (1.0 = off, 3.0 = 3×SS)
    float flicker;            // 0 = off, 1 = phosphor flicker ~50 Hz — keep struct at 48 bytes (3 × 16)
};

// CrtPi uniforms pushed to fragment stage each frame.
// Must match the MSL struct and GLSL uniform block layout.
// 14 fields (8 floats + 6 ints) + 2 floats (texture size) = 16 fields = 64 bytes — 4 × 16-byte alignment.
struct CrtPiUniforms {
    // vec4 #0
    float scanline_weight;          // Ajuste gaussiano (default 6.0)
    float scanline_gap_brightness;  // Brillo mínimo entre scanlines (default 0.12)
    float bloom_factor;             // Factor brillo zonas iluminadas (default 3.5)
    float input_gamma;              // Gamma de entrada (default 2.4)
    // vec4 #1
    float output_gamma;     // Gamma de salida (default 2.2)
    float mask_brightness;  // Brillo sub-píxeles máscara (default 0.80)
    float curvature_x;      // Distorsión barrel X (default 0.05)
    float curvature_y;      // Distorsión barrel Y (default 0.10)
    // vec4 #2
    int mask_type;           // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
    int enable_scanlines;    // 0 = off, 1 = on
    int enable_multisample;  // 0 = off, 1 = on (antialiasing analítico)
    int enable_gamma;        // 0 = off, 1 = on
    // vec4 #3
    int enable_curvature;  // 0 = off, 1 = on
    int enable_sharper;    // 0 = off, 1 = on
    float texture_width;   // Ancho del canvas en píxeles (inyectado en render)
    float texture_height;  // Alto del canvas en píxeles (inyectado en render)
};

// Downscale uniforms pushed to the Lanczos downscale fragment stage.
// 1 int + 3 floats = 16 bytes — meets Metal/Vulkan alignment.
struct DownscaleUniforms {
    int algorithm;  // 0 = Lanczos2 (ventana 2), 1 = Lanczos3 (ventana 3)
    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)
     *
     * Reemplaza el backend OpenGL para que los shaders PostFX funcionen en macOS.
     * Pipeline: Surface pixels (CPU) → SDL_GPUTransferBuffer → SDL_GPUTexture (scene)
     *           → PostFX render pass → 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;  // Libera pipeline/texturas pero mantiene el device vivo
        void destroy();        // Limpieza completa (device + swapchain); llamar solo al cerrar
        [[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
        [[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }

        // Establece el driver GPU preferido (vacío = auto). Debe llamarse antes de init().
        void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; }

        // Sube píxeles ARGB8888 desde CPU; llamado antes de render()
        void uploadPixels(const Uint32* pixels, int width, int height) override;

        // Actualiza los parámetros de intensidad de los efectos PostFX
        void setPostFXParams(const PostFXParams& p) override;

        // Activa/desactiva VSync en el swapchain
        void setVSync(bool vsync) override;

        // Selecciona el mode de presentació lògica (DISABLED/STRETCH/LETTERBOX/OVERSCAN/INTEGER)
        void setScalingMode(Options::ScalingMode mode) override;

        // Establece factor de supersampling (1 = off, 3 = 3×SS)
        void setOversample(int factor) override;

        // Selecciona algoritmo de downscale: 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3
        void setDownscaleAlgo(int algo) override;

        // Devuelve las dimensiones de la textura de supersampling (0,0 si SS desactivado)
        [[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;

        // Selecciona el shader de post-procesado activo (POSTFX o CRTPI)
        void setActiveShader(ShaderType type) override;

        // Actualiza los parámetros del shader CRT-Pi
        void setCrtPiParams(const CrtPiParams& p) override;

        // Devuelve el shader activo
        [[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; }

        // Estirament vertical 4:3 (fusionat amb l'upscale pass)
        void setStretch43(bool enabled) override;
        [[nodiscard]] auto isStretch43() const -> bool override { return stretch_4_3_; }

        // Filtre de textura global (sempre aplicat, independent de 4:3)
        void setTextureFilter(Options::TextureFilter filter) override {
            texture_filter_linear_ = (filter == Options::TextureFilter::LINEAR);
        }

        // Multiplicador de resolució interna (1 = off).
        void setInternalResolution(int multiplier) override;

       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;              // Pipeline dedicado para el shader CrtPi
        auto reinitTexturesAndBuffer() -> bool;          // Recrea scene_texture_ y upload_buffer_
        auto recreateScaledTexture(int factor) -> bool;  // Recrea scaled_texture_ para factor dado
        auto recreateInternalTexture() -> bool;          // Recrea internal_texture_ (res interna × N)
        static auto calcSsFactor(float zoom) -> int;     // Primer múltiplo de 3 >= zoom (mín 3)
        // Devuelve el mejor present mode disponible: IMMEDIATE > MAILBOX > VSYNC
        [[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;

        SDL_Window* window_ = nullptr;
        SDL_GPUDevice* device_ = nullptr;
        SDL_GPUGraphicsPipeline* pipeline_ = nullptr;                   // PostFX pass (→ swapchain o → postfx_texture_)
        SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr;             // CrtPi pass (→ swapchain directo, sin SS)
        SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr;  // PostFX → postfx_texture_ (B8G8R8A8, solo con Lanczos)
        SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr;           // Upscale pass (solo con SS)
        SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr;         // Lanczos downscale (solo con SS + algo > 0)
        SDL_GPUTexture* scene_texture_ = nullptr;                       // Canvas del joc (game_width_ × game_height_)
        SDL_GPUTexture* internal_texture_ = nullptr;                    // Resolució interna ampliada (game·N × game·N), si N>1
        SDL_GPUTexture* scaled_texture_ = nullptr;                      // Upscale target (game×factor, amb 4:3 si actiu)
        SDL_GPUTexture* postfx_texture_ = nullptr;                      // PostFX output a resolució escalada, sols amb Lanczos
        SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
        SDL_GPUSampler* sampler_ = nullptr;         // NEAREST
        SDL_GPUSampler* linear_sampler_ = nullptr;  // LINEAR

        PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 200.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;  // Shader de post-procesado activo

        int game_width_ = 0;  // Dimensions originals del canvas
        int game_height_ = 0;
        int ss_factor_ = 0;       // Factor SS activo (3, 6, 9...) o 0 si SS desactivado
        int oversample_ = 1;      // SS on/off (1 = off, >1 = on)
        int downscale_algo_ = 1;  // 0 = bilinear legacy, 1 = Lanczos2, 2 = Lanczos3
        int internal_res_ = 1;    // Multiplicador de resolució interna (1 = off)
        std::string driver_name_;
        std::string preferred_driver_;  // Driver preferido; vacío = auto (SDL elige)
        bool is_initialized_ = false;
        bool vsync_ = true;
        Options::ScalingMode scaling_mode_ = Options::ScalingMode::INTEGER;
        bool stretch_4_3_ = false;            // Estirament vertical 4:3
        bool texture_filter_linear_ = false;  // Filtre global (false=NEAREST, true=LINEAR)
    };

}  // namespace Rendering
