#pragma once

#include <SDL3/SDL.h>

#include <cstddef>  // Para size_t
#include <memory>   // Para shared_ptr, __shared_ptr_access
#include <string>   // Para string
#include <vector>   // Para vector

#include "utils/utils.hpp"  // Para Color
class Surface;
namespace Rendering {
class ShaderBackend;
}

// Tipos de filtro
enum class ScreenFilter : Uint32 {
    NEAREST = 0,
    LINEAR = 1,
};

class Screen {
    private:
        // Constantes
        static constexpr int WINDOWS_DECORATIONS = 35;  // Decoraciones de la ventana

        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.

                // Constructor para inicializar la estructura.
                FPS()

                    = default;

                // Incrementador que se llama en cada frame.
                void increment() {
                    frame_count++;
                }

                // Método para calcular y devolver el valor de FPS.
                auto calculate(Uint32 current_ticks) -> int {
                    if (current_ticks - ticks >= 1000)  // Si ha pasado un segundo o más.
                    {
                        last_value = frame_count;  // Actualizamos el valor del último FPS.
                        frame_count = 0;           // Reiniciamos el contador de frames.
                        ticks = current_ticks;     // Actualizamos el tiempo base.
                    }
                    return last_value;
                }
        };

        // [SINGLETON] Objeto privado
        static Screen* screen;

        // Objetos y punteros
        SDL_Window* window_;                                          // Ventana de la aplicación
        SDL_Renderer* renderer_;                                      // El renderizador de la ventana
        SDL_Texture* game_texture_;                                   // Textura donde se dibuja el juego
        SDL_Texture* border_texture_;                                 // Textura donde se dibuja el borde del juego
        std::shared_ptr<Surface> game_surface_;                       // Surface principal para manejar game_surface_data_
        std::shared_ptr<Surface> border_surface_;                     // Surface para pintar el el borde de la pantalla
        std::shared_ptr<std::shared_ptr<Surface>> renderer_surface_;  // Puntero a la Surface que actua
        std::unique_ptr<Rendering::ShaderBackend> shader_backend_;    // Backend de shaders (OpenGL/Metal/Vulkan)

        // Variables
        int window_width_;                    // Ancho de la pantalla o ventana
        int window_height_;                   // Alto de la pantalla o ventana
        SDL_FRect game_surface_dstrect_;      // Coordenadas donde se va a dibujar la textura del juego sobre la pantalla o ventana
        Uint8 border_color_;                  // Color del borde añadido a la textura de juego para rellenar la pantalla
        std::vector<std::string> palettes_;   // Listado de los ficheros de paletta disponibles
        Uint8 current_palette_ = 0;           // Indice para el vector de paletas
        bool notifications_enabled_ = false;  // indica si se muestran las notificaciones
        FPS fps_;                             // Variable para gestionar los frames por segundo
        std::string info_resolution_;         // Texto con la informacion de la pantalla
        std::string vertex_shader_source_;    // Almacena el vertex shader
        std::string fragment_shader_source_;  // Almacena el fragment shader
        DisplayMonitor display_monitor_;      // Informacion de la pantalla

#ifdef _DEBUG
        bool show_debug_info_ = true;  // Indica si ha de mostrar/ocultar la información de la pantalla
#else
        bool show_debug_info_ = false;  // Indica si ha de mostrar/ocultar la información de la pantalla
#endif

        // Dibuja las notificaciones
        void renderNotifications() const;

        // Calcula el tamaño de la ventana
        void adjustWindowSize();

        // Ajusta el tamaño lógico del renderizador
        void adjustRenderLogicalSize();

        // Extrae los nombres de las paletas
        void processPaletteList();

        // Copia la surface a la textura
        void surfaceToTexture();

        // Copia la textura al renderizador
        void textureToRenderer();

        // Renderiza todos los overlays
        void renderOverlays();

        // Localiza la paleta dentro del vector de paletas
        auto findPalette(const std::string& name) -> size_t;

        void initShaders();           // Inicializa los shaders
        void loadShaders();           // Carga el contenido del archivo GLSL
        void renderInfo();            // Muestra información por pantalla
        void getDisplayInfo();        // Obtiene información sobre la pantalla
        auto initSDLVideo() -> bool;  // Arranca SDL VIDEO y crea la ventana

        // Constructor
        Screen();

        // Destructor
        ~Screen();

    public:
        // [SINGLETON] Crearemos el objeto con esta función estática
        static void init();

        // [SINGLETON] Destruiremos el objeto con esta función estática
        static void destroy();

        // [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
        static auto get() -> Screen*;

        // Limpia el renderer
        void clearRenderer(Color color = {0x00, 0x00, 0x00});

        // Limpia la game_surface_
        void clearSurface(Uint8 index);

        // Prepara para empezar a dibujar en la textura de juego
        void start();

        // Vuelca el contenido del renderizador en pantalla
        void render();

        // Actualiza la lógica de la clase
        void update();                  // Para escenas no migradas (sin delta_time)
        void update(float delta_time);  // Para escenas migradas (con delta_time)

        // Establece el modo de video
        void setVideoMode(bool mode);

        // Camibia entre pantalla completa y ventana
        void toggleVideoMode();

        // Alterna entre activar y desactivar el escalado entero
        void toggleIntegerScale();

        // Reduce el tamaño de la ventana
        auto decWindowZoom() -> bool;

        // Aumenta el tamaño de la ventana
        auto incWindowZoom() -> bool;

        // Cambia el color del borde
        void setBorderColor(Uint8 color);

        // Establece el tamaño del borde
        static void setBorderWidth(int width);

        // Establece el tamaño del borde
        static void setBorderHeight(int height);

        // Establece si se ha de ver el borde en el modo ventana
        static void setBorderEnabled(bool value);

        // Cambia entre borde visible y no visible
        void toggleBorder();

        // Cambia el estado de los shaders
        void toggleShaders();

        // Muestra la ventana
        void show();

        // Oculta la ventana
        void hide();

        // Establece el renderizador para las surfaces
        void setRendererSurface(const std::shared_ptr<Surface> &surface = nullptr);

        // Cambia la paleta
        void nextPalette();
        void previousPalette();

        // Establece la paleta
        void setPalete();

        // Establece la visibilidad de las notificaciones
        void setNotificationsEnabled(bool value);

        // Activa o desactiva la información de debug
        void toggleDebugInfo();

        // Getters
        auto getRenderer() -> SDL_Renderer*;
        auto getRendererSurface() -> std::shared_ptr<Surface>;
        auto getBorderSurface() -> std::shared_ptr<Surface>;
};