#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 "color.h"    // Para Color
#include "options.h"  // Para VideoOptions, video

class Notifier;
class ServiceMenu;
class Text;

// Clase Screen: gestiona la ventana, el renderizador y los efectos visuales globales
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();                                      // Actualiza la lógica de la clase
        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 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
        void initShaders();            // Inicializa los shaders

        // --- Efectos visuales ---
        void shake(int desp = 2, int delay = 3, int lenght = 8) { shake_effect_.enable(src_rect_, dst_rect_, desp, delay, lenght); }  // Agita la pantalla
        void flash(Color color, int lenght = 10, int delay = 0) { flash_effect_ = FlashEffect(true, lenght, delay, color); }          // Pone la pantalla de color
        void toggleShaders();                                                                                                         // Alterna entre activar y desactivar los shaders
        void toggleIntegerScale();                                                                                                    // Alterna entre activar y desactivar el escalado entero
        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]] auto getText() const -> std::shared_ptr<Text> { return text_; }  // Obtiene el puntero al texto de Screen

#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;

        // --- Estructuras internas ---
        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 unos frames
        struct FlashEffect {
                bool enabled;  // Indica si el efecto está activo
                int lenght;    // Duración total del efecto en frames
                int delay;     // Retraso antes de mostrar el flash
                int counter;   // Contador de frames restantes
                Color color;   // Color del flash

                explicit FlashEffect(bool enabled = false, int lenght = 0, int delay = 0, Color color = Color(0xFF, 0xFF, 0xFF))
                    : enabled(enabled), lenght(lenght), delay(delay), counter(lenght), color(color) {}

                void update() { (enabled && counter > 0) ? counter-- : static_cast<int>(enabled = false); }
                [[nodiscard]] auto isRendarable() const -> bool { return enabled && counter < lenght - delay; }
        };

        // 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)
                int delay;           // Frames entre cada movimiento de sacudida
                int counter;         // Contador de frames para el siguiente movimiento
                int lenght;          // Duración total del efecto en frames
                int remaining;       // Frames restantes 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, int dl = 3, int cnt = 0, int len = 8, int rem = 0, int orig_pos = 0, int orig_width = 800)
                    : desp(dp), delay(dl), counter(cnt), lenght(len), remaining(rem), 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, int new_delay = -1, int new_lenght = -1) {
                    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 != -1) {
                            delay = new_delay;
                        }
                        if (new_lenght != -1) {
                            lenght = new_lenght;
                        }

                        src_rect.w -= desp;
                        dst_rect.w = src_rect.w;
                    }
                    remaining = lenght;
                    counter = delay;
                }

                // Actualiza el estado del efecto de sacudida
                void update(SDL_FRect &src_rect, SDL_FRect &dst_rect) {
                    if (enabled) {
                        if (counter > 0) {
                            counter--;
                        } else {
                            counter = delay;
                            const auto SRC_DESP = (remaining % 2 == 0) ? 0 : desp;
                            const auto DST_DESP = (remaining % 2 == 1) ? 0 : desp;
                            src_rect.x = original_pos + SRC_DESP;
                            dst_rect.x = original_pos + DST_DESP;
                            remaining--;
                            if (remaining == -1) {
                                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

        // --- Singleton ---
        static Screen *instance;

        // --- 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

        // --- 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
        FPS fps_;                        // Gestión de frames por segundo
        std::string shader_source_;      // Almacena el contenido del archivo GLSL
        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
#ifdef _DEBUG
        Debug debug_info_;  // Información de debug
#endif

        // --- Texto ---
        std::shared_ptr<Text> text_;  // Objeto para escribir texto en pantalla

        // --- Métodos internos ---
        auto initSDLVideo() -> bool;   // Arranca SDL VIDEO y crea la ventana
        void renderFlash();            // Dibuja el efecto de flash en la pantalla
        void renderShake();            // Aplica el efecto de agitar la pantalla
        void renderInfo();             // Muestra información por pantalla
        void renderScreen();           // Selecciona y ejecuta el método de renderizado adecuado
        void loadShaders();            // Carga el contenido del archivo GLSL
        void adjustWindowSize();       // Calcula el tamaño de la ventana
        static 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 ---
        Screen();
        ~Screen();
};