#pragma once

#include "chunk.h"

#include <cstddef>
#include <cstdint>
#include <deque>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

struct ChunkPosHash {
    std::size_t operator()(const ChunkPos& p) const noexcept;
};

struct WorldCell {
    int x{};
    int y{};
    int z{};

    bool operator==(const WorldCell& other) const = default;
};

struct WorldCellHash {
    std::size_t operator()(const WorldCell& c) const noexcept {
        std::size_t hx = static_cast<std::size_t>(static_cast<std::uint32_t>(c.x * 73856093));
        std::size_t hy = static_cast<std::size_t>(static_cast<std::uint32_t>(c.y * 19349663));
        std::size_t hz = static_cast<std::size_t>(static_cast<std::uint32_t>(c.z * 83492791));
        return hx ^ hy ^ hz;
    }
};

class World {
public:
    // Seed used by ChunkGenerator. Must be set before any chunks are loaded.
    static void setSeed(uint64_t seed);
    static uint64_t getSeed();

    World();

    const Chunk* getChunk(const ChunkPos& pos) const;
    Chunk* getChunk(const ChunkPos& pos);
    bool hasChunk(const ChunkPos& pos) const;
    void ensureChunk(const ChunkPos& pos);
    void addPrebuiltChunk(Chunk chunk); // insert chunk built off main thread
    void markDirty(const ChunkPos& pos); // mark chunk for GPU mesh rebuild
    void unloadChunk(const ChunkPos& pos);
    void saveAllModified();   // flush all player-modified chunks to disk

    ChunkPos worldToChunkPos(int worldX, int worldY, int worldZ) const;

    BlockType getBlock(int worldX, int worldY, int worldZ) const;
    void setBlock(int worldX, int worldY, int worldZ, BlockType type);

    bool isBlockSubdivided(int worldX, int worldY, int worldZ) const;
    BlockType getVoxel(int worldX, int worldY, int worldZ, int voxelX, int voxelY, int voxelZ) const;
    void setVoxel(int worldX, int worldY, int worldZ, int voxelX, int voxelY, int voxelZ, BlockType type);

    std::uint8_t getWaterLevel(int worldX, int worldY, int worldZ) const;
    void setWaterLevel(int worldX, int worldY, int worldZ, std::uint8_t level);
    bool hasWater(int worldX, int worldY, int worldZ) const;
    void removeWater(int worldX, int worldY, int worldZ);

    void tickWater(int maxUpdatesPerTick = 64);

    std::vector<ChunkPos> consumeDirtyChunks(int limit = 0); // 0 = all
    const std::unordered_map<ChunkPos, Chunk, ChunkPosHash>& chunks() const;

    // Player-placed leaves tracking
    void  markBlockPlayerPlaced(int worldX, int worldY, int worldZ);
    bool  isBlockPlayerPlaced(int worldX, int worldY, int worldZ) const;
    void  clearBlockPlayerPlaced(int worldX, int worldY, int worldZ);

private:
    static void worldToLocal(
        int worldX, int worldY, int worldZ,
        ChunkPos& chunkPos,
        int& lx, int& ly, int& lz
    );

    void enqueueWaterUpdate(int worldX, int worldY, int worldZ);
    void enqueueWaterUpdateAround(int worldX, int worldY, int worldZ);
    void processWaterCell(const WorldCell& cell);

    void markChunkDirty(const ChunkPos& pos);
    void markChunkDirtyByWorld(int worldX, int worldY, int worldZ);
    void markChunkAndNeighborsDirtyByWorld(int worldX, int worldY, int worldZ);

    std::unordered_map<ChunkPos, Chunk, ChunkPosHash> chunks_;

    // Tracks chunks modified by the player (not procedurally generated).
    // Only these need to be persisted to disk on unload.
    std::unordered_set<ChunkPos, ChunkPosHash> playerModifiedChunks_;

    std::deque<WorldCell> pendingWaterUpdates_;
    std::unordered_set<WorldCell, WorldCellHash> pendingWaterUpdateSet_;

    std::vector<ChunkPos> dirtyChunks_;
    std::unordered_set<ChunkPos, ChunkPosHash> dirtyChunkSet_;
};
