#pragma once

#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <string>
#include <vector>

// Per-world info
struct WorldInfo {
    std::string name;   // directory name (≤20 chars, printable ASCII)
    uint64_t    seed = 0;
};

// Manages the saves/ directory: nickname, world list, per-world player position.
class WorldManager {
public:
    static constexpr int MAX_WORLDS   = 8;
    static constexpr int MAX_NAME_LEN = 20;

    // ── User-writable saves root ───────────────────────────────────────────
    // Windows:  %LOCALAPPDATA%\VoxelRealm\saves
    // Linux:    $XDG_DATA_HOME/VoxelRealm/saves  (or $HOME/.local/share/VoxelRealm/saves)
    // Fallback: ./saves  (current working directory)
    static const std::string& savesRoot() {
        static std::string root = [](){
            std::string base;
#ifdef _WIN32
            if (const char* p = std::getenv("LOCALAPPDATA"))      base = p;
            else if (const char* p2 = std::getenv("APPDATA"))     base = p2;
            if (!base.empty()) base += "\\VoxelRealm\\saves";
#else
            if (const char* p = std::getenv("XDG_DATA_HOME"))     base = p;
            else if (const char* h = std::getenv("HOME"))       { base = h; base += "/.local/share"; }
            if (!base.empty()) base += "/VoxelRealm/saves";
#endif
            if (base.empty()) base = "saves";
            std::error_code ec;
            std::filesystem::create_directories(base, ec);
            return base;
        }();
        return root;
    }
    static std::string savesPath(const std::string& rel) {
        return savesRoot() + "/" + rel;
    }

    // ── Config ─────────────────────────────────────────────────────────────
    static std::string nickname() {
        std::string n = loadLine(configFile("nickname"));
        return n.empty() ? "Player" : n;
    }
    static void setNickname(const std::string& n) {
        ensureConfigDir();
        saveLine(configFile("nickname"), n);
    }

    // ── World list ─────────────────────────────────────────────────────────
    static std::vector<WorldInfo> listWorlds() {
        std::vector<WorldInfo> worlds;
        std::error_code ec;
        const std::string& root = savesRoot();
        std::filesystem::create_directories(root, ec);
        for (auto& e : std::filesystem::directory_iterator(root, ec)) {
            if (!e.is_directory(ec)) continue;
            const std::string name = e.path().filename().string();
            if (name == "config") continue;            // skip config dir
            const std::string seedPath = savesPath(name + "/seed.txt");
            uint64_t seed = 0;
            std::string s = loadLine(seedPath);
            if (!s.empty()) {
                try { seed = std::stoull(s, nullptr, 16); } catch (...) {}
            }
            worlds.push_back({name, seed});
            if (static_cast<int>(worlds.size()) >= MAX_WORLDS) break;
        }
        return worlds;
    }

    static bool createWorld(const std::string& name, uint64_t seed) {
        std::string dir = savesPath(name);
        std::error_code ec;
        std::filesystem::create_directories(dir + "/chunks", ec);
        if (ec) return false;
        char buf[32];
        std::snprintf(buf, sizeof(buf), "%016llx",
                      static_cast<unsigned long long>(seed));
        saveLine(dir + "/seed.txt", buf);
        return true;
    }

    static std::string chunksDir(const std::string& worldName) {
        return savesPath(worldName + "/chunks");
    }

    static void deleteWorld(const std::string& name) {
        std::error_code ec;
        std::filesystem::remove_all(savesPath(name), ec);
    }

    static bool renameWorld(const std::string& oldName, const std::string& newName) {
        if (oldName.empty() || newName.empty()
            || static_cast<int>(newName.size()) > MAX_NAME_LEN) return false;
        std::string newPath = savesPath(newName);
        std::error_code ec;
        if (std::filesystem::exists(newPath, ec)) return false; // name taken
        std::filesystem::rename(savesPath(oldName), newPath, ec);
        return !ec;
    }

    // ── Player position ────────────────────────────────────────────────────
    static void savePlayerPos(const std::string& worldName,
                              double x, double y, double z,
                              float fwdX, float fwdY, float fwdZ)
    {
        std::string path = savesPath(worldName + "/player.bin");
        std::FILE* f = std::fopen(path.c_str(), "wb");
        if (!f) return;
        double pos[3] = {x, y, z};
        float  fwd[3] = {fwdX, fwdY, fwdZ};
        std::fwrite(pos, sizeof(pos), 1, f);
        std::fwrite(fwd, sizeof(fwd), 1, f);
        std::fclose(f);
    }

    static bool loadPlayerPos(const std::string& worldName,
                              double& x, double& y, double& z,
                              float& fwdX, float& fwdY, float& fwdZ)
    {
        std::string path = savesPath(worldName + "/player.bin");
        std::FILE* f = std::fopen(path.c_str(), "rb");
        if (!f) return false;
        double pos[3] = {}; float fwd[3] = {};
        bool ok = std::fread(pos, sizeof(pos), 1, f) == 1
               && std::fread(fwd, sizeof(fwd), 1, f) == 1;
        std::fclose(f);
        if (!ok) return false;
        x = pos[0]; y = pos[1]; z = pos[2];
        fwdX = fwd[0]; fwdY = fwd[1]; fwdZ = fwd[2];
        return true;
    }

    // ── Play time ─────────────────────────────────────────────────────────
    static void savePlayTime(const std::string& worldName, float seconds) {
        std::string path = savesPath(worldName + "/playtime.bin");
        std::FILE* f = std::fopen(path.c_str(), "wb");
        if (!f) return;
        std::fwrite(&seconds, sizeof(seconds), 1, f);
        std::fclose(f);
    }

    static float loadPlayTime(const std::string& worldName) {
        std::string path = savesPath(worldName + "/playtime.bin");
        std::FILE* f = std::fopen(path.c_str(), "rb");
        if (!f) return 0.0f;
        float seconds = 0.0f;
        std::fread(&seconds, sizeof(seconds), 1, f);
        std::fclose(f);
        return seconds;
    }

    // ── Inventory ──────────────────────────────────────────────────────────
    // Slot layout: 16 × { int32 blockType, int32 count }
    struct SaveSlot { int32_t type; int32_t count; };

    static void saveInventory(const std::string& worldName,
                              const SaveSlot slots[16])
    {
        std::string path = savesPath(worldName + "/inventory.bin");
        std::FILE* f = std::fopen(path.c_str(), "wb");
        if (!f) return;
        std::fwrite(slots, sizeof(SaveSlot) * 16, 1, f);
        std::fclose(f);
    }

    static bool loadInventory(const std::string& worldName,
                              SaveSlot slots[16])
    {
        std::string path = savesPath(worldName + "/inventory.bin");
        std::FILE* f = std::fopen(path.c_str(), "rb");
        if (!f) return false;
        bool ok = std::fread(slots, sizeof(SaveSlot) * 16, 1, f) == 1;
        std::fclose(f);
        return ok;
    }

    // ── Glyph encoding helpers (shared with shader's packing scheme) ───────
    // Converts ASCII char to shader glyph code.
    static int charToGlyph(char c) {
        if (c >= '0' && c <= '9') return c - '0';          // 0-9
        if (c == '-') return 10;
        if (c == 'X' || c == 'x') return 11;
        if (c == 'Y' || c == 'y') return 12;
        if (c == 'Z' || c == 'z') return 13;
        if (c >= 'A' && c <= 'W') return 15 + (c - 'A');
        if (c >= 'a' && c <= 'w') return 15 + (c - 'a');
        return 100; // space / unknown
    }

    // Pack up to 12 chars of `name` into 3 × int32 (same encoding as planetName0/1/2).
    static void packName12(const std::string& name,
                           int32_t& w0, int32_t& w1, int32_t& w2)
    {
        // Default: all spaces
        w0 = w1 = w2 = (100 << 24) | (100 << 16) | (100 << 8) | 100;
        for (int i = 0; i < 12 && i < static_cast<int>(name.size()); ++i) {
            int code   = charToGlyph(name[i]);
            int wordIdx = i / 4;
            int bitOff  = 24 - 8 * (i % 4);
            int32_t& word = (wordIdx == 0) ? w0 : (wordIdx == 1) ? w1 : w2;
            word &= ~(0xFF << bitOff);
            word |= (code & 0xFF) << bitOff;
        }
    }

    // Pack up to 24 chars into 6 × int32 (for text input display).
    static void packText24(const std::string& text, int32_t buf[6]) {
        for (int i = 0; i < 6; ++i) buf[i] = (100<<24)|(100<<16)|(100<<8)|100;
        for (int i = 0; i < 24 && i < static_cast<int>(text.size()); ++i) {
            int code    = charToGlyph(text[i]);
            int wordIdx = i / 4;
            int bitOff  = 24 - 8 * (i % 4);
            buf[wordIdx] &= ~(0xFF << bitOff);
            buf[wordIdx] |= (code & 0xFF) << bitOff;
        }
    }

    // Pack up to 16 hex-chars into 4 × int32.
    static void packHex16(const std::string& hex, int32_t buf[4]) {
        for (int i = 0; i < 4; ++i) buf[i] = (100<<24)|(100<<16)|(100<<8)|100;
        for (int i = 0; i < 16 && i < static_cast<int>(hex.size()); ++i) {
            int code    = charToGlyph(hex[i]);
            int wordIdx = i / 4;
            int bitOff  = 24 - 8 * (i % 4);
            buf[wordIdx] &= ~(0xFF << bitOff);
            buf[wordIdx] |= (code & 0xFF) << bitOff;
        }
    }

private:
    static void ensureConfigDir() {
        std::error_code ec;
        std::filesystem::create_directories(savesPath("config"), ec);
    }
    static std::string configFile(const std::string& key) {
        ensureConfigDir();
        return savesPath("config/" + key + ".txt");
    }
    static std::string loadLine(const std::string& path) {
        std::FILE* f = std::fopen(path.c_str(), "r");
        if (!f) return {};
        char buf[256] = {};
        std::fgets(buf, sizeof(buf), f);
        std::fclose(f);
        std::string s(buf);
        while (!s.empty() && (s.back() == '\n' || s.back() == '\r' || s.back() == ' '))
            s.pop_back();
        return s;
    }
    static void saveLine(const std::string& path, const std::string& val) {
        std::FILE* f = std::fopen(path.c_str(), "w");
        if (!f) return;
        std::fprintf(f, "%s\n", val.c_str());
        std::fclose(f);
    }
};
