#pragma once

#include <SDL3/SDL.h>  // Para SDL_FRect, SDL_HideWindow, SDL_Renderer, SDL_ShowWindow, Uint32, SDL_Texture, SDL_Window

#include <memory>  // Para shared_ptr
#include <string>  // Para string
#include <vector>  // Para vector

#include "core/rendering/shader_backend.hpp"  // Para Rendering::ShaderType
#include "game/options.hpp"                   // Para VideoOptions, video
#include "utils/color.hpp"                    // Para Color

// Forward declarations
class Notifier;
class ServiceMenu;
class Text;

// --- Clase Screen: gestiona la ventana, el renderizador y los efectos visuales globales (singleton) ---
class Screen {
   public:
    // --- Métodos de singleton ---
    static void init();            // Inicializa el objeto Screen
    static void destroy();         // Libera el objeto Screen
    static auto get() -> Screen*;  // Obtiene el puntero al objeto Screen

    // --- Métodos principales ---
    void update(float delta_time);                      // Recibe deltaTime de las secciones y actualiza la lógica
    void coreUpdate();                                  // Actualiza los elementos mínimos
    void clean(Color color = Color(0x00, 0x00, 0x00));  // Limpia la pantalla
    void start();                                       // Prepara para empezar a dibujar en la textura de juego
    void render();                                      // Vuelca el contenido del renderizador en pantalla
    void coreRender();                                  // Vuelca el contenido del renderizador en pantalla exceptuando ciertas partes

    // --- Configuración de ventana y render ---
    void setFullscreenMode();      // Establece el modo de pantalla completa
    void toggleFullscreen();       // Cambia entre pantalla completa y ventana
    void handleCanvasResized();    // Restaura el canvas cuando SDL3 no reporta el cambio (emscripten only: salida de fullscreen con Esc, rotación); no-op fuera de emscripten
    void setWindowZoom(int zoom);  // Cambia el tamaño de la ventana
    auto decWindowSize() -> bool;  // Reduce el tamaño de la ventana
    auto incWindowSize() -> bool;  // Aumenta el tamaño de la ventana
    void applySettings();          // Aplica los valores de las opciones
    static void initShaders();     // Inicializa shaders (SDL3GPU)

    // --- Efectos visuales ---
    void shake(int desp = 2, float delay_s = 0.05F, float duration_s = 0.133F) { shake_effect_.enable(src_rect_, dst_rect_, desp, delay_s, duration_s); }
    void flash(Color color, float duration_s = 0.167F, float delay_s = 0.0F) { flash_effect_ = FlashEffect(true, duration_s, delay_s, color); }
    static void toggleShaders();        // Alterna activar/desactivar shaders
    static void nextShader();           // Cambia entre PostFX y CrtPi
    static void nextPostFXPreset();     // Avanza al siguiente preset PostFX
    static void nextCrtPiPreset();      // Avanza al siguiente preset CrtPi
    static void toggleSupersampling();  // Alterna supersampling
    void toggleFilter();                // Alterna SDL_SCALEMODE_NEAREST ↔ SDL_SCALEMODE_LINEAR
    void applyFilter();                 // Aplica Options::video.scale_mode a game_canvas_
    void toggleIntegerScale();
    void toggleVSync();                                        // Alterna entre activar y desactivar el V-Sync
    void setVSync(bool enabled);                               // Establece el estado del V-Sync
    void attenuate(bool value) { attenuate_effect_ = value; }  // Atenúa la pantalla

    // --- Getters ---
    auto getRenderer() -> SDL_Renderer* { return renderer_; }                      // Obtiene el renderizador
    void show() { SDL_ShowWindow(window_); }                                       // Muestra la ventana
    void hide() { SDL_HideWindow(window_); }                                       // Oculta la ventana
    void getSingletons();                                                          // Obtiene los punteros a los singletones
    [[nodiscard]] static auto getVSync() -> bool { return Options::video.vsync; }  // Obtiene el valor de V-Sync
    [[nodiscard]] static auto isHardwareAccelerated() -> bool;                     // True si SDL3GPU está activo
    [[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; }  // Obtiene el puntero al texto de Screen

    // --- Display Monitor getters ---
    [[nodiscard]] auto getDisplayMonitorName() const -> std::string { return display_monitor_.name; }
    [[nodiscard]] auto getDisplayMonitorWidth() const -> int { return display_monitor_.width; }
    [[nodiscard]] auto getDisplayMonitorHeight() const -> int { return display_monitor_.height; }
    [[nodiscard]] auto getDisplayMonitorRefreshRate() const -> int { return display_monitor_.refresh_rate; }

#ifdef _DEBUG
    // --- Debug ---
    void toggleDebugInfo() { debug_info_.show = !debug_info_.show; }
    void setDebugInfoEnabled(bool value) { debug_info_.show = value; }
#endif

   private:
    // --- Constantes ---
    static constexpr int WINDOWS_DECORATIONS = 35;  // Decoraciones de la ventana

    // --- Estructuras privadas ---
    struct DisplayMonitor {
        std::string name;
        int width;
        int height;
        int refresh_rate;
    };
    struct FPS {
        Uint32 ticks{0};     // Tiempo en milisegundos desde que se comenzó a contar.
        int frame_count{0};  // Número acumulado de frames en el intervalo.
        int last_value{0};   // Número de frames calculado en el último segundo.

        FPS() = default;
        void increment() { frame_count++; }
        auto calculate(Uint32 current_ticks) -> int {
            if (current_ticks - ticks >= 1000) {
                last_value = frame_count;
                frame_count = 0;
                ticks = current_ticks;
            }
            return last_value;
        }
    };

    // Efecto de flash en pantalla: pinta la pantalla de un color durante un tiempo
    struct FlashEffect {
        bool enabled;      // Indica si el efecto está activo
        float duration_s;  // Duración total del efecto en segundos
        float delay_s;     // Retraso antes de mostrar el flash en segundos
        float timer_s;     // Timer en segundos (contador decreciente)
        Color color;       // Color del flash

        explicit FlashEffect(bool enabled = false, float duration_s = 0.0F, float delay_s = 0.0F, Color color = Color(0xFF, 0xFF, 0xFF))
            : enabled(enabled),
              duration_s(duration_s),
              delay_s(delay_s),
              timer_s(duration_s),
              color(color) {}

        void update(float delta_time) {
            if (enabled && timer_s > 0.0F) {
                timer_s -= delta_time;
                if (timer_s <= 0.0F) {
                    enabled = false;
                }
            }
        }
        [[nodiscard]] auto isRendarable() const -> bool { return enabled && timer_s < duration_s - delay_s; }
    };

    // Efecto de sacudida/agitación de pantalla: mueve la imagen para simular un temblor
    struct ShakeEffect {
        int desp;            // Desplazamiento máximo de la sacudida (en píxeles)
        float delay_s;       // Segundos entre cada movimiento de sacudida
        float counter_s;     // Timer para el siguiente movimiento (decreciente)
        float duration_s;    // Duración total del efecto en segundos
        float remaining_s;   // Tiempo restante de sacudida
        int original_pos;    // Posición original de la imagen (x)
        int original_width;  // Ancho original de la imagen
        bool enabled;        // Indica si el efecto está activo

        explicit ShakeEffect(bool en = false, int dp = 2, float dl_s = 0.05F, float cnt_s = 0.0F, float len_s = 0.133F, float rem_s = 0.0F, int orig_pos = 0, int orig_width = 800)
            : desp(dp),
              delay_s(dl_s),
              counter_s(cnt_s),
              duration_s(len_s),
              remaining_s(rem_s),
              original_pos(orig_pos),
              original_width(orig_width),
              enabled(en) {}

        // Activa el efecto de sacudida y guarda la posición y tamaño originales
        void enable(SDL_FRect& src_rect, SDL_FRect& dst_rect, int new_desp = -1, float new_delay_s = -1.0F, float new_duration_s = -1.0F) {
            if (!enabled) {
                enabled = true;
                original_pos = src_rect.x;
                original_width = src_rect.w;

                // Usar nuevos valores si se proporcionan, sino mantener los actuales
                if (new_desp != -1) {
                    desp = new_desp;
                }
                if (new_delay_s >= 0.0F) {
                    delay_s = new_delay_s;
                }
                if (new_duration_s >= 0.0F) {
                    duration_s = new_duration_s;
                }

                src_rect.w -= desp;
                dst_rect.w = src_rect.w;
            }
            remaining_s = duration_s;
            counter_s = delay_s;
        }

        // Actualiza el estado del efecto de sacudida
        void update(SDL_FRect& src_rect, SDL_FRect& dst_rect, float delta_time) {
            if (enabled) {
                counter_s -= delta_time;
                if (counter_s <= 0.0F) {
                    counter_s = delay_s;
                    // Alternar desplazamiento basado en tiempo restante
                    const bool SHAKE_LEFT = static_cast<int>(remaining_s * 30.0F) % 2 == 0;  // ~30 cambios por segundo
                    const auto SRC_DESP = SHAKE_LEFT ? 0 : desp;
                    const auto DST_DESP = SHAKE_LEFT ? desp : 0;
                    src_rect.x = original_pos + SRC_DESP;
                    dst_rect.x = original_pos + DST_DESP;

                    remaining_s -= delay_s;
                    if (remaining_s <= 0.0F) {
                        enabled = false;
                        src_rect.x = original_pos;
                        src_rect.w = original_width;
                        dst_rect.x = original_pos;
                        dst_rect.w = original_width;
                    }
                }
            }
        }

        [[nodiscard]] auto isEnabled() const -> bool { return enabled; }
    };

#ifdef _DEBUG
    struct Debug {
        std::shared_ptr<Text> text;
        bool show = false;
    };

#endif

    // --- Objetos y punteros ---
    SDL_Window* window_;                                        // Ventana de la aplicación
    SDL_Renderer* renderer_;                                    // El renderizador de la ventana
    SDL_Texture* game_canvas_;                                  // Textura donde se dibuja todo antes de volcarse al renderizador
    ServiceMenu* service_menu_;                                 // Objeto para mostrar el menú de servicio
    Notifier* notifier_;                                        // Objeto para mostrar las notificaciones por pantalla
    std::shared_ptr<Text> text_;                                // Objeto para escribir texto en pantalla
    std::unique_ptr<Rendering::ShaderBackend> shader_backend_;  // Backend de shaders (SDL3GPU)

    // --- Variables de estado ---
    SDL_FRect src_rect_;                // Coordenadas de origen para dibujar la textura del juego
    SDL_FRect dst_rect_;                // Coordenadas destino para dibujar la textura del juego
    std::vector<Uint32> pixel_buffer_;  // Buffer de píxeles para SDL_RenderReadPixels
    FPS fps_;                           // Gestión de frames por segundo
    FlashEffect flash_effect_;          // Efecto de flash en pantalla
    ShakeEffect shake_effect_;          // Efecto de agitar la pantalla
    bool attenuate_effect_ = false;     // Indica si la pantalla ha de estar atenuada
    DisplayMonitor display_monitor_;    // Información del monitor actual
#ifdef _DEBUG
    Debug debug_info_;  // Información de debug
#endif

    // --- Métodos internos ---
    auto initSDLVideo() -> bool;              // Arranca SDL VIDEO y crea la ventana
    void registerEmscriptenEventCallbacks();  // Registra callbacks nativos para restaurar el canvas en wasm (no-op fuera de emscripten)
    void renderFlash();                       // Dibuja el efecto de flash en la pantalla
    void renderShake();                       // Aplica el efecto de agitar la pantalla
    void renderInfo() const;                  // Muestra información por pantalla
    void renderPresent();                     // Selecciona y ejecuta el método de renderizado adecuado
    void applyCurrentPostFXPreset();          // Aplica el preset PostFX activo al backend
    void applyCurrentCrtPiPreset();           // Aplica el preset CrtPi activo al backend
    void adjustWindowSize();                  // Calcula el tamaño de la ventana
    void getDisplayInfo();                    // Obtiene información sobre la pantalla
    void renderOverlays();                    // Renderiza todos los overlays y efectos
    void renderAttenuate();                   // Atenúa la pantalla
    void createText();                        // Crea el objeto de texto

    // --- Constructores y destructor privados (singleton) ---
    Screen();   // Constructor privado
    ~Screen();  // Destructor privado

    // --- Instancia singleton ---
    static Screen* instance;  // Instancia única de Screen
};