#include "core/input/key_config.hpp"

#include <algorithm>
#include <fstream>
#include <iostream>
#include <utility>

#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"

namespace KeyConfig {

    namespace {
        std::vector<KeyEntry> key_entries;
        std::unordered_map<std::string, size_t> index_table;
        std::string overrides_path;

        auto findIndex(const std::string& id) -> size_t {
            auto it = index_table.find(id);
            if (it == index_table.end()) {
                return SIZE_MAX;
            }
            return it->second;
        }

        void loadDefaults(const std::string& defaults_resource_path) {
            auto buf = ResourceHelper::loadFile(defaults_resource_path);
            if (buf.empty()) {
                std::cerr << "KeyConfig: no s'ha pogut llegir " << defaults_resource_path << '\n';
                return;
            }

            std::string content(buf.begin(), buf.end());
            try {
                auto yaml = fkyaml::node::deserialize(content);
                if (!yaml.contains("keys")) {
                    return;
                }

                for (const auto& node : yaml["keys"]) {
                    KeyEntry entry;
                    entry.id = node["id"].get_value<std::string>();
                    entry.code = node["code"].get_value<std::string>();
                    if (node.contains("desc")) {
                        entry.desc = node["desc"].get_value<std::string>();
                    }
                    SDL_Scancode sc = SDL_GetScancodeFromName(entry.code.c_str());
                    if (sc == SDL_SCANCODE_UNKNOWN) {
                        std::cerr << "KeyConfig: scancode desconegut '" << entry.code
                                  << "' per '" << entry.id << "'\n";
                    }
                    entry.scancode = sc;
                    entry.default_scancode = sc;

                    index_table[entry.id] = key_entries.size();
                    key_entries.push_back(std::move(entry));
                }
                std::cout << "KeyConfig: " << key_entries.size() << " tecles carregades de "
                          << defaults_resource_path << '\n';
            } catch (const fkyaml::exception& e) {
                std::cerr << "KeyConfig: error parsejant YAML: " << e.what() << '\n';
            }
        }

        void applyOverrides(const std::string& disk_path) {
            std::ifstream file(disk_path);
            if (!file.good()) {
                return;
            }

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

            try {
                auto yaml = fkyaml::node::deserialize(content);
                if (!yaml.contains("overrides")) {
                    return;
                }

                int applied = 0;
                for (const auto& kv : yaml["overrides"].as_map()) {
                    auto id = kv.first.get_value<std::string>();
                    auto code = kv.second.get_value<std::string>();
                    auto idx = findIndex(id);
                    if (idx == SIZE_MAX) {
                        std::cerr << "KeyConfig: override per id desconegut '" << id << "'\n";
                        continue;
                    }
                    SDL_Scancode sc = SDL_GetScancodeFromName(code.c_str());
                    if (sc == SDL_SCANCODE_UNKNOWN) {
                        std::cerr << "KeyConfig: override amb scancode invàlid '" << code
                                  << "' per '" << id << "'\n";
                        continue;
                    }
                    key_entries[idx].scancode = sc;
                    key_entries[idx].code = code;
                    applied++;
                }
                if (applied > 0) {
                    std::cout << "KeyConfig: aplicats " << applied
                              << " overrides de " << disk_path << '\n';
                }
            } catch (const fkyaml::exception& e) {
                std::cerr << "KeyConfig: error parsejant overrides: " << e.what() << '\n';
            }
        }
    }  // namespace

    void init(const std::string& defaults_resource_path,
        const std::string& user_overrides_disk_path) {
        key_entries.clear();
        index_table.clear();
        overrides_path = user_overrides_disk_path;

        loadDefaults(defaults_resource_path);
        if (!overrides_path.empty()) {
            applyOverrides(overrides_path);
        }
    }

    void destroy() {
        key_entries.clear();
        index_table.clear();
        overrides_path.clear();
    }

    auto scancode(const std::string& id) -> SDL_Scancode {
        auto idx = findIndex(id);
        if (idx == SIZE_MAX) {
            return SDL_SCANCODE_UNKNOWN;
        }
        return key_entries[idx].scancode;
    }

    auto scancodePtr(const std::string& id) -> SDL_Scancode* {
        auto idx = findIndex(id);
        if (idx == SIZE_MAX) {
            return nullptr;
        }
        return &key_entries[idx].scancode;
    }

    void setScancode(const std::string& id, SDL_Scancode sc) {
        auto idx = findIndex(id);
        if (idx == SIZE_MAX) {
            return;
        }
        key_entries[idx].scancode = sc;
        const char* name = SDL_GetScancodeName(sc);
        key_entries[idx].code = (name != nullptr) ? name : "";
    }

    auto isGuiKey(SDL_Scancode sc) -> bool {
        if (sc == SDL_SCANCODE_UNKNOWN) {
            return false;
        }
        return std::ranges::any_of(key_entries, [sc](const auto& e) { return e.scancode == sc; });
    }

    auto entries() -> const std::vector<KeyEntry>& {
        return key_entries;
    }

    auto saveOverrides() -> bool {
        if (overrides_path.empty()) {
            return false;
        }

        // Recull només les entrades remapeades.
        std::vector<const KeyEntry*> changed;
        for (const auto& e : key_entries) {
            if (e.scancode != e.default_scancode) {
                changed.push_back(&e);
            }
        }

        std::ofstream file(overrides_path);
        if (!file.is_open()) {
            std::cerr << "KeyConfig: no es pot escriure " << overrides_path << '\n';
            return false;
        }

        file << "# AEE - Overrides de tecles d'UI\n";
        file << "# Auto-generat. Només llista les tecles modificades respecte\n";
        file << "# els valors per defecte de data/input/keys.yaml.\n";
        file << "\n";

        if (changed.empty()) {
            file << "overrides: {}\n";
        } else {
            file << "overrides:\n";
            for (const auto* e : changed) {
                file << "  " << e->id << ": \"" << e->code << "\"\n";
            }
        }
        return true;
    }

}  // namespace KeyConfig
