#pragma once

#include <cstdint>
#include <cmath>
#include <vector>

// ---- Biome types -------------------------------------------------------
enum class Biome : uint8_t {
    Temperate = 0,  // Grass, dirt, stone
    Desert    = 1,  // Sand, sandstone
    Snow      = 2,  // Snow, ice, stone
    Ocean     = 3,  // Water surface, sand/gravel bottom
    Swamp     = 4,  // Flat, muddy, shallow water patches
    Volcanic  = 5,  // Bare stone, very rough
    Forest    = 6,  // Grassy, hilly
    COUNT     = 7
};

// ---- Planet descriptor -------------------------------------------------
struct Planet {
    float    cx, cy, cz;   // centre in world block coordinates
    int      baseRadius;   // half-size of bounding cube (blocks)
    uint64_t seed;
    Biome    biome;
};

// ---- Universe constants ------------------------------------------------
// One sector = one cube of space that may contain at most one planet.
static constexpr int64_t SECTOR_SIZE      = 400'000;   // blocks per sector side
static constexpr int     PLANET_R_MIN     = 400;       // min planet radius (blocks)
static constexpr int     PLANET_R_MAX     = 8'000;     // max planet radius (blocks)
static constexpr float   RELIEF_AMP_MAX   = 120.0f;    // max terrain relief above base radius

// ---- PlanetManager -----------------------------------------------------
class PlanetManager {
public:
    inline static uint64_t globalSeed_ = 12345ULL;

    // Call once at startup. Stores the universe seed.
    static void init(uint64_t seed) {
        globalSeed_ = seed;
    }

    // ------------------------------------------------------------------
    // Deterministically generate the planet in sector (sx, sy, sz).
    // Returns false when the sector is empty.
    // ------------------------------------------------------------------
    static bool sectorPlanet(int64_t sx, int64_t sy, int64_t sz, Planet& out) {
        // Sector (0,0,0) always holds the spawn planet.
        if (sx == 0 && sy == 0 && sz == 0) {
            out = spawnPlanet();
            return true;
        }

        // Hash sector coords + global seed → unique state for this sector
        uint64_t h = mix3(globalSeed_,
                          static_cast<uint64_t>(sx) ^ 0xFACEFEEDULL,
                          static_cast<uint64_t>(sy) ^ 0xCAFEBABEULL,
                          static_cast<uint64_t>(sz) ^ 0xDEADBEEFULL);

        // ~70 % of sectors contain a planet
        if ((h & 0xFF) >= 179) return false;

        auto next = [&]() -> uint64_t {
            h = h * 6364136223846793005ULL + 1442695040888963407ULL;
            return h;
        };

        // Planet centre: random in the inner 60 % of the sector
        // (20 % margin from each edge prevents surfaces touching across borders)
        const int64_t margin = SECTOR_SIZE / 5;               // 20 %
        const int64_t range  = SECTOR_SIZE - 2 * margin;      // 60 %
        auto coord = [&](int64_t s) -> float {
            return static_cast<float>(s * SECTOR_SIZE + margin
                   + static_cast<int64_t>(next() % static_cast<uint64_t>(range)));
        };
        out.cx = coord(sx);
        out.cy = coord(sy);
        out.cz = coord(sz);

        out.baseRadius = PLANET_R_MIN
                       + static_cast<int>(next() % static_cast<uint64_t>(PLANET_R_MAX - PLANET_R_MIN));
        out.seed  = next();
        out.biome = static_cast<Biome>(next() % static_cast<uint64_t>(static_cast<int>(Biome::COUNT)));
        return true;
    }

    // ------------------------------------------------------------------
    // Return all planets whose surface can reach the AABB
    // [minX,maxX] × [minY,maxY] × [minZ,maxZ].
    // Called from ChunkGenerator (may be called from worker threads — pure/const).
    // ------------------------------------------------------------------
    static std::vector<Planet> planetsNear(
        float minX, float minY, float minZ,
        float maxX, float maxY, float maxZ)
    {
        const float reach = static_cast<float>(PLANET_R_MAX) + RELIEF_AMP_MAX + 2.0f;

        const int64_t sx0 = floorDiv(static_cast<int64_t>(minX - reach), SECTOR_SIZE);
        const int64_t sx1 = floorDiv(static_cast<int64_t>(maxX + reach), SECTOR_SIZE);
        const int64_t sy0 = floorDiv(static_cast<int64_t>(minY - reach), SECTOR_SIZE);
        const int64_t sy1 = floorDiv(static_cast<int64_t>(maxY + reach), SECTOR_SIZE);
        const int64_t sz0 = floorDiv(static_cast<int64_t>(minZ - reach), SECTOR_SIZE);
        const int64_t sz1 = floorDiv(static_cast<int64_t>(maxZ + reach), SECTOR_SIZE);

        std::vector<Planet> result;
        for (int64_t sx = sx0; sx <= sx1; ++sx)
        for (int64_t sy = sy0; sy <= sy1; ++sy)
        for (int64_t sz = sz0; sz <= sz1; ++sz) {
            Planet p;
            if (!sectorPlanet(sx, sy, sz, p)) continue;
            const float pr = static_cast<float>(p.baseRadius) + RELIEF_AMP_MAX + 2.0f;
            if (p.cx + pr < minX || p.cx - pr > maxX) continue;
            if (p.cy + pr < minY || p.cy - pr > maxY) continue;
            if (p.cz + pr < minZ || p.cz - pr > maxZ) continue;
            result.push_back(p);
        }
        return result;
    }

    // ------------------------------------------------------------------
    // Return the nearest planet to (px, py, pz).
    // Searches nearby sectors; falls back to spawn planet if space is empty.
    // ------------------------------------------------------------------
    static Planet nearest(float px, float py, float pz) {
        // Scan ±2 sectors in each axis (covers the widest empty gap possible)
        const int64_t sx0 = floorDiv(static_cast<int64_t>(px), SECTOR_SIZE) - 2;
        const int64_t sx1 = sx0 + 4;
        const int64_t sy0 = floorDiv(static_cast<int64_t>(py), SECTOR_SIZE) - 2;
        const int64_t sy1 = sy0 + 4;
        const int64_t sz0 = floorDiv(static_cast<int64_t>(pz), SECTOR_SIZE) - 2;
        const int64_t sz1 = sz0 + 4;

        Planet best  = spawnPlanet();
        float  best2 = 1e30f;

        for (int64_t sx = sx0; sx <= sx1; ++sx)
        for (int64_t sy = sy0; sy <= sy1; ++sy)
        for (int64_t sz = sz0; sz <= sz1; ++sz) {
            Planet p;
            if (!sectorPlanet(sx, sy, sz, p)) continue;
            float dx = px - p.cx, dy = py - p.cy, dz = pz - p.cz;
            float d2 = dx*dx + dy*dy + dz*dz;
            if (d2 < best2) { best2 = d2; best = p; }
        }
        return best;
    }

    // ------------------------------------------------------------------
    // Gravity "up" = outward normal of the dominant face of the nearest planet.
    // ------------------------------------------------------------------
    static void gravityUp(float px, float py, float pz,
                          float& gx, float& gy, float& gz)
    {
        const Planet p = nearest(px, py, pz);
        const float dx = px - p.cx, dy = py - p.cy, dz = pz - p.cz;
        const float ax = std::abs(dx), ay = std::abs(dy), az = std::abs(dz);
        if (ax >= ay && ax >= az) {
            gx = (dx >= 0.0f) ? 1.0f : -1.0f; gy = 0.0f; gz = 0.0f;
        } else if (ay >= az) {
            gx = 0.0f; gy = (dy >= 0.0f) ? 1.0f : -1.0f; gz = 0.0f;
        } else {
            gx = 0.0f; gy = 0.0f; gz = (dz >= 0.0f) ? 1.0f : -1.0f;
        }
    }

    // Always-available spawn planet (origin, radius 3200, Temperate)
    static Planet spawnPlanet() {
        Planet sp{};
        sp.cx = 0.0f; sp.cy = 0.0f; sp.cz = 0.0f;
        sp.baseRadius = 3200;
        sp.seed  = globalSeed_ ^ 0x11111111ULL;
        sp.biome = Biome::Temperate;
        return sp;
    }

private:
    // Floor-division that handles negative numerators correctly
    static int64_t floorDiv(int64_t a, int64_t b) {
        return a / b - (a % b != 0 && (a ^ b) < 0 ? 1 : 0);
    }

    // Splitmix64-based hash of four 64-bit values
    static uint64_t mix3(uint64_t seed, uint64_t a, uint64_t b, uint64_t c) {
        auto sm = [](uint64_t x) -> uint64_t {
            x ^= x >> 30; x *= 0xBF58476D1CE4E5B9ULL;
            x ^= x >> 27; x *= 0x94D049BB133111EBULL;
            return x ^ (x >> 31);
        };
        return sm(sm(sm(seed ^ a) ^ b) ^ c);
    }
};
