#pragma once

#include <SDL3/SDL.h>  // Para SDL_ScaleMode

#include <array>        // Para array
#include <cstddef>      // Para size_t
#include <exception>    // Para exception
#include <fstream>      // Para basic_ostream, operator<<, basic_ofstream, basic_ostream::operator<<, ofstream
#include <memory>       // Para shared_ptr, __shared_ptr_access, swap
#include <stdexcept>    // Para out_of_range, invalid_argument
#include <string>       // Para char_traits, basic_string, string, operator==, operator<<, swap, stoi
#include <string_view>  // Para string_view
#include <utility>      // Para move
#include <vector>       // Para vector

#include "core/input/input.hpp"               // for Input
#include "core/locale/lang.hpp"               // for Code
#include "core/rendering/shader_backend.hpp"  // for Rendering::ShaderType
#include "core/system/defaults.hpp"
#include "game/entities/player.hpp"                // for Player
#include "game/gameplay/difficulty.hpp"            // for Code
#include "game/gameplay/manage_hiscore_table.hpp"  // for ManageHiScoreTable, Table

// --- Namespace Options: gestión de configuración y opciones del juego ---
namespace Options {

    // --- Estructuras ---
    struct PostFXPreset {
        std::string name;
        float vignette{0.6F};
        float scanlines{0.7F};
        float chroma{0.15F};
        float mask{0.0F};
        float gamma{0.0F};
        float curvature{0.0F};
        float bleeding{0.0F};
        float flicker{0.0F};
    };

    struct CrtPiPreset {
        std::string name;
        float scanline_weight{6.0F};
        float scanline_gap_brightness{0.12F};
        float bloom_factor{3.5F};
        float input_gamma{2.4F};
        float output_gamma{2.2F};
        float mask_brightness{0.80F};
        float curvature_x{0.05F};
        float curvature_y{0.10F};
        int mask_type{2};
        bool enable_scanlines{true};
        bool enable_multisample{true};
        bool enable_gamma{true};
        bool enable_curvature{false};
        bool enable_sharper{false};
    };

    struct Window {
        std::string caption = Defaults::Window::CAPTION;
        int zoom = Defaults::Window::ZOOM;
        int max_zoom = Defaults::Window::MAX_ZOOM;
    };

    struct GPU {
        bool acceleration{Defaults::Video::GPU_ACCELERATION};
        std::string preferred_driver;
    };

    struct Supersampling {
        bool enabled{Defaults::Video::SUPERSAMPLING};
        bool linear_upscale{Defaults::Video::LINEAR_UPSCALE};
        int downscale_algo{Defaults::Video::DOWNSCALE_ALGO};
    };

    struct ShaderConfig {
        bool enabled{Defaults::Video::SHADER_ENABLED};
        Rendering::ShaderType current_shader{Rendering::ShaderType::POSTFX};
        std::string current_postfx_preset_name;
        std::string current_crtpi_preset_name;
        int current_postfx_preset{0};
        int current_crtpi_preset{0};
    };

    struct Video {
        SDL_ScaleMode scale_mode = Defaults::Video::SCALE_MODE;
        bool fullscreen = Defaults::Video::FULLSCREEN;
        bool vsync = Defaults::Video::VSYNC;
        bool integer_scale = Defaults::Video::INTEGER_SCALE;
        std::string info;
        GPU gpu{};
        Supersampling supersampling{};
        ShaderConfig shader{};
    };

    struct Music {
        bool enabled = Defaults::Music::ENABLED;  // Indica si la música suena o no
        float volume = Defaults::Music::VOLUME;   // Volumen de la música (0.0..1.0)
    };

    struct Sound {
        bool enabled = Defaults::Sound::ENABLED;  // Indica si los sonidos suenan o no
        float volume = Defaults::Sound::VOLUME;   // Volumen de los sonidos (0.0..1.0)
    };

    struct Audio {
        Music music;                              // Opciones para la música
        Sound sound;                              // Opciones para los efectos de sonido
        bool enabled = Defaults::Audio::ENABLED;  // Indica si el audio está activo o no
        float volume = Defaults::Audio::VOLUME;   // Volumen general del audio (0.0..1.0)
    };

    struct Loading {
        bool show = Defaults::Loading::SHOW;                              // Muestra la pantalla de carga (si no, pantalla en negro)
        bool show_resource_name = Defaults::Loading::SHOW_RESOURCE_NAME;  // Muestra el nombre del recurso en curso sobre la barra de progreso
        bool wait_for_input = Defaults::Loading::WAIT_FOR_INPUT;          // Al terminar la carga, espera a que el usuario pulse una tecla/botón (solo si show=true)
    };

    struct Settings {
        static constexpr int CURRENT_CONFIG_VERSION = 3;                                                  // Versión esperada del fichero
        int config_version = CURRENT_CONFIG_VERSION;                                                      // Versión del archivo de configuración
        Difficulty::Code difficulty = Difficulty::Code::NORMAL;                                           // Dificultad del juego
        Lang::Code language = Lang::Code::VALENCIAN;                                                      // Idioma usado en el juego
        bool autofire = Defaults::Settings::AUTOFIRE;                                                     // Indicador de autofire
        bool shutdown_enabled = Defaults::Settings::SHUTDOWN_ENABLED;                                     // Especifica si se puede apagar el sistema
        Table hi_score_table;                                                                             // Tabla de mejores puntuaciones
        std::vector<int> glowing_entries = {ManageHiScoreTable::NO_ENTRY, ManageHiScoreTable::NO_ENTRY};  // Últimas posiciones de entrada en la tabla
        std::string config_file;                                                                          // Ruta al fichero donde guardar la configuración y las opciones del juego
        std::string controllers_file;                                                                     // Ruta al fichero con las configuraciones de los mandos
        std::string params_file = Defaults::Settings::PARAMS_FILE;                                        // Ruta al fichero de parámetros del juego

        // Reinicia las últimas entradas de puntuación
        void clearLastHiScoreEntries() {
            glowing_entries.at(0) = ManageHiScoreTable::NO_ENTRY;
            glowing_entries.at(1) = ManageHiScoreTable::NO_ENTRY;
        }
    };

    struct Gamepad {
        std::shared_ptr<Input::Gamepad> instance = nullptr;  // Referencia al mando
        std::string name;                                    // Nombre del mando
        std::string path;                                    // Ruta física del dispositivo
        Player::Id player_id;                                // Jugador asociado al mando

        Gamepad(Player::Id custom_player_id = Player::Id::NO_PLAYER)
            : player_id(custom_player_id) {}
    };

    // --- Clases ---
    class GamepadManager {
       public:
        void init() {
            gamepads_[0] = Gamepad(Player::Id::PLAYER1);
            gamepads_[1] = Gamepad(Player::Id::PLAYER2);
        }

        // Acceso directo por player_id (más intuitivo)
        auto getGamepad(Player::Id player_id) -> Gamepad& {
            return gamepads_[playerIdToIndex(player_id)];
        }

        [[nodiscard]] auto getGamepad(Player::Id player_id) const -> const Gamepad& {
            return gamepads_[playerIdToIndex(player_id)];
        }

        // Acceso por índice (más eficiente si ya tienes el índice)
        auto operator[](size_t index) -> Gamepad& {
            if (index >= MAX_PLAYERS) {
                throw std::out_of_range("Invalid gamepad index");
            }
            return gamepads_[index];
        }

        auto operator[](size_t index) const -> const Gamepad& {
            if (index >= MAX_PLAYERS) {
                throw std::out_of_range("Invalid gamepad index");
            }
            return gamepads_[index];
        }

        auto assignGamepadToPlayer(Player::Id player_id,
            std::shared_ptr<Input::Gamepad> instance,
            const std::string& name) -> bool {
            try {
                auto& gamepad = getGamepad(player_id);
                gamepad.instance = std::move(instance);
                gamepad.name = name;
                return true;
            } catch (const std::exception&) {
                return false;
            }
        }

        void swapPlayers() {
            std::swap(gamepads_[0].instance, gamepads_[1].instance);
            std::swap(gamepads_[0].name, gamepads_[1].name);
            std::swap(gamepads_[0].path, gamepads_[1].path);

            resyncGamepadsWithPlayers();
        }

        void resyncGamepadsWithPlayers() {
            for (const auto& player : players_) {
                switch (player->getId()) {
                    case Player::Id::PLAYER1:
                        player->setGamepad(gamepads_[0].instance);
                        break;
                    case Player::Id::PLAYER2:
                        player->setGamepad(gamepads_[1].instance);
                        break;
                    default:
                        break;
                }
            }
        }

        void saveToFile(std::ofstream& file) const {
            for (size_t i = 0; i < MAX_PLAYERS; ++i) {
                const auto& gamepad = gamepads_[i];
                file << "  - name: \"" << (gamepad.path.empty() ? "" : gamepad.name) << "\"\n";
                file << "    path: \"" << gamepad.path << "\"\n";
                file << "    player: " << static_cast<int>(gamepad.player_id) << "\n";
            }
        }

        // Método helper para parseAndSetController
        auto setControllerProperty(size_t controller_index,
            const std::string& property,
            const std::string& value) -> bool {
            if (controller_index >= MAX_PLAYERS) {
                return false;
            }

            auto& gamepad = gamepads_[controller_index];

            if (property == "name") {
                gamepad.name = value;
                return true;
            }
            if (property == "path") {
                gamepad.path = value;
                return true;
            }
            if (property == "player") {
                try {
                    int player_int = std::stoi(value);
                    if (player_int == 1) {
                        gamepad.player_id = Player::Id::PLAYER1;
                    } else if (player_int == 2) {
                        gamepad.player_id = Player::Id::PLAYER2;
                    } else {
                        return false;
                    }
                    return true;
                } catch (const std::exception&) {
                    return false;
                }
            }

            return false;
        }

        void addPlayer(const std::shared_ptr<Player>& player) { players_.push_back(player); }  // Añade un jugador a la lista
        void clearPlayers() { players_.clear(); }                                              // Limpia la lista de jugadores

        // Asigna el mando a un jugador
        void assignTo(const Input::Gamepad& gamepad, Player::Id player_id) {
        }

        // Asigna los mandos físicos basándose en la configuración actual de nombres.
        void assignAndLinkGamepads();

        // Iteradores
        auto begin() { return gamepads_.begin(); }
        auto end() { return gamepads_.end(); }
        [[nodiscard]] auto begin() const { return gamepads_.begin(); }
        [[nodiscard]] auto end() const { return gamepads_.end(); }

        [[nodiscard]] static auto size() -> size_t { return MAX_PLAYERS; }

       private:
        static constexpr std::string_view UNASSIGNED_TEXT = "---";
        static constexpr size_t MAX_PLAYERS = 2;
        std::array<Gamepad, MAX_PLAYERS> gamepads_;     // Punteros a las estructuras de mandos de Options
        std::vector<std::shared_ptr<Player>> players_;  // Punteros a los jugadores

        // Convierte Player::Id a índice del array
        [[nodiscard]] static auto playerIdToIndex(Player::Id player_id) -> size_t {
            switch (player_id) {
                case Player::Id::PLAYER1:
                    return 0;
                case Player::Id::PLAYER2:
                    return 1;
                default:
                    throw std::invalid_argument("Invalid player ID");
            }
        }

        void assignGamepadsByPath(
            const std::array<std::string, MAX_PLAYERS>& desired_paths,
            const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads,  // NOLINT(readability-avoid-const-params-in-decls)
            std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances);
        void assignGamepadsByName(
            const std::array<std::string, MAX_PLAYERS>& desired_names,
            const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads,  // NOLINT(readability-avoid-const-params-in-decls)
            std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances);
        void assignRemainingGamepads(
            const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads,  // NOLINT(readability-avoid-const-params-in-decls)
            std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances);
        void clearUnassignedGamepadSlots();
        [[nodiscard]] static auto isGamepadAssigned(
            const std::shared_ptr<Input::Gamepad>& physical_gamepad,
            const std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances) -> bool;  // NOLINT(readability-avoid-const-params-in-decls)
    };

    struct Keyboard {
        Player::Id player_id = Player::Id::PLAYER1;    // Jugador asociado al teclado
        std::vector<std::shared_ptr<Player>> players;  // Punteros a los jugadores

        void addPlayer(const std::shared_ptr<Player>& player) { players.push_back(player); }  // Añade un jugador a la lista
        void clearPlayers() { players.clear(); }                                              // Limpia la lista de jugadores

        // Asigna el teclado a un jugador
        void assignTo(Player::Id player_id) {
            this->player_id = player_id;
            for (auto& player : players) {
                player->setUsesKeyboard(player->getId() == player_id);
            }
        }
    };

    struct PendingChanges {
        Lang::Code new_language = Lang::Code::VALENCIAN;             // Idioma en espera de aplicar
        Difficulty::Code new_difficulty = Difficulty::Code::NORMAL;  // Dificultad en espera de aplicar
        bool has_pending_changes = false;                            // Indica si hay cambios pendientes
    };

    // --- Variables ---
    extern Window window;                             // Opciones de la ventana
    extern Settings settings;                         // Opciones del juego
    extern Video video;                               // Opciones de vídeo
    extern Audio audio;                               // Opciones de audio
    extern Loading loading;                           // Opciones de la pantalla de carga
    extern GamepadManager gamepad_manager;            // Manager de mandos para cada jugador
    extern Keyboard keyboard;                         // Opciones para el teclado
    extern PendingChanges pending_changes;            // Opciones que se aplican al cerrar
    extern std::vector<PostFXPreset> postfx_presets;  // Lista de presets de PostFX
    extern std::string postfx_file_path;              // Ruta al fichero de presets PostFX
    extern std::vector<CrtPiPreset> crtpi_presets;    // Lista de presets de CrtPi
    extern std::string crtpi_file_path;               // Ruta al fichero de presets CrtPi

    // --- Funciones ---
    void init();                                                                              // Inicializa las opciones del programa
    void setConfigFile(const std::string& file_path);                                         // Establece el fichero de configuración
    void setControllersFile(const std::string& file_path);                                    // Establece el fichero de configuración de mandos
    void setPostFXFile(const std::string& path);                                              // Establece el fichero de presets PostFX
    void setCrtPiFile(const std::string& path);                                               // Establece el fichero de presets CrtPi
    auto loadPostFXFromFile() -> bool;                                                        // Carga los presets PostFX desde fichero
    auto savePostFXToFile() -> bool;                                                          // Guarda los presets PostFX por defecto al fichero
    auto loadCrtPiFromFile() -> bool;                                                         // Carga los presets CrtPi desde fichero
    auto loadFromFile() -> bool;                                                              // Carga el fichero de configuración
    auto saveToFile() -> bool;                                                                // Guarda el fichero de configuración
    void setKeyboardToPlayer(Player::Id player_id);                                           // Asigna el teclado al jugador
    void swapKeyboard();                                                                      // Intercambia el teclado de jugador
    void swapControllers();                                                                   // Intercambia los jugadores asignados a los dos primeros mandos
    auto getPlayerWhoUsesKeyboard() -> Player::Id;                                            // Averigua quién está usando el teclado
    auto playerIdToString(Player::Id player_id) -> std::string;                               // Convierte un player id a texto segun Lang
    auto stringToPlayerId(const std::string& name) -> Player::Id;                             // Convierte un texto a player id segun Lang
    void applyPendingChanges();                                                               // Aplica los cambios pendientes copiando los valores a sus variables
    void checkPendingChanges();                                                               // Verifica si hay cambios pendientes
    auto assignGamepadByName(const std::string& gamepad_name, Player::Id player_id) -> bool;  // Buscar y asignar un mando disponible por nombre
}  // namespace Options