#include "app.h"
#include "chunk_mesh.h"
#include "debug_draw.h"
#include "planet.h"
#include "texture_atlas.h"

#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <stdexcept>

#include "third_party/stb_image.h"
void App::createBuffer(
    VkDeviceSize size,
    VkBufferUsageFlags usage,
    VkMemoryPropertyFlags properties,
    VkBuffer& buffer,
    VkDeviceMemory& bufferMemory
) {
    VkBufferCreateInfo bufferInfo{};
    bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    bufferInfo.size = size;
    bufferInfo.usage = usage;
    bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

    if (vkCreateBuffer(device_, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
        throw std::runtime_error("vkCreateBuffer failed");
    }

    VkMemoryRequirements memRequirements{};
    vkGetBufferMemoryRequirements(device_, buffer, &memRequirements);

    VkMemoryAllocateInfo allocInfo{};
    allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    allocInfo.allocationSize = memRequirements.size;
    allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);

    if (vkAllocateMemory(device_, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) {
        throw std::runtime_error("vkAllocateMemory failed");
    }

    if (vkBindBufferMemory(device_, buffer, bufferMemory, 0) != VK_SUCCESS) {
        throw std::runtime_error("vkBindBufferMemory failed");
    }
}

void App::destroyBuffer(VkBuffer& buffer, VkDeviceMemory& memory) {
    if (buffer != VK_NULL_HANDLE) {
        vkDestroyBuffer(device_, buffer, nullptr);
        buffer = VK_NULL_HANDLE;
    }
    if (memory != VK_NULL_HANDLE) {
        vkFreeMemory(device_, memory, nullptr);
        memory = VK_NULL_HANDLE;
    }
}

// Queue buffer destruction until the GPU is guaranteed done with this frame slot.
// Called from rebuildChunkMesh during the game loop (before drawFrame submission).
// At frame N+MAX_FRAMES_IN_FLIGHT, vkWaitForFences fires for this slot and the
// deletion queue is drained — safe because the GPU finished frame N by then.
void App::destroyBufferDeferred(VkBuffer& buffer, VkDeviceMemory& memory) {
    if (buffer != VK_NULL_HANDLE || memory != VK_NULL_HANDLE) {
        // Queue into the OTHER frame's slot so the buffer lives until both
        // in-flight fences have fired (MAX_FRAMES_IN_FLIGHT frames apart).
        const int safeSlot = (currentFrame_ + MAX_FRAMES_IN_FLIGHT - 1) % MAX_FRAMES_IN_FLIGHT;
        deletionQueues_[safeSlot].push_back({buffer, memory});
        buffer = VK_NULL_HANDLE;
        memory = VK_NULL_HANDLE;
    }
}

void App::destroyChunkMeshes() {
    for (auto& [pos, mesh] : chunkMeshes_) {
        destroyBuffer(mesh.opaqueVertexBuffer, mesh.opaqueVertexBufferMemory);
        destroyBuffer(mesh.opaqueIndexBuffer, mesh.opaqueIndexBufferMemory);
        destroyBuffer(mesh.waterVertexBuffer, mesh.waterVertexBufferMemory);
        destroyBuffer(mesh.waterIndexBuffer, mesh.waterIndexBufferMemory);
    }
    chunkMeshes_.clear();
}

void App::destroyOutlineMesh() {
    destroyBuffer(outlineMesh_.vertexBuffer, outlineMesh_.vertexBufferMemory);
    destroyBuffer(outlineMesh_.indexBuffer, outlineMesh_.indexBufferMemory);
    outlineMesh_.indexCount = 0;
    outlineMesh_.valid = false;
}

void App::rebuildOutlineMesh() {
    // Deferred: the old outline buffers may still be referenced by in-flight frames.
    destroyBufferDeferred(outlineMesh_.vertexBuffer, outlineMesh_.vertexBufferMemory);
    destroyBufferDeferred(outlineMesh_.indexBuffer, outlineMesh_.indexBufferMemory);
    outlineMesh_.indexCount = 0;
    outlineMesh_.valid = false;

    if (!currentHit_.hit) {
        return;
    }

    DebugLineMesh mesh = DebugDraw::makeBlockOutline(
        currentHit_.blockX,
        currentHit_.blockY,
        currentHit_.blockZ
    );

    if (mesh.vertices.empty() || mesh.indices.empty()) {
        return;
    }

    outlineMesh_.indexCount = static_cast<uint32_t>(mesh.indices.size());

    const VkDeviceSize vertexSize = sizeof(DebugLineVertex) * mesh.vertices.size();
    createBuffer(
        vertexSize,
        VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        outlineMesh_.vertexBuffer,
        outlineMesh_.vertexBufferMemory
    );

    void* vertexDst = nullptr;
    vkMapMemory(device_, outlineMesh_.vertexBufferMemory, 0, vertexSize, 0, &vertexDst);
    std::memcpy(vertexDst, mesh.vertices.data(), static_cast<size_t>(vertexSize));
    vkUnmapMemory(device_, outlineMesh_.vertexBufferMemory);

    const VkDeviceSize indexSize = sizeof(std::uint32_t) * mesh.indices.size();
    createBuffer(
        indexSize,
        VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        outlineMesh_.indexBuffer,
        outlineMesh_.indexBufferMemory
    );

    void* indexDst = nullptr;
    vkMapMemory(device_, outlineMesh_.indexBufferMemory, 0, indexSize, 0, &indexDst);
    std::memcpy(indexDst, mesh.indices.data(), static_cast<size_t>(indexSize));
    vkUnmapMemory(device_, outlineMesh_.indexBufferMemory);

    outlineMesh_.valid = true;
}

void App::destroyBreakMesh() {
    destroyBufferDeferred(breakMesh_.vertexBuffer, breakMesh_.vertexBufferMemory);
    destroyBufferDeferred(breakMesh_.indexBuffer, breakMesh_.indexBufferMemory);
    breakMesh_.indexCount = 0;
    breakMesh_.valid = false;
}

void App::rebuildBreakMesh() {
    destroyBufferDeferred(breakMesh_.vertexBuffer, breakMesh_.vertexBufferMemory);
    destroyBufferDeferred(breakMesh_.indexBuffer, breakMesh_.indexBufferMemory);
    breakMesh_.indexCount = 0;
    breakMesh_.valid = false;

    if (breakProgress_ <= 0.0f) return;

    // Vertex format matching the main pipeline
    struct V { float pos[3]; float col[3]; float uv[2]; float ti; };

    const float bx = static_cast<float>(breakX_);
    const float by = static_cast<float>(breakY_);
    const float bz = static_cast<float>(breakZ_);
    constexpr float E = 0.003f; // slight outward expansion so overlay sits outside block

    // 6 faces, 4 verts each, 2 tris each
    V verts[24];
    uint32_t indices[36];
    int vi = 0, ii = 0;

    auto addFace = [&](float ax, float ay, float az,
                       float bx2, float by2, float bz2,
                       float cx, float cy, float cz,
                       float dx, float dy, float dz) {
        // Quad: a(0,0) b(1,0) c(1,1) d(0,1)
        uint32_t base = static_cast<uint32_t>(vi);
        verts[vi++] = { {ax,ay,az}, {1,1,1}, {0,0}, -8.0f };
        verts[vi++] = { {bx2,by2,bz2}, {1,1,1}, {1,0}, -8.0f };
        verts[vi++] = { {cx,cy,cz}, {1,1,1}, {1,1}, -8.0f };
        verts[vi++] = { {dx,dy,dz}, {1,1,1}, {0,1}, -8.0f };
        indices[ii++] = base+0; indices[ii++] = base+1; indices[ii++] = base+2;
        indices[ii++] = base+0; indices[ii++] = base+2; indices[ii++] = base+3;
    };

    // +X face (right)
    addFace(bx+1+E, by,   bz,
            bx+1+E, by,   bz+1,
            bx+1+E, by+1, bz+1,
            bx+1+E, by+1, bz);
    // -X face (left)
    addFace(bx-E,   by,   bz+1,
            bx-E,   by,   bz,
            bx-E,   by+1, bz,
            bx-E,   by+1, bz+1);
    // +Y face (top)
    addFace(bx,   by+1+E, bz,
            bx+1, by+1+E, bz,
            bx+1, by+1+E, bz+1,
            bx,   by+1+E, bz+1);
    // -Y face (bottom)
    addFace(bx,   by-E, bz+1,
            bx+1, by-E, bz+1,
            bx+1, by-E, bz,
            bx,   by-E, bz);
    // +Z face (front)
    addFace(bx+1, by,   bz+1+E,
            bx,   by,   bz+1+E,
            bx,   by+1, bz+1+E,
            bx+1, by+1, bz+1+E);
    // -Z face (back)
    addFace(bx,   by,   bz-E,
            bx+1, by,   bz-E,
            bx+1, by+1, bz-E,
            bx,   by+1, bz-E);

    const VkDeviceSize vsize = sizeof(verts);
    createBuffer(vsize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        breakMesh_.vertexBuffer, breakMesh_.vertexBufferMemory);
    void* vdata = nullptr;
    vkMapMemory(device_, breakMesh_.vertexBufferMemory, 0, vsize, 0, &vdata);
    std::memcpy(vdata, verts, static_cast<size_t>(vsize));
    vkUnmapMemory(device_, breakMesh_.vertexBufferMemory);

    const VkDeviceSize isize = sizeof(indices);
    createBuffer(isize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        breakMesh_.indexBuffer, breakMesh_.indexBufferMemory);
    void* idata = nullptr;
    vkMapMemory(device_, breakMesh_.indexBufferMemory, 0, isize, 0, &idata);
    std::memcpy(idata, indices, static_cast<size_t>(isize));
    vkUnmapMemory(device_, breakMesh_.indexBufferMemory);

    breakMesh_.indexCount = 36;
    breakMesh_.valid = true;
}

void App::rebuildChunkMesh(const ChunkPos& pos) {
    // O(1) lookup — preserve spawnTime and spawnUp so re-meshed chunks don't re-animate.
    float spawnTime = currentTime_;
    Vec3  savedSpawnUp{0.0f, 1.0f, 0.0f};
    bool  hadExisting = false;
    auto existing = chunkMeshes_.find(pos);
    if (existing != chunkMeshes_.end()) {
        spawnTime    = existing->second.spawnTime;
        savedSpawnUp = existing->second.spawnUp;
        hadExisting  = true;
        auto& m = existing->second;
        destroyBufferDeferred(m.opaqueVertexBuffer, m.opaqueVertexBufferMemory);
        destroyBufferDeferred(m.opaqueIndexBuffer, m.opaqueIndexBufferMemory);
        destroyBufferDeferred(m.waterVertexBuffer, m.waterVertexBufferMemory);
        destroyBufferDeferred(m.waterIndexBuffer, m.waterIndexBufferMemory);
        chunkMeshes_.erase(existing);
    }

    if (!world_.getChunk(pos)) {
        return;
    }

    const int lod = getLodLevel(pos);
    ChunkMesh meshData = ChunkMeshBuilder::build(world_, pos, lod);

    if (meshData.opaque.vertices.empty() &&
        meshData.opaque.indices.empty() &&
        meshData.water.vertices.empty() &&
        meshData.water.indices.empty()) {
        return;
    }

    // Compute gravity direction at chunk centre for the spawn animation axis.
    // Preserve spawnUp from the old mesh so re-LOD rebuilds don't flip direction.
    Vec3 chunkSpawnUp = savedSpawnUp;
    if (!hadExisting) {
        const float cx = static_cast<float>(pos.x * Chunk::SIZE + Chunk::SIZE / 2);
        const float cy = static_cast<float>(pos.y * Chunk::SIZE + Chunk::SIZE / 2);
        const float cz = static_cast<float>(pos.z * Chunk::SIZE + Chunk::SIZE / 2);
        float gx, gy, gz;
        PlanetManager::gravityUp(cx, cy, cz, gx, gy, gz);
        chunkSpawnUp = {gx, gy, gz};
    }

    ChunkGpuMesh gpu{};
    gpu.pos = pos;
    gpu.lodLevel = lod;
    gpu.spawnTime = spawnTime;
    gpu.spawnUp   = chunkSpawnUp;

    if (!meshData.opaque.vertices.empty() && !meshData.opaque.indices.empty()) {
        gpu.opaqueIndexCount = static_cast<uint32_t>(meshData.opaque.indices.size());

        const VkDeviceSize vertexSize = sizeof(Vertex) * meshData.opaque.vertices.size();
        createBuffer(
            vertexSize,
            VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
            gpu.opaqueVertexBuffer,
            gpu.opaqueVertexBufferMemory
        );

        void* vertexDst = nullptr;
        vkMapMemory(device_, gpu.opaqueVertexBufferMemory, 0, vertexSize, 0, &vertexDst);
        std::memcpy(vertexDst, meshData.opaque.vertices.data(), static_cast<size_t>(vertexSize));
        vkUnmapMemory(device_, gpu.opaqueVertexBufferMemory);

        const VkDeviceSize indexSize = sizeof(std::uint32_t) * meshData.opaque.indices.size();
        createBuffer(
            indexSize,
            VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
            gpu.opaqueIndexBuffer,
            gpu.opaqueIndexBufferMemory
        );

        void* indexDst = nullptr;
        vkMapMemory(device_, gpu.opaqueIndexBufferMemory, 0, indexSize, 0, &indexDst);
        std::memcpy(indexDst, meshData.opaque.indices.data(), static_cast<size_t>(indexSize));
        vkUnmapMemory(device_, gpu.opaqueIndexBufferMemory);
    }

    if (!meshData.water.vertices.empty() && !meshData.water.indices.empty()) {
        gpu.waterIndexCount = static_cast<uint32_t>(meshData.water.indices.size());

        const VkDeviceSize vertexSize = sizeof(Vertex) * meshData.water.vertices.size();
        createBuffer(
            vertexSize,
            VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
            gpu.waterVertexBuffer,
            gpu.waterVertexBufferMemory
        );

        void* vertexDst = nullptr;
        vkMapMemory(device_, gpu.waterVertexBufferMemory, 0, vertexSize, 0, &vertexDst);
        std::memcpy(vertexDst, meshData.water.vertices.data(), static_cast<size_t>(vertexSize));
        vkUnmapMemory(device_, gpu.waterVertexBufferMemory);

        const VkDeviceSize indexSize = sizeof(std::uint32_t) * meshData.water.indices.size();
        createBuffer(
            indexSize,
            VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
            gpu.waterIndexBuffer,
            gpu.waterIndexBufferMemory
        );

        void* indexDst = nullptr;
        vkMapMemory(device_, gpu.waterIndexBufferMemory, 0, indexSize, 0, &indexDst);
        std::memcpy(indexDst, meshData.water.indices.data(), static_cast<size_t>(indexSize));
        vkUnmapMemory(device_, gpu.waterIndexBufferMemory);
    }

    chunkMeshes_.emplace(pos, std::move(gpu));
}

void App::createChunkMeshes() {
    destroyChunkMeshes();

    for (const auto& [pos, chunk] : world_.chunks()) {
        (void)chunk;
        rebuildChunkMesh(pos);
    }
}

void App::createCrosshairMesh() {
    // Fullscreen triangle covering the entire screen in NDC.
    // texIndex = -2.0 → vertex shader passes pos directly as clip coords,
    // fragment shader uses gl_FragCoord to draw the crosshair and discards
    // all other pixels (preserving terrain underneath).
    struct V { float pos[3]; float col[3]; float uv[2]; float ti; };
    const float w[3] = {1.f, 1.f, 1.f};
    const V verts[3] = {
        {{-1.0f, -1.0f, 0.0f}, {w[0],w[1],w[2]}, {0,0}, -2.0f},
        {{ 3.0f, -1.0f, 0.0f}, {w[0],w[1],w[2]}, {0,0}, -2.0f},
        {{-1.0f,  3.0f, 0.0f}, {w[0],w[1],w[2]}, {0,0}, -2.0f},
    };
    const uint32_t indices[3] = {0, 1, 2};

    VkDeviceSize vsize = sizeof(verts);
    createBuffer(vsize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        crosshairVertexBuffer_, crosshairVertexBufferMemory_);
    void* vdata;
    vkMapMemory(device_, crosshairVertexBufferMemory_, 0, vsize, 0, &vdata);
    std::memcpy(vdata, verts, vsize);
    vkUnmapMemory(device_, crosshairVertexBufferMemory_);

    VkDeviceSize isize = sizeof(indices);
    createBuffer(isize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        crosshairIndexBuffer_, crosshairIndexBufferMemory_);
    void* idata;
    vkMapMemory(device_, crosshairIndexBufferMemory_, 0, isize, 0, &idata);
    std::memcpy(idata, indices, isize);
    vkUnmapMemory(device_, crosshairIndexBufferMemory_);
}

void App::destroyCrosshairMesh() {
    destroyBuffer(crosshairVertexBuffer_, crosshairVertexBufferMemory_);
    destroyBuffer(crosshairIndexBuffer_, crosshairIndexBufferMemory_);
}
