#pragma once

#include <SDL3/SDL.h>

#include <memory>  // for unique_ptr
#include <string>  // for string
#include <vector>  // for vector

#include "utils/utils.h"  // for color_t

#ifndef NO_SHADERS
#include "core/rendering/shader_backend.hpp"  // for Rendering::ShaderType
namespace Rendering {
    class ShaderBackend;
}
#endif

class Text;

class Screen {
   public:
    // Constantes
    static constexpr int WINDOW_ZOOM_MIN = 1;
    static constexpr int WINDOW_ZOOM_MAX = 4;
#ifdef __EMSCRIPTEN__
    // En WASM el tamaño de ventana está fijado a 1x, así que escalamos el
    // renderizado por 3 aprovechando el modo NEAREST de la textura del juego
    // para que los píxeles salgan nítidos.
    static constexpr int WASM_RENDER_SCALE = 3;
#endif

    // Singleton API
    static void init(SDL_Window *window, SDL_Renderer *renderer);  // Crea la instancia
    static void destroy();                                         // Libera la instancia
    static auto get() -> Screen *;                                 // Obtiene el puntero a la instancia

    // Destructor (público por requisitos de `delete` desde destroy())
    ~Screen();

    // Render loop
    void clean(color_t color = {0x00, 0x00, 0x00});  // Limpia la pantalla
    void start();                                    // Prepara para empezar a dibujar en la textura de juego
    void blit();                                     // Vuelca el contenido del renderizador en pantalla

    // Video y ventana
    void setVideoMode(bool fullscreen);                     // Establece el modo de video
    void toggleVideoMode();                                 // Cambia entre pantalla completa y ventana
    void handleCanvasResized();                             // En Emscripten, reaplica setVideoMode tras un cambio del navegador (salida de fullscreen con Esc, rotación). No-op fuera de Emscripten
    void syncFullscreenFlagFromBrowser(bool isFullscreen);  // Sincroniza el flag interno de fullscreen con el estado real del navegador. Debe llamarse antes de diferir handleCanvasResized. No-op fuera de Emscripten
    void toggleIntegerScale();                              // Alterna el escalado entero
    void setIntegerScale(bool enabled);                     // Establece el escalado entero
    void toggleVSync();                                     // Alterna el V-Sync
    void setVSync(bool enabled);                            // Establece el V-Sync
    auto decWindowZoom() -> bool;                           // Reduce el zoom de la ventana (devuelve true si cambió)
    auto incWindowZoom() -> bool;                           // Aumenta el zoom de la ventana (devuelve true si cambió)
    auto setWindowZoom(int zoom) -> bool;                   // Establece el zoom de la ventana (devuelve true si cambió)

    // Borde
    void setBorderColor(color_t color);  // Cambia el color del borde

    // Notificaciones
    void initNotifications();                                                                          // Enllaça el Text de notificacions amb `Resource`. A cridar després de `Resource::init(...)`.
    void notify(const std::string &text, color_t textColor, color_t outlineColor, Uint32 durationMs);  // Muestra una notificación en la línea superior del canvas durante durationMs. Sobrescribe cualquier notificación activa (sin apilación).
    void clearNotification();                                                                          // Limpia la notificación actual

    // GPU / shaders (post-procesado). En builds con NO_SHADERS (Emscripten) son no-op.
    void initShaders();                     // Crea el backend GPU si no existe y lo inicializa
    void shutdownShaders();                 // Libera el backend GPU
    auto isGpuAccelerated() const -> bool;  // true si el backend existe y reporta hardware OK
    void setShaderEnabled(bool enabled);    // Activa o desactiva el post-procesado (persiste)
    void toggleShaderEnabled();             // Alterna post-procesado
    auto isShaderEnabled() const -> bool;   // Estado actual (lee options)
#ifndef NO_SHADERS
    void setActiveShader(Rendering::ShaderType type);  // POSTFX o CRTPI
    auto getActiveShader() const -> Rendering::ShaderType;
#endif
    void toggleActiveShader();  // Alterna POSTFX ↔ CRTPI

    // Presets de shaders (PostFX/CrtPi). Operen sobre el shader actiu.
    // Retornen false si GPU off / shaders off / llista buida (igual que a aee_plus).
    auto nextPreset() -> bool;
    auto prevPreset() -> bool;
    auto getCurrentPresetName() const -> const char *;
    void applyCurrentPostFXPreset();  // Escriu el preset PostFX actiu al backend
    void applyCurrentCrtPiPreset();   // Escriu el preset CrtPi actiu al backend

   private:
    // Constructor privado (usar Screen::init)
    Screen(SDL_Window *window, SDL_Renderer *renderer);

    // Instancia única
    static Screen *instance;

    // Helpers internos de setVideoMode
    void applyFullscreen(bool fullscreen);           // SDL_SetWindowFullscreen + visibilidad del cursor
    void applyWindowedLayout();                      // Calcula windowWidth/Height/dest + SDL_SetWindowSize + SDL_SetWindowPosition
    void applyFullscreenLayout();                    // SDL_GetWindowSize + delegación a computeFullscreenGameRect
    void computeFullscreenGameRect();                // Calcula dest en fullscreen (integerScale / keepAspect / stretched)
    void applyLogicalPresentation(bool fullscreen);  // SDL_SetRenderLogicalPresentation + persistencia a options

    // Emscripten
    void registerEmscriptenEventCallbacks();  // Registra los callbacks nativos de Emscripten para fullscreenchange y orientationchange. No-op fuera de Emscripten

    // Notificaciones
    void renderNotification();  // Dibuja la notificación activa (si la hay) sobre el gameCanvas

#ifndef NO_SHADERS
    // Aplica els paràmetres actuals del shader al backend segons options
    // (pass-through si `videoShaderEnabled==false`, preset per defecte si true).
    void applyShaderParams();
#endif

    // Objetos y punteros
    SDL_Window *window;       // Ventana de la aplicación
    SDL_Renderer *renderer;   // El renderizador de la ventana
    SDL_Texture *gameCanvas;  // Textura para completar la ventana de juego hasta la pantalla completa

    // Variables
    int windowWidth;       // Ancho de la pantalla o ventana
    int windowHeight;      // Alto de la pantalla o ventana
    int gameCanvasWidth;   // Resolución interna del juego. Es el ancho de la textura donde se dibuja el juego
    int gameCanvasHeight;  // Resolución interna del juego. Es el alto de la textura donde se dibuja el juego
    SDL_Rect dest;         // Coordenadas donde se va a dibujar la textura del juego sobre la pantalla o ventana
    color_t borderColor;   // Color del borde añadido a la textura de juego para rellenar la pantalla

    // Notificaciones - una sola activa, sin apilación ni animaciones
    Text *notificationText;            // Fuente 8bithud dedicada a las notificaciones
    std::string notificationMessage;   // Texto a mostrar
    color_t notificationTextColor;     // Color del texto
    color_t notificationOutlineColor;  // Color del outline
    Uint32 notificationEndTime;        // SDL_GetTicks() hasta el cual se muestra
    int notificationY;                 // Fila vertical en el canvas virtual

#ifndef NO_SHADERS
    // GPU / shaders
    std::unique_ptr<Rendering::ShaderBackend> shader_backend_;  // Backend GPU (nullptr si inactivo)
    std::vector<Uint32> pixel_buffer_;                          // Buffer de readback del gameCanvas (ARGB8888)
#endif
};
