#include "game/options.hpp"

#include <SDL3/SDL.h>

#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>

#include "core/input/input.h"        // for INPUT_USE_KEYBOARD, INPUT_USE_GAMECONTROLLER
#include "core/locale/lang.h"        // for MAX_LANGUAGES, en_UK
#include "external/fkyaml_node.hpp"  // for fkyaml::node
#include "utils/utils.h"             // for boolToString

namespace Options {

    // --- Variables globales ---
    Window window;
    Video video;
    Audio audio;
    Loading loading;
    Settings settings;
    std::vector<input_t> inputs;

    std::vector<PostFXPreset> postfx_presets;
    std::string postfx_file_path;
    int current_postfx_preset = 0;

    std::vector<CrtPiPreset> crtpi_presets;
    std::string crtpi_file_path;
    int current_crtpi_preset = 0;

    // --- Helpers locals ---
    namespace {
        void parseBoolField(const fkyaml::node &node, const std::string &key, bool &target) {
            if (node.contains(key)) {
                try {
                    target = node[key].get_value<bool>();
                } catch (...) {}
            }
        }

        void parseIntField(const fkyaml::node &node, const std::string &key, int &target) {
            if (node.contains(key)) {
                try {
                    target = node[key].get_value<int>();
                } catch (...) {}
            }
        }

        void parseStringField(const fkyaml::node &node, const std::string &key, std::string &target) {
            if (node.contains(key)) {
                try {
                    target = node[key].get_value<std::string>();
                } catch (...) {}
            }
        }

        void loadWindowFromYaml(const fkyaml::node &yaml) {
            if (!yaml.contains("window")) { return; }
            const auto &win = yaml["window"];
            parseIntField(win, "zoom", window.zoom);
            if (window.zoom < 1 || window.zoom > window.max_zoom) {
                window.zoom = Defaults::Window::ZOOM;
            }
        }

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

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

            if (vid.contains("gpu")) {
                const auto &gpu = vid["gpu"];
                parseBoolField(gpu, "acceleration", video.gpu.acceleration);
                parseStringField(gpu, "preferred_driver", video.gpu.preferred_driver);
            }

            if (vid.contains("supersampling")) {
                const auto &ss = vid["supersampling"];
                parseBoolField(ss, "enabled", video.supersampling.enabled);
                parseBoolField(ss, "linear_upscale", video.supersampling.linear_upscale);
                parseIntField(ss, "downscale_algo", video.supersampling.downscale_algo);
            }

            if (vid.contains("shader")) {
                const auto &sh = vid["shader"];
                parseBoolField(sh, "enabled", video.shader.enabled);
                if (sh.contains("current_shader")) {
                    try {
                        auto s = sh["current_shader"].get_value<std::string>();
                        video.shader.current_shader = (s == "crtpi" || s == "CRTPI")
                            ? Rendering::ShaderType::CRTPI
                            : Rendering::ShaderType::POSTFX;
                    } catch (...) {}
                }
                parseStringField(sh, "current_postfx_preset", video.shader.current_postfx_preset_name);
                parseStringField(sh, "current_crtpi_preset", video.shader.current_crtpi_preset_name);
            }
        }

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

            parseBoolField(aud, "enabled", audio.enabled);
            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"];
                parseBoolField(mus, "enabled", audio.music.enabled);
                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"];
                parseBoolField(snd, "enabled", audio.sound.enabled);
                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 loadSettingsFromYaml(const fkyaml::node &yaml) {
            if (!yaml.contains("settings")) { return; }
            const auto &st = yaml["settings"];
            parseIntField(st, "difficulty", settings.difficulty);
            parseIntField(st, "language", settings.language);
            if (settings.language < 0 || settings.language > MAX_LANGUAGES) {
                settings.language = en_UK;
            }
            if (st.contains("palette")) {
                try {
                    settings.palette = static_cast<palette_e>(st["palette"].get_value<int>());
                } catch (...) {}
            }
            parseIntField(st, "player_selected", settings.player_selected);
        }

        void loadInputsFromYaml(const fkyaml::node &yaml) {
            if (!yaml.contains("input") || inputs.size() < 2) { return; }
            const auto &ins = yaml["input"];
            size_t i = 0;
            for (const auto &entry : ins) {
                if (i >= inputs.size()) { break; }
                if (entry.contains("device_type")) {
                    try {
                        inputs[i].deviceType = static_cast<Uint8>(entry["device_type"].get_value<int>());
                    } catch (...) {}
                }
                ++i;
            }
        }
    }  // namespace

    // --- Funciones públiques ---

    void setConfigFile(const std::string &file_path) {
        settings.config_file = file_path;
    }

    void init() {
        // Reinicia structs a defaults (els member-initializers ho fan sols).
        window = Window{};
        video = Video{};
        audio = Audio{};
        loading = Loading{};

        // Preserva config_file si ja s'ha establert abans.
        const std::string PREV_CONFIG_FILE = settings.config_file;
        settings = Settings{};
        settings.config_file = PREV_CONFIG_FILE;

#ifdef __EMSCRIPTEN__
        // En Emscripten la ventana la gestiona el navegador
        window.zoom = 4;
        video.fullscreen = false;
        video.integer_scale = true;
#endif

        // Dispositius d'entrada per defecte
        inputs.clear();
        input_t kb;
        kb.id = 0;
        kb.name = "KEYBOARD";
        kb.deviceType = INPUT_USE_KEYBOARD;
        inputs.push_back(kb);

        input_t gc;
        gc.id = 0;
        gc.name = "GAME CONTROLLER";
        gc.deviceType = INPUT_USE_GAMECONTROLLER;
        inputs.push_back(gc);
    }

    auto loadFromFile() -> bool {
        init();

        std::ifstream file(settings.config_file);
        if (!file.is_open()) {
            // Primera execució: crea el YAML amb defaults.
            return saveToFile();
        }

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

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

            int file_version = 0;
            if (yaml.contains("config_version")) {
                try {
                    file_version = yaml["config_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();
                return saveToFile();
            }

            loadWindowFromYaml(yaml);
            loadVideoFromYaml(yaml);
            loadAudioFromYaml(yaml);
            loadLoadingFromYaml(yaml);
            loadSettingsFromYaml(yaml);
            loadInputsFromYaml(yaml);

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

        return true;
    }

    auto saveToFile() -> bool {
        if (settings.config_file.empty()) { return false; }
        std::ofstream file(settings.config_file);
        if (!file.is_open()) {
            std::cout << "Error: " << settings.config_file << " can't be opened for writing\n";
            return false;
        }

        file << "# Coffee Crisis - Configuration file\n";
        file << "# Auto-generated, managed by the game.\n\n";

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

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

        // VIDEO
        file << "# VIDEO\n";
        file << "video:\n";
        file << "  fullscreen: " << boolToString(video.fullscreen) << "\n";
        file << "  vsync: " << boolToString(video.vsync) << "\n";
        file << "  integer_scale: " << boolToString(video.integer_scale) << "\n";
        file << "  scale_mode: " << static_cast<int>(video.scale_mode)
             << "  # " << static_cast<int>(SDL_SCALEMODE_NEAREST) << ": nearest, "
             << static_cast<int>(SDL_SCALEMODE_LINEAR) << ": linear\n";
        file << "  gpu:\n";
        file << "    acceleration: " << boolToString(video.gpu.acceleration) << "\n";
        file << "    preferred_driver: \"" << video.gpu.preferred_driver << "\"\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 << "  shader:\n";
        file << "    enabled: " << boolToString(video.shader.enabled) << "\n";
        file << "    current_shader: "
             << (video.shader.current_shader == Rendering::ShaderType::CRTPI ? "crtpi" : "postfx")
             << "\n";
        file << "    current_postfx_preset: \"" << video.shader.current_postfx_preset_name << "\"\n";
        file << "    current_crtpi_preset: \"" << video.shader.current_crtpi_preset_name << "\"\n\n";

        // AUDIO
        file << "# AUDIO (volume range: 0.0..1.0)\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\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\n";

        // SETTINGS
        file << "# SETTINGS\n";
        file << "settings:\n";
        file << "  difficulty: " << settings.difficulty << "\n";
        file << "  language: " << settings.language << "\n";
        file << "  palette: " << static_cast<int>(settings.palette) << "\n";
        file << "  player_selected: " << settings.player_selected << "\n\n";

        // INPUT
        file << "# INPUT DEVICES (device_type: "
             << static_cast<int>(INPUT_USE_KEYBOARD) << "=KEYBOARD, "
             << static_cast<int>(INPUT_USE_GAMECONTROLLER) << "=GAMECONTROLLER)\n";
        file << "input:\n";
        for (size_t i = 0; i < inputs.size(); ++i) {
            file << "  - slot: " << i << "\n";
            file << "    device_type: " << static_cast<int>(inputs[i].deviceType) << "\n";
        }

        file.close();
        return true;
    }

    // ========================================================================
    // Presets de shaders (postfx.yaml / crtpi.yaml)
    //
    // Els defaults viuen en una única font (defaultPostFXPresets / defaultCrtPiPresets).
    // Generem el YAML a partir d'ells el primer cop i els usem també com a
    // fallback si el YAML és absent o corrupte. Si algú toca els valors, ho fa
    // en un sol lloc.
    // ========================================================================

    namespace {
        void parseFloatField(const fkyaml::node &node, const std::string &key, float &target) {
            if (node.contains(key)) {
                try {
                    target = node[key].get_value<float>();
                } catch (...) {}
            }
        }

        auto defaultPostFXPresets() -> const std::vector<PostFXPreset> & {
            static const std::vector<PostFXPreset> DEFAULTS = {
                {"CRT", 0.6F, 0.7F, 0.15F, 0.6F, 0.8F},
                {"NTSC", 0.4F, 0.5F, 0.2F, 0.4F, 0.5F, 0.0F, 0.6F},
                {"CURVED", 0.5F, 0.6F, 0.1F, 0.5F, 0.7F, 0.8F},
                {"SCANLINES", 0.0F, 0.8F},
                {"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.3F},
                {"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F},
            };
            return DEFAULTS;
        }

        auto defaultCrtPiPresets() -> const std::vector<CrtPiPreset> & {
            static const std::vector<CrtPiPreset> DEFAULTS = {
                {"Default", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, false, false},
                {"Curved", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false},
                {"Sharp", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true},
                {"Minimal", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false},
            };
            return DEFAULTS;
        }

        void writePostFXDefaults(std::ostream &out) {
            out << "# Coffee Crisis - PostFX Shader Presets\n\n";
            out << "presets:\n";
            for (const auto &p : defaultPostFXPresets()) {
                out << "  - name: \"" << p.name << "\"\n";
                out << "    vignette: " << p.vignette << "\n";
                out << "    scanlines: " << p.scanlines << "\n";
                out << "    chroma: " << p.chroma << "\n";
                out << "    mask: " << p.mask << "\n";
                out << "    gamma: " << p.gamma << "\n";
                out << "    curvature: " << p.curvature << "\n";
                out << "    bleeding: " << p.bleeding << "\n";
                out << "    flicker: " << p.flicker << "\n";
            }
        }

        void writeCrtPiDefaults(std::ostream &out) {
            out << "# Coffee Crisis - CrtPi Shader Presets\n\n";
            out << "presets:\n";
            for (const auto &p : defaultCrtPiPresets()) {
                out << "  - name: \"" << p.name << "\"\n";
                out << "    scanline_weight: " << p.scanline_weight << "\n";
                out << "    scanline_gap_brightness: " << p.scanline_gap_brightness << "\n";
                out << "    bloom_factor: " << p.bloom_factor << "\n";
                out << "    input_gamma: " << p.input_gamma << "\n";
                out << "    output_gamma: " << p.output_gamma << "\n";
                out << "    mask_brightness: " << p.mask_brightness << "\n";
                out << "    curvature_x: " << p.curvature_x << "\n";
                out << "    curvature_y: " << p.curvature_y << "\n";
                out << "    mask_type: " << p.mask_type << "\n";
                out << "    enable_scanlines: " << boolToString(p.enable_scanlines) << "\n";
                out << "    enable_multisample: " << boolToString(p.enable_multisample) << "\n";
                out << "    enable_gamma: " << boolToString(p.enable_gamma) << "\n";
                out << "    enable_curvature: " << boolToString(p.enable_curvature) << "\n";
                out << "    enable_sharper: " << boolToString(p.enable_sharper) << "\n";
            }
        }
    }  // namespace

    void setPostFXFile(const std::string &path) {
        postfx_file_path = path;
    }

    auto loadPostFXFromFile() -> bool {
        postfx_presets.clear();
        current_postfx_preset = 0;

        std::ifstream file(postfx_file_path);
        if (!file.is_open()) {
            // No existeix: escriu el YAML a partir dels defaults i copia'ls a memòria.
            std::ofstream out(postfx_file_path);
            if (out.is_open()) {
                writePostFXDefaults(out);
                out.close();
            }
            postfx_presets = defaultPostFXPresets();
            return true;
        }

        const 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")) {
                for (const auto &p : yaml["presets"]) {
                    PostFXPreset preset;
                    if (p.contains("name")) {
                        try {
                            preset.name = p["name"].get_value<std::string>();
                        } catch (...) {}
                    }
                    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);
                }
            }
            std::cout << "PostFX loaded: " << postfx_presets.size() << " preset(s)\n";
        } catch (const fkyaml::exception &e) {
            std::cout << "Error parsing PostFX YAML: " << e.what() << ". Using defaults.\n";
            postfx_presets = defaultPostFXPresets();
            return false;
        }

        if (postfx_presets.empty()) {
            postfx_presets = defaultPostFXPresets();
        }
        return true;
    }

    void setCrtPiFile(const std::string &path) {
        crtpi_file_path = path;
    }

    auto loadCrtPiFromFile() -> bool {
        crtpi_presets.clear();
        current_crtpi_preset = 0;

        std::ifstream file(crtpi_file_path);
        if (!file.is_open()) {
            std::ofstream out(crtpi_file_path);
            if (out.is_open()) {
                writeCrtPiDefaults(out);
                out.close();
            }
            crtpi_presets = defaultCrtPiPresets();
            return true;
        }

        const 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")) {
                for (const auto &p : yaml["presets"]) {
                    CrtPiPreset preset;
                    if (p.contains("name")) {
                        try {
                            preset.name = p["name"].get_value<std::string>();
                        } catch (...) {}
                    }
                    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);
                    if (p.contains("mask_type")) {
                        try {
                            preset.mask_type = p["mask_type"].get_value<int>();
                        } catch (...) {}
                    }
                    if (p.contains("enable_scanlines")) {
                        try {
                            preset.enable_scanlines = p["enable_scanlines"].get_value<bool>();
                        } catch (...) {}
                    }
                    if (p.contains("enable_multisample")) {
                        try {
                            preset.enable_multisample = p["enable_multisample"].get_value<bool>();
                        } catch (...) {}
                    }
                    if (p.contains("enable_gamma")) {
                        try {
                            preset.enable_gamma = p["enable_gamma"].get_value<bool>();
                        } catch (...) {}
                    }
                    if (p.contains("enable_curvature")) {
                        try {
                            preset.enable_curvature = p["enable_curvature"].get_value<bool>();
                        } catch (...) {}
                    }
                    if (p.contains("enable_sharper")) {
                        try {
                            preset.enable_sharper = p["enable_sharper"].get_value<bool>();
                        } catch (...) {}
                    }
                    crtpi_presets.push_back(preset);
                }
            }
            std::cout << "CrtPi loaded: " << crtpi_presets.size() << " preset(s)\n";
        } catch (const fkyaml::exception &e) {
            std::cout << "Error parsing CrtPi YAML: " << e.what() << ". Using defaults.\n";
            crtpi_presets = defaultCrtPiPresets();
            return false;
        }

        if (crtpi_presets.empty()) {
            crtpi_presets = defaultCrtPiPresets();
        }
        return true;
    }

}  // namespace Options
