#include "game/options.hpp"

#include <SDL3/SDL.h>  // Para SDL_ScaleMode, SDL_LogCategory, SDL_LogError, SDL_LogInfo, SDL_LogWarn

#include <algorithm>  // Para clamp
#include <cstddef>    // Para size_t
#include <fstream>    // Para ifstream, ofstream
#include <iostream>   // Para std::cout
#include <string>     // Para string
#include <vector>     // Para vector

#include "core/input/input.hpp"          // Para Input
#include "core/locale/lang.hpp"          // Para getText, Code
#include "external/fkyaml_node.hpp"      // Para fkyaml::node
#include "game/gameplay/difficulty.hpp"  // Para Code, init
#include "utils/utils.hpp"               // Para boolToString, getFileName

namespace Options {
    // --- Variables globales ---
    Window window;                   // Opciones de la ventana
    Settings settings;               // Opciones del juego
    Video video;                     // Opciones de vídeo
    Audio audio;                     // Opciones de audio
    Loading loading;                 // Opciones de la pantalla de carga
    GamepadManager gamepad_manager;  // Opciones de mando para cada jugador
    Keyboard keyboard;               // Opciones para el teclado
    PendingChanges pending_changes;  // Opciones que se aplican al cerrar
    std::vector<PostFXPreset> postfx_presets = {
        {"CRT", 0.15F, 0.7F, 0.2F, 0.5F, 0.1F, 0.0F, 0.0F, 0.0F},
        {"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F},
        {"Curved", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F},
        {"Scanlines", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F},
        {"Subtle", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F},
        {"CRT Live", 0.15F, 0.6F, 0.3F, 0.3F, 0.1F, 0.0F, 0.4F, 0.8F},
    };
    std::string postfx_file_path;
    std::vector<CrtPiPreset> crtpi_presets;
    std::string crtpi_file_path;

    // Establece el fichero de configuración
    void setConfigFile(const std::string& file_path) { settings.config_file = file_path; }

    // Establece el fichero de configuración de mandos
    void setControllersFile(const std::string& file_path) { settings.controllers_file = file_path; }

    // Establece la ruta del fichero de PostFX
    void setPostFXFile(const std::string& path) { postfx_file_path = path; }

    // Establece la ruta del fichero de CrtPi
    void setCrtPiFile(const std::string& path) { crtpi_file_path = path; }

    // Helper: extrae un campo float de un nodo YAML si existe, ignorando errores de conversión
    static void parseFloatField(const fkyaml::node& node, const std::string& key, float& target) {
        if (node.contains(key)) {
            try {
                target = node[key].get_value<float>();
            } catch (...) {}
        }
    }

    // Carga los presets de PostFX desde el fichero
    auto loadPostFXFromFile() -> bool {
        postfx_presets.clear();

        std::ifstream file(postfx_file_path);
        if (!file.good()) {
            return savePostFXToFile();
        }

        std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        file.close();

        try {
            auto yaml = fkyaml::node::deserialize(content);

            if (yaml.contains("presets")) {
                const auto& presets = yaml["presets"];
                for (const auto& p : presets) {
                    PostFXPreset preset;
                    if (p.contains("name")) {
                        preset.name = p["name"].get_value<std::string>();
                    }
                    parseFloatField(p, "vignette", preset.vignette);
                    parseFloatField(p, "scanlines", preset.scanlines);
                    parseFloatField(p, "chroma", preset.chroma);
                    parseFloatField(p, "mask", preset.mask);
                    parseFloatField(p, "gamma", preset.gamma);
                    parseFloatField(p, "curvature", preset.curvature);
                    parseFloatField(p, "bleeding", preset.bleeding);
                    parseFloatField(p, "flicker", preset.flicker);
                    postfx_presets.push_back(preset);
                }
            }

            if (!postfx_presets.empty()) {
                // Resolver nombre → índice
                if (!video.shader.current_postfx_preset_name.empty()) {
                    for (int i = 0; i < static_cast<int>(postfx_presets.size()); ++i) {
                        if (postfx_presets[static_cast<size_t>(i)].name == video.shader.current_postfx_preset_name) {
                            video.shader.current_postfx_preset = i;
                            break;
                        }
                    }
                }
                video.shader.current_postfx_preset = std::clamp(
                    video.shader.current_postfx_preset,
                    0,
                    static_cast<int>(postfx_presets.size()) - 1);
            } else {
                video.shader.current_postfx_preset = 0;
            }

            return true;

        } catch (const fkyaml::exception& e) {
            std::cout << "Error parsing PostFX YAML: " << e.what() << ". Recreating defaults." << '\n';
            return savePostFXToFile();
        }
    }

    // Guarda los presets de PostFX por defecto al fichero
    auto savePostFXToFile() -> bool {
        if (postfx_file_path.empty()) {
            return false;
        }

        std::ofstream file(postfx_file_path);
        if (!file.is_open()) {
            std::cout << "Error: " << postfx_file_path << " can't be opened for writing" << '\n';
            return false;
        }

        file << "# Coffee Crisis Arcade Edition - PostFX Presets\n";
        file << "# Each preset defines the intensity of post-processing effects (0.0 to 1.0).\n";
        file << "# vignette: screen darkening at the edges\n";
        file << "# scanlines: horizontal scanline effect\n";
        file << "# chroma: chromatic aberration (RGB color fringing)\n";
        file << "# mask: phosphor dot mask (RGB subpixel pattern)\n";
        file << "# gamma: gamma correction input 2.4 / output 2.2\n";
        file << "# curvature: CRT barrel distortion\n";
        file << "# bleeding: NTSC horizontal colour bleeding\n";
        file << "# flicker: phosphor CRT flicker ~50 Hz (0.0 = off, 1.0 = max)\n";
        file << "\n";
        file << "presets:\n";
        file << "  - name: \"CRT\"\n";
        file << "    vignette: 0.15\n";
        file << "    scanlines: 0.7\n";
        file << "    chroma: 0.2\n";
        file << "    mask: 0.5\n";
        file << "    gamma: 0.1\n";
        file << "    curvature: 0.0\n";
        file << "    bleeding: 0.0\n";
        file << "    flicker: 0.0\n";
        file << "  - name: \"NTSC\"\n";
        file << "    vignette: 0.4\n";
        file << "    scanlines: 0.5\n";
        file << "    chroma: 0.2\n";
        file << "    mask: 0.3\n";
        file << "    gamma: 0.3\n";
        file << "    curvature: 0.0\n";
        file << "    bleeding: 0.6\n";
        file << "    flicker: 0.0\n";
        file << "  - name: \"Curved\"\n";
        file << "    vignette: 0.5\n";
        file << "    scanlines: 0.6\n";
        file << "    chroma: 0.1\n";
        file << "    mask: 0.4\n";
        file << "    gamma: 0.4\n";
        file << "    curvature: 0.8\n";
        file << "    bleeding: 0.0\n";
        file << "    flicker: 0.0\n";
        file << "  - name: \"Scanlines\"\n";
        file << "    vignette: 0.0\n";
        file << "    scanlines: 0.8\n";
        file << "    chroma: 0.0\n";
        file << "    mask: 0.0\n";
        file << "    gamma: 0.0\n";
        file << "    curvature: 0.0\n";
        file << "    bleeding: 0.0\n";
        file << "    flicker: 0.0\n";
        file << "  - name: \"Subtle\"\n";
        file << "    vignette: 0.3\n";
        file << "    scanlines: 0.4\n";
        file << "    chroma: 0.05\n";
        file << "    mask: 0.0\n";
        file << "    gamma: 0.2\n";
        file << "    curvature: 0.0\n";
        file << "    bleeding: 0.0\n";
        file << "    flicker: 0.0\n";
        file << "  - name: \"CRT Live\"\n";
        file << "    vignette: 0.15\n";
        file << "    scanlines: 0.6\n";
        file << "    chroma: 0.3\n";
        file << "    mask: 0.3\n";
        file << "    gamma: 0.1\n";
        file << "    curvature: 0.0\n";
        file << "    bleeding: 0.4\n";
        file << "    flicker: 0.8\n";

        file.close();

        // Cargar los presets recién escritos
        postfx_presets.clear();
        postfx_presets.push_back({"CRT", 0.15F, 0.7F, 0.2F, 0.5F, 0.1F, 0.0F, 0.0F, 0.0F});
        postfx_presets.push_back({"NTSC", 0.4F, 0.5F, 0.2F, 0.3F, 0.3F, 0.0F, 0.6F, 0.0F});
        postfx_presets.push_back({"Curved", 0.5F, 0.6F, 0.1F, 0.4F, 0.4F, 0.8F, 0.0F, 0.0F});
        postfx_presets.push_back({"Scanlines", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F});
        postfx_presets.push_back({"Subtle", 0.3F, 0.4F, 0.05F, 0.0F, 0.2F, 0.0F, 0.0F, 0.0F});
        postfx_presets.push_back({"CRT Live", 0.15F, 0.6F, 0.3F, 0.3F, 0.1F, 0.0F, 0.4F, 0.8F});
        video.shader.current_postfx_preset = 0;

        return true;
    }

    // Helper: extrae un campo bool de un nodo YAML si existe, ignorando errores
    static void parseBoolField(const fkyaml::node& node, const std::string& key, bool& target) {
        if (node.contains(key)) {
            try {
                target = node[key].get_value<bool>();
            } catch (...) {}
        }
    }

    // Helper: extrae un campo int de un nodo YAML si existe, ignorando errores
    static void parseIntField(const fkyaml::node& node, const std::string& key, int& target) {
        if (node.contains(key)) {
            try {
                target = node[key].get_value<int>();
            } catch (...) {}
        }
    }

    // Rellena los presets CrtPi por defecto
    static void populateDefaultCrtPiPresets() {
        crtpi_presets.clear();
        crtpi_presets.push_back({"Default", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, false, false});
        crtpi_presets.push_back({"Curved", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false});
        crtpi_presets.push_back({"Sharp", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true});
        crtpi_presets.push_back({"Minimal", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false});
    }

    // Escribe los presets CrtPi por defecto al fichero
    static auto saveCrtPiDefaults() -> bool {
        if (crtpi_file_path.empty()) { return false; }
        std::ofstream file(crtpi_file_path);
        if (!file.is_open()) {
            std::cout << "Error: " << crtpi_file_path << " can't be opened for writing" << '\n';
            return false;
        }
        file << "# Coffee Crisis Arcade Edition - CrtPi Shader Presets\n";
        file << "# scanline_weight: gaussian adjustment (higher = narrower scanlines, default 6.0)\n";
        file << "# scanline_gap_brightness: min brightness between scanlines (0.0-1.0, default 0.12)\n";
        file << "# bloom_factor: brightness for bright areas (default 3.5)\n";
        file << "# input_gamma: input gamma - linearization (default 2.4)\n";
        file << "# output_gamma: output gamma - encoding (default 2.2)\n";
        file << "# mask_brightness: sub-pixel brightness (default 0.80)\n";
        file << "# curvature_x/y: barrel CRT distortion (0.0 = flat)\n";
        file << "# mask_type: 0=none, 1=green/magenta, 2=RGB phosphor\n";
        file << "# enable_scanlines/multisample/gamma/curvature/sharper: true/false\n";
        file << "\npresets:\n";
        file << "  - name: \"Default\"\n    scanline_weight: 6.0\n    scanline_gap_brightness: 0.12\n    bloom_factor: 3.5\n    input_gamma: 2.4\n    output_gamma: 2.2\n    mask_brightness: 0.80\n    curvature_x: 0.05\n    curvature_y: 0.10\n    mask_type: 2\n    enable_scanlines: true\n    enable_multisample: true\n    enable_gamma: true\n    enable_curvature: false\n    enable_sharper: false\n";
        file << "  - name: \"Curved\"\n    scanline_weight: 6.0\n    scanline_gap_brightness: 0.12\n    bloom_factor: 3.5\n    input_gamma: 2.4\n    output_gamma: 2.2\n    mask_brightness: 0.80\n    curvature_x: 0.05\n    curvature_y: 0.10\n    mask_type: 2\n    enable_scanlines: true\n    enable_multisample: true\n    enable_gamma: true\n    enable_curvature: true\n    enable_sharper: false\n";
        file << "  - name: \"Sharp\"\n    scanline_weight: 6.0\n    scanline_gap_brightness: 0.12\n    bloom_factor: 3.5\n    input_gamma: 2.4\n    output_gamma: 2.2\n    mask_brightness: 0.80\n    curvature_x: 0.05\n    curvature_y: 0.10\n    mask_type: 2\n    enable_scanlines: true\n    enable_multisample: false\n    enable_gamma: true\n    enable_curvature: false\n    enable_sharper: true\n";
        file << "  - name: \"Minimal\"\n    scanline_weight: 8.0\n    scanline_gap_brightness: 0.05\n    bloom_factor: 2.0\n    input_gamma: 2.4\n    output_gamma: 2.2\n    mask_brightness: 1.00\n    curvature_x: 0.0\n    curvature_y: 0.0\n    mask_type: 0\n    enable_scanlines: true\n    enable_multisample: false\n    enable_gamma: false\n    enable_curvature: false\n    enable_sharper: false\n";
        file.close();
        populateDefaultCrtPiPresets();
        return true;
    }

    // Carga los presets de CrtPi desde el fichero
    auto loadCrtPiFromFile() -> bool {
        crtpi_presets.clear();

        std::ifstream file(crtpi_file_path);
        if (!file.good()) {
            return saveCrtPiDefaults();
        }

        std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        file.close();

        try {
            auto yaml = fkyaml::node::deserialize(content);

            if (yaml.contains("presets")) {
                const auto& presets = yaml["presets"];
                for (const auto& p : presets) {
                    CrtPiPreset preset;
                    if (p.contains("name")) {
                        preset.name = p["name"].get_value<std::string>();
                    }
                    parseFloatField(p, "scanline_weight", preset.scanline_weight);
                    parseFloatField(p, "scanline_gap_brightness", preset.scanline_gap_brightness);
                    parseFloatField(p, "bloom_factor", preset.bloom_factor);
                    parseFloatField(p, "input_gamma", preset.input_gamma);
                    parseFloatField(p, "output_gamma", preset.output_gamma);
                    parseFloatField(p, "mask_brightness", preset.mask_brightness);
                    parseFloatField(p, "curvature_x", preset.curvature_x);
                    parseFloatField(p, "curvature_y", preset.curvature_y);
                    parseIntField(p, "mask_type", preset.mask_type);
                    parseBoolField(p, "enable_scanlines", preset.enable_scanlines);
                    parseBoolField(p, "enable_multisample", preset.enable_multisample);
                    parseBoolField(p, "enable_gamma", preset.enable_gamma);
                    parseBoolField(p, "enable_curvature", preset.enable_curvature);
                    parseBoolField(p, "enable_sharper", preset.enable_sharper);
                    crtpi_presets.push_back(preset);
                }
            }

            if (!crtpi_presets.empty()) {
                // Resolver nombre → índice
                if (!video.shader.current_crtpi_preset_name.empty()) {
                    for (int i = 0; i < static_cast<int>(crtpi_presets.size()); ++i) {
                        if (crtpi_presets[static_cast<size_t>(i)].name == video.shader.current_crtpi_preset_name) {
                            video.shader.current_crtpi_preset = i;
                            break;
                        }
                    }
                }
                video.shader.current_crtpi_preset = std::clamp(
                    video.shader.current_crtpi_preset,
                    0,
                    static_cast<int>(crtpi_presets.size()) - 1);
            } else {
                video.shader.current_crtpi_preset = 0;
            }

            return true;

        } catch (const fkyaml::exception& e) {
            std::cout << "Error parsing CrtPi YAML: " << e.what() << ". Recreating defaults." << '\n';
            return saveCrtPiDefaults();
        }
    }

    // Inicializa las opciones del programa
    void init() {
        // Dificultades
        Difficulty::init();

        // Opciones de control
        gamepad_manager.init();
        setKeyboardToPlayer(Player::Id::PLAYER1);

        // Opciones de cambios pendientes
        pending_changes.new_language = settings.language;
        pending_changes.new_difficulty = settings.difficulty;
        pending_changes.has_pending_changes = false;
    }

    // --- Funciones helper de carga desde YAML ---

    void loadWindowFromYaml(const fkyaml::node& yaml) {
        if (!yaml.contains("window")) { return; }
        const auto& win = yaml["window"];
        if (win.contains("zoom")) {
            try {
                int val = win["zoom"].get_value<int>();
                window.zoom = (val > 0) ? val : window.zoom;
            } catch (...) {}
        }
    }

    void loadVideoFromYaml(const fkyaml::node& yaml) {
        if (!yaml.contains("video")) { return; }
        const auto& vid = yaml["video"];

        if (vid.contains("fullscreen")) {
            try {
                video.fullscreen = vid["fullscreen"].get_value<bool>();
            } catch (...) {}
        }
        if (vid.contains("scale_mode")) {
            try {
                video.scale_mode = static_cast<SDL_ScaleMode>(vid["scale_mode"].get_value<int>());
            } catch (...) {}
        }
        if (vid.contains("vsync")) {
            try {
                video.vsync = vid["vsync"].get_value<bool>();
            } catch (...) {}
        }
        if (vid.contains("integer_scale")) {
            try {
                video.integer_scale = vid["integer_scale"].get_value<bool>();
            } catch (...) {}
        }

        // --- GPU ---
        if (vid.contains("gpu")) {
            const auto& gpu_node = vid["gpu"];
            if (gpu_node.contains("acceleration")) {
                try {
                    video.gpu.acceleration = gpu_node["acceleration"].get_value<bool>();
                } catch (...) {}
            }
            if (gpu_node.contains("preferred_driver")) {
                try {
                    video.gpu.preferred_driver = gpu_node["preferred_driver"].get_value<std::string>();
                } catch (...) {}
            }
        }

        // --- Shader config ---
        if (vid.contains("shader")) {
            const auto& sh = vid["shader"];
            if (sh.contains("enabled")) {
                try {
                    video.shader.enabled = sh["enabled"].get_value<bool>();
                } catch (...) {}
            }
            if (sh.contains("current_shader")) {
                try {
                    auto s = sh["current_shader"].get_value<std::string>();
                    video.shader.current_shader = (s == "crtpi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
                } catch (...) {}
            }
            if (sh.contains("postfx_preset")) {
                try {
                    video.shader.current_postfx_preset_name = sh["postfx_preset"].get_value<std::string>();
                } catch (...) {}
            }
            if (sh.contains("crtpi_preset")) {
                try {
                    video.shader.current_crtpi_preset_name = sh["crtpi_preset"].get_value<std::string>();
                } catch (...) {}
            }
        }

        // --- Supersampling ---
        if (vid.contains("supersampling")) {
            const auto& ss_node = vid["supersampling"];
            if (ss_node.contains("enabled")) {
                try {
                    video.supersampling.enabled = ss_node["enabled"].get_value<bool>();
                } catch (...) {}
            }
            if (ss_node.contains("linear_upscale")) {
                try {
                    video.supersampling.linear_upscale = ss_node["linear_upscale"].get_value<bool>();
                } catch (...) {}
            }
            if (ss_node.contains("downscale_algo")) {
                try {
                    video.supersampling.downscale_algo = ss_node["downscale_algo"].get_value<int>();
                } catch (...) {}
            }
        }
    }

    void loadAudioFromYaml(const fkyaml::node& yaml) {
        if (!yaml.contains("audio")) { return; }
        const auto& aud = yaml["audio"];

        if (aud.contains("enabled")) {
            try {
                audio.enabled = aud["enabled"].get_value<bool>();
            } catch (...) {}
        }
        if (aud.contains("volume")) {
            try {
                audio.volume = std::clamp(aud["volume"].get_value<float>(), 0.0F, 1.0F);
            } catch (...) {}
        }
        if (aud.contains("music")) {
            const auto& mus = aud["music"];
            if (mus.contains("enabled")) {
                try {
                    audio.music.enabled = mus["enabled"].get_value<bool>();
                } catch (...) {}
            }
            if (mus.contains("volume")) {
                try {
                    audio.music.volume = std::clamp(mus["volume"].get_value<float>(), 0.0F, 1.0F);
                } catch (...) {}
            }
        }
        if (aud.contains("sound")) {
            const auto& snd = aud["sound"];
            if (snd.contains("enabled")) {
                try {
                    audio.sound.enabled = snd["enabled"].get_value<bool>();
                } catch (...) {}
            }
            if (snd.contains("volume")) {
                try {
                    audio.sound.volume = std::clamp(snd["volume"].get_value<float>(), 0.0F, 1.0F);
                } catch (...) {}
            }
        }
    }

    void loadLoadingFromYaml(const fkyaml::node& yaml) {
        if (!yaml.contains("loading")) { return; }
        const auto& ld = yaml["loading"];
        parseBoolField(ld, "show", loading.show);
        parseBoolField(ld, "show_resource_name", loading.show_resource_name);
        parseBoolField(ld, "wait_for_input", loading.wait_for_input);
    }

    void loadGameFromYaml(const fkyaml::node& yaml) {
        if (!yaml.contains("game")) { return; }
        const auto& game = yaml["game"];

        if (game.contains("language")) {
            try {
                auto lang = static_cast<Lang::Code>(game["language"].get_value<int>());
                if (lang == Lang::Code::ENGLISH || lang == Lang::Code::VALENCIAN || lang == Lang::Code::SPANISH) {
                    settings.language = lang;
                } else {
                    settings.language = Lang::Code::ENGLISH;
                }
                pending_changes.new_language = settings.language;
            } catch (...) {}
        }
        if (game.contains("difficulty")) {
            try {
                settings.difficulty = static_cast<Difficulty::Code>(game["difficulty"].get_value<int>());
                pending_changes.new_difficulty = settings.difficulty;
            } catch (...) {}
        }
        if (game.contains("autofire")) {
            try {
                settings.autofire = game["autofire"].get_value<bool>();
            } catch (...) {}
        }
        if (game.contains("shutdown_enabled")) {
            try {
                settings.shutdown_enabled = game["shutdown_enabled"].get_value<bool>();
            } catch (...) {}
        }
        if (game.contains("params_file")) {
            try {
                settings.params_file = game["params_file"].get_value<std::string>();
            } catch (...) {}
        }
    }

    void loadControllersFromYaml(const fkyaml::node& yaml) {
        if (!yaml.contains("controllers")) { return; }
        const auto& controllers = yaml["controllers"];

        size_t i = 0;
        for (const auto& ctrl : controllers) {
            if (i >= GamepadManager::size()) { break; }
            if (ctrl.contains("name")) {
                try {
                    gamepad_manager[i].name = ctrl["name"].get_value<std::string>();
                } catch (...) {}
            }
            if (ctrl.contains("path")) {
                try {
                    gamepad_manager[i].path = ctrl["path"].get_value<std::string>();
                } catch (...) {}
            }
            if (ctrl.contains("player")) {
                try {
                    int player_int = ctrl["player"].get_value<int>();
                    if (player_int == 1) {
                        gamepad_manager[i].player_id = Player::Id::PLAYER1;
                    } else if (player_int == 2) {
                        gamepad_manager[i].player_id = Player::Id::PLAYER2;
                    }
                } catch (...) {}
            }
            ++i;
        }
    }

    void loadKeyboardFromYaml(const fkyaml::node& yaml) {
        if (!yaml.contains("keyboard")) { return; }
        const auto& kb = yaml["keyboard"];
        if (kb.contains("player")) {
            try {
                keyboard.player_id = static_cast<Player::Id>(kb["player"].get_value<int>());
            } catch (...) {}
        }
    }

    // Carga el fichero de configuración
    auto loadFromFile() -> bool {
        init();

        std::ifstream file(settings.config_file);
        if (!file.is_open()) {
            saveToFile();
            return true;
        }

        std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        file.close();

        try {
            auto yaml = fkyaml::node::deserialize(content);

            // Comprobar versión: si no coincide, regenerar config por defecto
            int file_version = 0;
            if (yaml.contains("version")) {
                try {
                    file_version = yaml["version"].get_value<int>();
                } catch (...) {}
            }
            if (file_version != Settings::CURRENT_CONFIG_VERSION) {
                std::cout << "Config version " << file_version << " != expected " << Settings::CURRENT_CONFIG_VERSION << ". Recreating defaults." << '\n';
                init();
                saveToFile();
                return true;
            }

            loadWindowFromYaml(yaml);
            loadVideoFromYaml(yaml);
            loadAudioFromYaml(yaml);
            loadLoadingFromYaml(yaml);
            loadGameFromYaml(yaml);
            loadControllersFromYaml(yaml);
            loadKeyboardFromYaml(yaml);

        } catch (const fkyaml::exception& e) {
            std::cout << "Error parsing YAML config: " << e.what() << ". Using defaults." << '\n';
            init();
            saveToFile();
            return true;
        }

        gamepad_manager.assignAndLinkGamepads();
        return true;
    }

    // Guarda el fichero de configuración
    auto saveToFile() -> bool {
        std::ofstream file(settings.config_file);

        if (!file.good()) {
            std::cout << "Error: " << getFileName(settings.config_file) << " can't be opened" << '\n';
            return false;
        }

        applyPendingChanges();

        file << "# Coffee Crisis Arcade Edition - Configuration File\n";
        file << "# This file is automatically generated and managed by the game.\n";
        file << "\n";

        file << "version: " << settings.config_version << "\n";
        file << "\n";

        // WINDOW
        file << "# WINDOW\n";
        file << "window:\n";
        file << "  zoom: " << window.zoom << "\n";
        file << "\n";

        // VIDEO
        file << "# VIDEO\n";
        file << "video:\n";
        file << "  fullscreen: " << boolToString(video.fullscreen) << "\n";
        file << "  scale_mode: " << static_cast<int>(video.scale_mode) << "  # " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_NEAREST) << ": nearest, " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_LINEAR) << ": linear\n";
        file << "  vsync: " << boolToString(video.vsync) << "\n";
        file << "  integer_scale: " << boolToString(video.integer_scale) << "\n";
        file << "  gpu:\n";
        file << "    acceleration: " << boolToString(video.gpu.acceleration) << "\n";
        file << "    preferred_driver: \"" << video.gpu.preferred_driver << "\"\n";
        file << "  shader:\n";
        file << "    enabled: " << boolToString(video.shader.enabled) << "\n";
        file << "    current_shader: " << (video.shader.current_shader == Rendering::ShaderType::CRTPI ? "crtpi" : "postfx") << "\n";
        {
            std::string postfx_name = (!postfx_presets.empty() && video.shader.current_postfx_preset < static_cast<int>(postfx_presets.size()))
                ? postfx_presets[static_cast<size_t>(video.shader.current_postfx_preset)].name
                : "";
            std::string crtpi_name = (!crtpi_presets.empty() && video.shader.current_crtpi_preset < static_cast<int>(crtpi_presets.size()))
                ? crtpi_presets[static_cast<size_t>(video.shader.current_crtpi_preset)].name
                : "";
            file << "    postfx_preset: \"" << postfx_name << "\"\n";
            file << "    crtpi_preset: \"" << crtpi_name << "\"\n";
        }
        file << "  supersampling:\n";
        file << "    enabled: " << boolToString(video.supersampling.enabled) << "\n";
        file << "    linear_upscale: " << boolToString(video.supersampling.linear_upscale) << "\n";
        file << "    downscale_algo: " << video.supersampling.downscale_algo << "\n";
        file << "\n";

        // AUDIO
        file << "# AUDIO  (volume range: 0..100)\n";
        file << "audio:\n";
        file << "  enabled: " << boolToString(audio.enabled) << "\n";
        file << "  volume: " << audio.volume << "\n";
        file << "  music:\n";
        file << "    enabled: " << boolToString(audio.music.enabled) << "\n";
        file << "    volume: " << audio.music.volume << "\n";
        file << "  sound:\n";
        file << "    enabled: " << boolToString(audio.sound.enabled) << "\n";
        file << "    volume: " << audio.sound.volume << "\n";
        file << "\n";

        // LOADING
        file << "# LOADING SCREEN\n";
        file << "loading:\n";
        file << "  show: " << boolToString(loading.show) << "\n";
        file << "  show_resource_name: " << boolToString(loading.show_resource_name) << "\n";
        file << "  wait_for_input: " << boolToString(loading.wait_for_input) << "\n";
        file << "\n";

        // GAME
        file << "# GAME\n";
        file << "game:\n";
        file << "  language: " << static_cast<int>(settings.language) << "  # 0: spanish, 1: valencian, 2: english\n";
        file << "  difficulty: " << static_cast<int>(settings.difficulty) << "  # " << static_cast<int>(Difficulty::Code::EASY) << ": easy, " << static_cast<int>(Difficulty::Code::NORMAL) << ": normal, " << static_cast<int>(Difficulty::Code::HARD) << ": hard\n";
        file << "  autofire: " << boolToString(settings.autofire) << "\n";
        file << "  shutdown_enabled: " << boolToString(settings.shutdown_enabled) << "\n";
        file << "  params_file: " << settings.params_file << "\n";
        file << "\n";

        // CONTROLLERS
        file << "# CONTROLLERS\n";
        file << "controllers:\n";
        gamepad_manager.saveToFile(file);
        file << "\n";

        // KEYBOARD
        file << "# KEYBOARD\n";
        file << "keyboard:\n";
        file << "  player: " << static_cast<int>(keyboard.player_id) << "\n";

        file.close();
        return true;
    }

    // Asigna el teclado al jugador
    void setKeyboardToPlayer(Player::Id player_id) {
        keyboard.player_id = player_id;
    }

    // Intercambia el teclado de jugador
    void swapKeyboard() {
        keyboard.player_id = keyboard.player_id == Player::Id::PLAYER1 ? Player::Id::PLAYER2 : Player::Id::PLAYER1;
    }

    // Intercambia los jugadores asignados a los dos primeros mandos
    void swapControllers() {
        gamepad_manager.swapPlayers();
    }

    // Averigua quien está usando el teclado
    auto getPlayerWhoUsesKeyboard() -> Player::Id {
        return keyboard.player_id;
    }

    // Aplica los cambios pendientes copiando los valores a sus variables
    void applyPendingChanges() {
        if (pending_changes.has_pending_changes) {
            settings.language = pending_changes.new_language;
            settings.difficulty = pending_changes.new_difficulty;
            pending_changes.has_pending_changes = false;
        }
    }

    void checkPendingChanges() {
        pending_changes.has_pending_changes = settings.language != pending_changes.new_language ||
            settings.difficulty != pending_changes.new_difficulty;
    }

    // Buscar y asignar un mando disponible por nombre
    auto assignGamepadByName(const std::string& gamepad_name_to_find, Player::Id player_id) -> bool {
        auto found_gamepad = Input::get()->findAvailableGamepadByName(gamepad_name_to_find);

        if (found_gamepad) {
            return gamepad_manager.assignGamepadToPlayer(player_id, found_gamepad, found_gamepad->name);
        }
        return false;
    }

    // Obtener información de un gamepad específico
    auto getGamepadInfo(Player::Id player_id) -> std::string {
        try {
            const auto& gamepad = gamepad_manager.getGamepad(player_id);
            return "Player " + std::to_string(static_cast<int>(player_id)) +
                ": " + (gamepad.name.empty() ? "No gamepad" : gamepad.name);
        } catch (const std::exception&) {
            return "Invalid player";
        }
    }

    // Asigna los mandos físicos basándose en la configuración actual.
    //
    // Tres pases en orden decreciente de prioridad:
    //   1) Match por path exacto: resuelve el caso arcade (dos sticks idénticos
    //      enchufados siempre a los mismos puertos USB).
    //   2) Match por nombre cuando el path no coincidió: recoloca un mando
    //      conocido que se ha reenchufado a otro puerto (ej. DualSense). En ese
    //      caso se refresca el path guardado al nuevo.
    //   3) Reparto en orden de los mandos físicos sobrantes a los slots que
    //      sigan libres.
    void GamepadManager::assignAndLinkGamepads() {
        auto physical_gamepads = Input::get()->getGamepads();

        std::array<std::string, MAX_PLAYERS> desired_paths;
        std::array<std::string, MAX_PLAYERS> desired_names;
        for (size_t i = 0; i < MAX_PLAYERS; ++i) {
            desired_paths[i] = gamepads_[i].path;
            desired_names[i] = gamepads_[i].name;
            gamepads_[i].instance = nullptr;
        }

        std::vector<std::shared_ptr<Input::Gamepad>> assigned_instances;

        assignGamepadsByPath(desired_paths, physical_gamepads, assigned_instances);
        assignGamepadsByName(desired_names, physical_gamepads, assigned_instances);
        assignRemainingGamepads(physical_gamepads, assigned_instances);
        clearUnassignedGamepadSlots();
    }

    // --- PRIMERA PASADA: Intenta asignar mandos basándose en la ruta guardada ---
    void GamepadManager::assignGamepadsByPath(
        const std::array<std::string, MAX_PLAYERS>& desired_paths,
        const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads,  // NOLINT(readability-named-parameter)
        std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances) {
        for (size_t i = 0; i < MAX_PLAYERS; ++i) {
            const std::string& desired_path = desired_paths[i];
            if (desired_path.empty()) {
                continue;
            }

            for (const auto& physical_gamepad : physical_gamepads) {
                if (physical_gamepad->path == desired_path && !isGamepadAssigned(physical_gamepad, assigned_instances)) {
                    gamepads_[i].instance = physical_gamepad;
                    gamepads_[i].name = physical_gamepad->name;

                    assigned_instances.push_back(physical_gamepad);
                    break;
                }
            }
        }
    }

    // --- SEGUNDA PASADA: Match por nombre para slots cuyo path guardado no casó ---
    // Si un slot tiene nombre guardado pero el path no ha coincidido (mando reenchufado
    // a otro puerto, o path del sistema cambiado), lo enlazamos por nombre y
    // refrescamos el path guardado al del dispositivo físico actual.
    void GamepadManager::assignGamepadsByName(
        const std::array<std::string, MAX_PLAYERS>& desired_names,
        const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads,  // NOLINT(readability-named-parameter)
        std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances) {
        for (size_t i = 0; i < MAX_PLAYERS; ++i) {
            if (gamepads_[i].instance != nullptr) {
                continue;
            }
            const std::string& desired_name = desired_names[i];
            if (desired_name.empty()) {
                continue;
            }

            for (const auto& physical_gamepad : physical_gamepads) {
                if (physical_gamepad->name == desired_name && !isGamepadAssigned(physical_gamepad, assigned_instances)) {
                    gamepads_[i].instance = physical_gamepad;
                    gamepads_[i].name = physical_gamepad->name;
                    gamepads_[i].path = physical_gamepad->path;

                    assigned_instances.push_back(physical_gamepad);
                    break;
                }
            }
        }
    }

    // --- TERCERA PASADA: Asigna los mandos físicos restantes a los jugadores libres ---
    void GamepadManager::assignRemainingGamepads(
        const std::vector<std::shared_ptr<Input::Gamepad>>& physical_gamepads,  // NOLINT(readability-named-parameter)
        std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances) {
        for (size_t i = 0; i < MAX_PLAYERS; ++i) {
            if (gamepads_[i].instance != nullptr) {
                continue;
            }

            for (const auto& physical_gamepad : physical_gamepads) {
                if (!isGamepadAssigned(physical_gamepad, assigned_instances)) {
                    gamepads_[i].instance = physical_gamepad;
                    gamepads_[i].name = physical_gamepad->name;
                    gamepads_[i].path = physical_gamepad->path;

                    assigned_instances.push_back(physical_gamepad);
                    break;
                }
            }
        }
    }

    // --- LIMPIEZA FINAL: Limpia la información "fantasma" de los slots no asignados ---
    void GamepadManager::clearUnassignedGamepadSlots() {
        for (auto& gamepad_config : gamepads_) {
            if (gamepad_config.instance == nullptr) {
                gamepad_config.name = Lang::getText("[SERVICE_MENU] NO_CONTROLLER");
                gamepad_config.path = "";
            }
        }
    }

    auto GamepadManager::isGamepadAssigned(
        const std::shared_ptr<Input::Gamepad>& physical_gamepad,
        const std::vector<std::shared_ptr<Input::Gamepad>>& assigned_instances) -> bool {  // NOLINT(readability-named-parameter)
        return std::ranges::any_of(assigned_instances,
            [&physical_gamepad](const auto& assigned) -> auto {
                return assigned == physical_gamepad;
            });
    }

    // Convierte un player id a texto segun Lang
    auto playerIdToString(Player::Id player_id) -> std::string {
        switch (player_id) {
            case Player::Id::PLAYER1:
                return Lang::getText("[SERVICE_MENU] PLAYER1");
            case Player::Id::PLAYER2:
                return Lang::getText("[SERVICE_MENU] PLAYER2");
            default:
                return "";
        }
    }

    // Convierte un texto a player id segun Lang
    auto stringToPlayerId(const std::string& name) -> Player::Id {
        if (name == Lang::getText("[SERVICE_MENU] PLAYER1")) {
            return Player::Id::PLAYER1;
        }
        if (name == Lang::getText("[SERVICE_MENU] PLAYER2")) {
            return Player::Id::PLAYER2;
        }
        return Player::Id::NO_PLAYER;
    }
}  // namespace Options
