// gpu_frame_renderer.hpp - Renderer de alto nivel basado en SDL_GPU
// © 2026 JailDesigner
//
// Flujo por frame:
//   1. beginFrame(clear_color)
//        → acquire swapchain + begin render pass sobre la textura OFFSCREEN
//          (clear a black; la swapchain se pinta después con el postpro).
//   2. pushLine(...) — encola líneas (extrusión en CPU).
//   3. endFrame()
//        → flush del batch en offscreen + pase de postpro (sample offscreen
//          → swapchain con bloom/flicker/background) + submit + presenta.
//
// La oscilación de brillo y el fondo pulsante viven ahora en el shader de
// postpro; el CPU solo le pasa los uniformes del struct PostFxParams.

#pragma once

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

#include <cstdint>
#include <vector>

#include "core/rendering/gpu/gpu_bloom_pipeline.hpp"
#include "core/rendering/gpu/gpu_device.hpp"
#include "core/rendering/gpu/gpu_line_pipeline.hpp"
#include "core/rendering/gpu/gpu_postfx_pipeline.hpp"

namespace Rendering::GPU {

    // Parámetros del postpro que el caller (SDLManager) pasa cada frame.
    // Equivalente al lado "humano" del PostFxUniforms (sin paddings).
    struct PostFxParams {
        bool bloom_enabled{true};
        float bloom_intensity{0.6F};
        float bloom_threshold{0.4F};
        float bloom_sigma_px{3.5F};  // sigma de la gaussiana en texels (separable blur)

        bool flicker_enabled{true};
        float flicker_amplitude{0.10F};
        float flicker_frequency_hz{6.0F};

        bool background_enabled{true};
        float background_min_r{0.0F};
        float background_min_g{0.02F};
        float background_min_b{0.0F};
        float background_max_r{0.0F};
        float background_max_g{0.06F};
        float background_max_b{0.0F};
        float background_pulse_freq_hz{6.0F};
    };

    class GpuFrameRenderer {
       public:
        GpuFrameRenderer() = default;
        ~GpuFrameRenderer();

        GpuFrameRenderer(const GpuFrameRenderer&) = delete;
        auto operator=(const GpuFrameRenderer&) -> GpuFrameRenderer& = delete;
        GpuFrameRenderer(GpuFrameRenderer&&) = delete;
        auto operator=(GpuFrameRenderer&&) -> GpuFrameRenderer& = delete;

        // Crea device + pipeline + offscreen + sampler.
        //   logical_w/h: tamaño en píxeles lógicos del juego (1280×720). Lo
        //                consume el shader de líneas para transformar a NDC.
        //   render_w/h:  tamaño físico del offscreen donde se rasterizan las
        //                líneas. Puede ser > logical para ganar nitidez al
        //                escalar a la swapchain (configurable vía YAML).
        [[nodiscard]] auto init(SDL_Window* window,
            float logical_w,
            float logical_h,
            float render_w,
            float render_h) -> bool;
        void destroy();

        // Recrea el offscreen con un nuevo tamaño físico de render. Solo es
        // seguro fuera de un frame (isInsideFrame() == false). Devuelve false
        // si está dentro de frame o si la creación de la textura falla.
        [[nodiscard]] auto resizeRenderTarget(float render_w, float render_h) -> bool;

        // beginFrame: adquiere swapchain, abre render pass sobre offscreen
        // con clear a negro. Devuelve false si no hay textura disponible.
        // Los argumentos clear_r/g/b se ignoran (compatibilidad de API: el
        // fondo lo dibuja el postpro).
        [[nodiscard]] auto beginFrame(float clear_r, float clear_g, float clear_b) -> bool;

        // Encola una línea con grosor configurable (px). Color RGBA en [0..1].
        void pushLine(float x1, float y1, float x2, float y2, float thickness, float r, float g, float b, float a);

        // Encola un rectàngle massís (2 triangles) amb color RGBA [0..1]. Es
        // remet pel mateix pipeline de líneas (TRIANGLELIST + alpha blend); els
        // vèrtexs es marquen amb edge_dist=0 perquè el fragment shader doni
        // alpha completa sense fade geomètric. Útil per a fons semitransparents
        // d'UI (notificacions, panels).
        void pushRect(float x, float y, float w, float h, float r, float g, float b, float a);

        // Clipping rectangular per a UI (scissor a SDL_GPU). pushClip/popClip
        // forcen un flush intermedi del batch i activen/restauren l'scissor del
        // pase actiu. Coordenades en píxels lògics del joc (1280×720); es
        // converteixen a píxels físics del offscreen automàticament. Stack
        // d'scissors per a clips niats. Quan la pila queda buida, l'scissor
        // torna a cobrir el target sencer.
        void pushClip(int logical_x, int logical_y, int logical_w, int logical_h);
        void popClip();

        // endFrame: flush del batch de líneas → composite postpro → submit + presenta.
        void endFrame();

        // Viewport del PASE FINAL (postpro → swapchain) en píxeles físicos.
        // Implementa el letterbox: cualquier área fuera del viewport queda
        // como el clear color del pase de composite (negro). Si w<=0 o h<=0,
        // se usa el tamaño completo de la ventana.
        void setViewport(float x, float y, float w, float h);

        // Activa/desactiva VSync. true = SDL_GPU_PRESENTMODE_VSYNC, false = IMMEDIATE.
        void setVSync(bool enabled);

        // Activa/desactiva l'antialias geomètric a les línies. Quan està OFF,
        // pushLine no extrudeix píxel extra ni emet edge_dist (geometria com
        // abans del commit d'AA-1). Quan està ON, l'extrusió i el fade del
        // fragment shader produeixen bords suavitzats.
        void setAntialias(bool enabled) { antialias_enabled_ = enabled; }
        [[nodiscard]] auto isAntialiasEnabled() const -> bool { return antialias_enabled_; }

        // Parámetros del postpro que se aplican en endFrame. Por defecto =
        // valores de Defaults (bloom moderado, flicker suave, fondo verde tenue).
        void setPostFx(const PostFxParams& params) { postfx_params_ = params; }
        [[nodiscard]] auto postfx() const -> const PostFxParams& { return postfx_params_; }

        // Interruptor mestre del postpro (F6). Quan està OFF, ni bloom ni
        // flicker ni background pulse afecten el frame — l'escena offscreen
        // surt tal com s'ha renderitzat (passthrough). Útil per A/B testing.
        void setPostFxEnabled(bool enabled) { postfx_enabled_ = enabled; }
        [[nodiscard]] auto isPostFxEnabled() const -> bool { return postfx_enabled_; }

        // Acceso a internals.
        [[nodiscard]] auto device() -> GpuDevice& { return device_; }
        [[nodiscard]] auto isInsideFrame() const -> bool { return cmd_buffer_ != nullptr; }

       private:
        GpuDevice device_;
        GpuLinePipeline line_pipeline_;
        GpuBloomPipeline bloom_pipeline_;
        GpuPostFxPipeline postfx_pipeline_;

        // Tamaño lógico del juego: espacio de coordenadas de las primitivas
        // (vértices, UBO del line shader). Fijo a 1280×720.
        float logical_w_{1280.0F};
        float logical_h_{720.0F};

        // Tamaño físico del offscreen (configurable). Independiente del lógico:
        // las coordenadas en NDC son agnósticas a la resolución de salida, así
        // que rasterizar a mayor render_w_/h_ da líneas más nítidas tras el
        // upscale lineal a la swapchain.
        float render_w_{1280.0F};
        float render_h_{720.0F};

        // Viewport del pase final en píxeles físicos. <0 = full window.
        float viewport_x_{0.0F};
        float viewport_y_{0.0F};
        float viewport_w_{-1.0F};
        float viewport_h_{-1.0F};

        // Offscreen color target (formato fijo R8G8B8A8_UNORM para portabilidad).
        SDL_GPUTexture* offscreen_texture_{nullptr};
        SDL_GPUTextureFormat offscreen_format_{SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM};
        SDL_GPUSampler* linear_sampler_{nullptr};

        // Bloom: dues textures intermèdies per al separable blur.
        // _a rep el resultat de la passada H (high-pass + horitzontal); _b rep
        // la V i és el bloom final que llegeix el composite. Mateixa mida que
        // l'offscreen (full-res, no downsample en aquesta versió).
        SDL_GPUTexture* bloom_texture_a_{nullptr};
        SDL_GPUTexture* bloom_texture_b_{nullptr};

        // Batch del frame en curso.
        std::vector<LineVertex> vertices_;
        std::vector<uint16_t> indices_;

        // Pila d'scissors actius en píxels físics del offscreen. Buida = sense
        // clip (full target). Cada push/pop fa un flushBatch i reaplica scissor.
        std::vector<SDL_Rect> clip_stack_;

        // Estado del frame en curso.
        SDL_GPUCommandBuffer* cmd_buffer_{nullptr};
        SDL_GPUTexture* swapchain_texture_{nullptr};
        SDL_GPURenderPass* render_pass_{nullptr};

        // Parámetros del postpro (configurables vía YAML).
        PostFxParams postfx_params_{};

        // Estat de l'antialias geomètric a les línies (toggle F5).
        bool antialias_enabled_{true};

        // Interruptor mestre del postpro (toggle F6). Quan és false, bloom +
        // flicker + background pulse queden anul·lats (passthrough de l'escena).
        bool postfx_enabled_{true};

        // Helpers internos.
        [[nodiscard]] auto createOffscreen() -> bool;
        void destroyOffscreen();
        void flushBatch();
        void bloomPass();  // pre-composite: H + V passes sobre les bloom textures
        void compositePass();
        void applyFinalViewport();
        void applyCurrentScissor();  // re-aplica el top de clip_stack_ al render_pass_
    };

}  // namespace Rendering::GPU
