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

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

#include "third_party/stb_image.h"

static constexpr float SKY_PLANET_DIST  = 900.0f;
static constexpr float SKY_PLANET_SCALE = 10.0f;

void App::createUniformBuffers() {
    const VkDeviceSize bufferSize = sizeof(UniformBufferObject);

    uniformBuffers_.resize(swapchainImages_.size());
    uniformBuffersMemory_.resize(swapchainImages_.size());

    for (std::size_t i = 0; i < swapchainImages_.size(); ++i) {
        createBuffer(
            bufferSize,
            VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
            uniformBuffers_[i],
            uniformBuffersMemory_[i]
        );
    }
}
void App::createDescriptorPool() {
    const uint32_t totalSets = static_cast<uint32_t>(swapchainImages_.size());

    std::array<VkDescriptorPoolSize, 2> poolSizes{};
    poolSizes[0].type            = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
    poolSizes[0].descriptorCount = totalSets;
    poolSizes[1].type            = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
    poolSizes[1].descriptorCount = totalSets;

    VkDescriptorPoolCreateInfo poolInfo{};
    poolInfo.sType         = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
    poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
    poolInfo.pPoolSizes    = poolSizes.data();
    poolInfo.maxSets       = totalSets;

    if (vkCreateDescriptorPool(device_, &poolInfo, nullptr, &descriptorPool_) != VK_SUCCESS) {
        throw std::runtime_error("vkCreateDescriptorPool failed");
    }
}
void App::createDescriptorSets() {
    std::vector<VkDescriptorSetLayout> layouts(swapchainImages_.size(), descriptorSetLayout_);

    VkDescriptorSetAllocateInfo allocInfo{};
    allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
    allocInfo.descriptorPool = descriptorPool_;
    allocInfo.descriptorSetCount = static_cast<uint32_t>(swapchainImages_.size());
    allocInfo.pSetLayouts = layouts.data();

    descriptorSets_.resize(swapchainImages_.size());

    if (vkAllocateDescriptorSets(device_, &allocInfo, descriptorSets_.data()) != VK_SUCCESS) {
        throw std::runtime_error("vkAllocateDescriptorSets failed");
    }

    for (std::size_t i = 0; i < swapchainImages_.size(); ++i) {
        VkDescriptorBufferInfo bufferInfo{};
        bufferInfo.buffer = uniformBuffers_[i];
        bufferInfo.offset = 0;
        bufferInfo.range = sizeof(UniformBufferObject);

        VkDescriptorImageInfo imageInfo{};
        imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
        imageInfo.imageView = textureAtlasImageView_;
        imageInfo.sampler = textureAtlasSampler_;

        std::array<VkWriteDescriptorSet, 2> descriptorWrites{};

        descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
        descriptorWrites[0].dstSet = descriptorSets_[i];
        descriptorWrites[0].dstBinding = 0;
        descriptorWrites[0].dstArrayElement = 0;
        descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
        descriptorWrites[0].descriptorCount = 1;
        descriptorWrites[0].pBufferInfo = &bufferInfo;

        descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
        descriptorWrites[1].dstSet = descriptorSets_[i];
        descriptorWrites[1].dstBinding = 1;
        descriptorWrites[1].dstArrayElement = 0;
        descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
        descriptorWrites[1].descriptorCount = 1;
        descriptorWrites[1].pImageInfo = &imageInfo;

        vkUpdateDescriptorSets(
            device_,
            static_cast<uint32_t>(descriptorWrites.size()),
            descriptorWrites.data(),
            0,
            nullptr
        );
    }
}
void App::updateUniformBuffer(uint32_t imageIndex) {
    UniformBufferObject ubo{};

    // Camera-relative rendering: view has only rotation, no translation.
    // The translation (world → camera-relative) is pushed per-draw as the model matrix.
    float view[16];
    camera_.buildViewRotationMatrix(view);

    const float aspect = static_cast<float>(swapchainExtent_.width) /
                         static_cast<float>(swapchainExtent_.height);
    const float fov = 3.1415926f / 4.0f;
    const float nearZ = 0.1f;
    const float farZ = 1024.0f;
    const float f = 1.0f / std::tan(fov * 0.5f);

    float proj[16] = {
        f / aspect, 0.0f, 0.0f,  0.0f,
        0.0f,      -f,   0.0f,  0.0f,
        0.0f,       0.0f, farZ / (nearZ - farZ), -1.0f,
        0.0f,       0.0f, (nearZ * farZ) / (nearZ - farZ), 0.0f
    };

    auto mul = [](const float a[16], const float b[16], float out[16]) {
        for (int col = 0; col < 4; ++col) {
            for (int row = 0; row < 4; ++row) {
                out[col * 4 + row] =
                    a[0 * 4 + row] * b[col * 4 + 0] +
                    a[1 * 4 + row] * b[col * 4 + 1] +
                    a[2 * 4 + row] * b[col * 4 + 2] +
                    a[3 * 4 + row] * b[col * 4 + 3];
            }
        }
    };

    float vp[16]{};
    mul(proj, view, vp);
    std::memcpy(ubo.mvp, vp, sizeof(ubo.mvp));
    std::memcpy(lastMvp_, vp, sizeof(lastMvp_));
    ubo.screenWidth  = static_cast<float>(swapchainExtent_.width);
    ubo.screenHeight = static_cast<float>(swapchainExtent_.height);
    ubo.playerX = static_cast<int32_t>(std::floor(camera_.position.x));
    ubo.playerY = static_cast<int32_t>(std::floor(camera_.position.y));
    ubo.playerZ = static_cast<int32_t>(std::floor(camera_.position.z));

    // ── Time of day ───────────────────────────────────────────────────────
    {
        constexpr float DAY_DURATION = 3600.0f; // 1-hour full cycle
        dayTime_ = std::fmod(currentTime_ / DAY_DURATION, 1.0f);

        // Sun angle: 0.0 = sunrise, 0.25 = noon, 0.5 = sunset, 0.75 = midnight
        const float ang = (dayTime_ - 0.25f) * 2.0f * 3.14159265f;
        float sx  =  std::sin(ang) * 0.90f;   // east at dawn, west at dusk
        float sy  =  std::cos(ang);            // up at noon, down at midnight
        float sz  =  0.35f;                    // slight fixed forward offset
        float len =  std::sqrt(sx*sx + sy*sy + sz*sz);
        sunDirX_ = sx / len;  sunDirY_ = sy / len;  sunDirZ_ = sz / len;
    }

    // ── Altitude-dependent sky colour with time-of-day tinting ───────────
    {
        const float px = camera_.position.x, py = camera_.position.y, pz = camera_.position.z;
        Planet nearP = PlanetManager::nearest(px, py, pz);
        float dx = px - nearP.cx, dy = py - nearP.cy, dz = pz - nearP.cz;
        float distCenter = std::sqrt(dx*dx + dy*dy + dz*dz);
        float altitude   = distCenter - static_cast<float>(nearP.baseRadius);
        float atmThick   = static_cast<float>(nearP.baseRadius) * 0.30f;
        float t = std::max(0.0f, std::min(1.0f, altitude / atmThick)); // 0=surface, 1=space

        float elev = sunDirY_; // -1 = midnight, +1 = noon

        // Three key sky (horizon fog) colours
        // Night: deep navy
        const float nR = 0.01f, nG = 0.02f, nB = 0.08f;
        // Dawn/dusk: warm orange-peach
        const float dR = 0.92f, dG = 0.52f, dB = 0.18f;
        // Midday: vivid sky blue
        const float mR = 0.32f, mG = 0.60f, mB = 0.98f;

        // Blend weights
        float dayF  = std::max(0.0f, std::min(1.0f, (elev + 0.15f) / 0.40f));
        float dawnF = std::pow(std::max(0.0f, 1.0f - std::abs(elev * 3.8f)), 2.2f);

        // Mix: start from night, add dawn glow, then lift to day
        float r = nR + dawnF * (dR - nR) + dayF * (mR - nR) * (1.0f - dawnF * 0.55f);
        float g = nG + dawnF * (dG - nG) + dayF * (mG - nG) * (1.0f - dawnF * 0.45f);
        float b = nB + dawnF * (dB - nB) + dayF * (mB - nB) * (1.0f - dawnF * 0.30f);

        // Extra camera-forward sun glow near horizon when looking at sun
        Vec3 fwd  = camera_.forward();
        float sd  = std::max(0.0f, fwd.x*sunDirX_ + fwd.y*sunDirY_ + fwd.z*sunDirZ_);
        float glow = sd * std::max(0.0f, 1.0f - std::abs(elev) * 2.5f) * 0.35f;
        r += glow * 0.90f;  g += glow * 0.40f;  b += glow * 0.05f;

        skyR_ = r * (1.0f - t);
        skyG_ = g * (1.0f - t);
        skyB_ = b * (1.0f - t);
        ubo.spaceBlend = t;
        spaceBlend_    = t;
    }
    ubo.skyR = skyR_;
    ubo.skyG = skyG_;
    ubo.skyB = skyB_;
    ubo.sunDirX  = sunDirX_;
    ubo.sunDirY  = sunDirY_;
    ubo.sunDirZ  = sunDirZ_;
    ubo.timeOfDay = dayTime_;
    ubo.gameTime        = std::fmod(currentTime_, 3600.0f);
    ubo.worldPlayTimeSec  = static_cast<int32_t>(worldPlayTime_);
    ubo.worldPlayTimeFrac = worldPlayTime_ - std::floor(worldPlayTime_);
    ubo.camVX = camVelX_;
    ubo.camVY = camVelY_;
    ubo.camVZ = camVelZ_;

    // Planet label: project aimed planet centre to screen space.
    ubo.planetVisible = 0;
    if (hasPlanetAim_ && aimedPlanetIdx_ >= 0 &&
        aimedPlanetIdx_ < static_cast<int>(nearbyPlanets_.size()))
    {
        const Planet& p   = nearbyPlanets_[aimedPlanetIdx_].planet;
        const float   dst = nearbyPlanets_[aimedPlanetIdx_].dist;

        // Camera-relative planet position
        const float relX = p.cx - static_cast<float>(cameraX_);
        const float relY = p.cy - static_cast<float>(cameraY_);
        const float relZ = p.cz - static_cast<float>(cameraZ_);

        // Multiply by VP (column-major): clip = lastMvp_ * [rel, 1]
        const float* M = lastMvp_;
        const float cx = M[0]*relX + M[4]*relY + M[8]*relZ  + M[12];
        const float cy = M[1]*relX + M[5]*relY + M[9]*relZ  + M[13];
        const float cw = M[3]*relX + M[7]*relY + M[11]*relZ + M[15];

        if (cw > 0.01f) {
            const float sw = static_cast<float>(swapchainExtent_.width);
            const float sh = static_cast<float>(swapchainExtent_.height);

            // Visual disc radius in screen pixels — physical formula α = 2·arctan(R/d).
            const float halfAngle = std::atan(static_cast<float>(p.baseRadius) / std::max(dst, 1.0f));
            const float halfSize  = SKY_PLANET_DIST * std::tan(halfAngle);
            const float fov  = 3.1415926f / 4.0f;
            const float scrR = halfSize / SKY_PLANET_DIST * (sh * 0.5f) / std::tan(fov * 0.5f);

            // Deterministic planet name from seed — pick from list of famous scientists/stars.
            // Letter → glyph code: A=15..W=37, X=11, Y=12, Z=13.
            static const int LETTER_CODES[26] = {
                15,16,17,18,19,20,21,22,23,24,25,26,27,
                28,29,30,31,32,33,34,35,36,37,11,12,13
            };
            static const char* NAMES[] = {
                // Astronomers
                "NEWTON","KEPLER","GALILEO","HUBBLE","TYCHO",
                "HALLEY","HUYGENS","CASSINI","BRAHE","HERSCHEL",
                "MESSIER","LAPLACE","FLAMSTEED","HIPPARCHUS","LACAILLE",
                // Physicists
                "EINSTEIN","BOHR","CURIE","FERMI","PLANCK",
                "TESLA","MAXWELL","FARADAY","LORENTZ","HEISENBERG",
                "DIRAC","FEYNMAN","HAWKING","PENROSE","BORN",
                "PAULI","BOLTZMANN","KIRCHHOFF","HERTZ","KELVIN",
                "AMPERE","VOLTA","JOULE","WATT","OHKAWA",
                // Mathematicians
                "EULER","GAUSS","RIEMANN","FOURIER","PASCAL",
                "LEIBNIZ","DESCARTES","NOETHER","RAMANUJAN","TURING",
                "HILBERT","POINCARE","CAUCHY","CANTOR","FIBONACCI",
                "WEIERSTRASS","GODEL","DEDEKIND","HERMITE","JACOBI",
                // Space explorers
                "GAGARIN","ARMSTRONG","SAGAN","ALDRIN","SHEPARD",
                "GLENN","RIDE","COLLINS","LOVELL","TERESHKOVA",
                "LEONOV","GRISSOM","CHAFFEE","COOPER","BEAN",
                // Stars & constellations
                "VEGA","SIRIUS","RIGEL","ALTAIR","DENEB",
                "CAPELLA","SPICA","ALGOL","MIRA","ANTARES",
                "PROCYON","POLLUX","CASTOR","CANOPUS","ACHERNAR",
                "ALDEBARAN","BETELGEUSE","FOMALHAUT","MIMOSA","HADAR",
                // Ancient scholars
                "ARISTOTLE","ARCHIMEDES","PYTHAGORAS","EUCLID","PTOLEMY",
                "COPERNICUS","ARISTARCHUS","ERATOSTHENES",
                // Other scientists
                "DARWIN","MENDEL","PASTEUR","MENDELEEV","LAVOISIER",
                "RUTHERFORD","BOYLE","HOOKE","AVOGADRO","DALTON",
                "BERNOULLI","DOPPLER","FOUCAULT","MICHELSON","LOMONOSOV",
            };
            static constexpr int NUM_NAMES = static_cast<int>(sizeof(NAMES) / sizeof(NAMES[0]));

            uint64_t h = p.seed ^ 0x504C414E4554ULL; // "PLANET" xor salt
            h ^= h >> 30; h *= 0xBF58476D1CE4E5B9ULL;
            h ^= h >> 27; h *= 0x94D049BB133111EBULL;
            h ^= h >> 31;
            const char* name = NAMES[h % static_cast<uint64_t>(NUM_NAMES)];

            // Encode name as packed glyph codes (8 bits per char, 4 per int32)
            uint8_t codes[12];
            std::fill(codes, codes + 12, uint8_t(100)); // default = space
            for (int i = 0; i < 12 && name[i]; ++i) {
                char c = name[i];
                codes[i] = (c >= 'A' && c <= 'Z')
                    ? static_cast<uint8_t>(LETTER_CODES[c - 'A']) : 100;
            }
            auto pack4 = [](uint8_t a, uint8_t b, uint8_t c2, uint8_t d) -> int32_t {
                return (int32_t(a) << 24) | (int32_t(b) << 16) | (int32_t(c2) << 8) | int32_t(d);
            };

            ubo.planetVisible  = 1;
            ubo.planetScreenX  = static_cast<int32_t>((cx / cw * 0.5f + 0.5f) * sw);
            ubo.planetScreenY  = static_cast<int32_t>((cy / cw * 0.5f + 0.5f) * sh);
            ubo.planetBiome    = static_cast<int32_t>(p.biome);
            ubo.planetDist     = static_cast<int32_t>(std::min(dst, 9999999.0f));
            ubo.planetCX       = static_cast<int32_t>(std::round(p.cx));
            ubo.planetCY       = static_cast<int32_t>(std::round(p.cy));
            ubo.planetCZ       = static_cast<int32_t>(std::round(p.cz));
            ubo.planetRadius   = p.baseRadius;
            ubo.planetName0    = pack4(codes[0], codes[1], codes[2],  codes[3]);
            ubo.planetName1    = pack4(codes[4], codes[5], codes[6],  codes[7]);
            ubo.planetName2    = pack4(codes[8], codes[9], codes[10], codes[11]);
            ubo.planetScreenR  = static_cast<int32_t>(scrR) + 18;
            ubo.planetOriginDist = static_cast<int32_t>(
                std::sqrt(p.cx * p.cx + p.cy * p.cy + p.cz * p.cz));
        }
    }

    // ── UI / game state ────────────────────────────────────────────────────
    ubo.gameState   = static_cast<int32_t>(gameState_);
    ubo.hotbarSlot  = hotbarSlot_;
    ubo.invOpen     = inventoryOpen_ ? 1 : 0;
    ubo.cursorBlink = cursorBlink_;
    ubo.heldType    = static_cast<int32_t>(heldItem_.type);
    ubo.heldCount   = heldItem_.count;
    {
        double cx, cy;
        glfwGetCursorPos(window_, &cx, &cy);
        int winW, winH;
        glfwGetWindowSize(window_, &winW, &winH);
        ubo.cursorX = static_cast<int32_t>(cx * swapchainExtent_.width  / winW);
        ubo.cursorY = static_cast<int32_t>(cy * swapchainExtent_.height / winH);
    }
    ubo.underWater = world_.hasWater(
        static_cast<int>(std::floor(cameraX_)),
        static_cast<int>(std::floor(cameraY_)),
        static_cast<int>(std::floor(cameraZ_))) ? 1 : 0;
    ubo.airPercent = static_cast<int32_t>(airPercent_);

    // Inventory slots (blockType << 16 | count)
    int32_t* invFields = &ubo.inv0;
    for (int i = 0; i < 16; ++i) {
        invFields[i] = (static_cast<int32_t>(inventory_[i].type) << 16)
                     | static_cast<int32_t>(inventory_[i].count);
    }

    // Main menu data
    ubo.menuSubState      = menuSubState_;
    ubo.menuSel           = menuSel_;
    ubo.worldCount        = static_cast<int32_t>(worldList_.size());
    ubo.settingsWorldSel  = (settingsWorldSel_ >= 0
                             && settingsWorldSel_ < static_cast<int>(worldList_.size()))
                            ? settingsWorldSel_ : -1;
    ubo.deleteConfirm     = deleteConfirm_;
    ubo.playerHp          = static_cast<int32_t>(std::max(0.0f, playerHp_));
    ubo.breakProgress     = breakProgress_;
    ubo.craft0 = (static_cast<int32_t>(craftGrid_[0].type) << 16) | static_cast<int32_t>(craftGrid_[0].count);
    ubo.craft1 = (static_cast<int32_t>(craftGrid_[1].type) << 16) | static_cast<int32_t>(craftGrid_[1].count);
    ubo.craft2 = (static_cast<int32_t>(craftGrid_[2].type) << 16) | static_cast<int32_t>(craftGrid_[2].count);
    ubo.craft3 = (static_cast<int32_t>(craftGrid_[3].type) << 16) | static_cast<int32_t>(craftGrid_[3].count);
    ubo.craft4 = (static_cast<int32_t>(craftGrid_[4].type) << 16) | static_cast<int32_t>(craftGrid_[4].count);
    ubo.craft5 = (static_cast<int32_t>(craftGrid_[5].type) << 16) | static_cast<int32_t>(craftGrid_[5].count);
    ubo.craft6 = (static_cast<int32_t>(craftGrid_[6].type) << 16) | static_cast<int32_t>(craftGrid_[6].count);
    ubo.craft7 = (static_cast<int32_t>(craftGrid_[7].type) << 16) | static_cast<int32_t>(craftGrid_[7].count);
    ubo.craft8 = (static_cast<int32_t>(craftGrid_[8].type) << 16) | static_cast<int32_t>(craftGrid_[8].count);
    ubo.craftOut = (static_cast<int32_t>(craftOutput_.type) << 16) | static_cast<int32_t>(craftOutput_.count);
    // recipeAvail bitmask: bit i = 1 if player has enough materials for recipe i
    {
        int ra = 0;
        // 0: Sticks — need 2 Wood
        if (countInventory(BlockType::Wood) >= 2)   ra |= (1 << 0);
        // 1: WoodPickaxe — need 3 Wood + 2 Stick
        if (countInventory(BlockType::Wood) >= 3 && countInventory(BlockType::Stick) >= 2) ra |= (1 << 1);
        // 2: StonePickaxe — need 3 Stone + 2 Stick
        if (countInventory(BlockType::Stone) >= 3 && countInventory(BlockType::Stick) >= 2) ra |= (1 << 2);
        // 3: WoodAxe — need 3 Wood + 2 Stick
        if (countInventory(BlockType::Wood) >= 3 && countInventory(BlockType::Stick) >= 2) ra |= (1 << 3);
        // 4: StoneAxe — need 3 Stone + 2 Stick
        if (countInventory(BlockType::Stone) >= 3 && countInventory(BlockType::Stick) >= 2) ra |= (1 << 4);
        // 5: WoodSword — need 2 Wood + 1 Stick
        if (countInventory(BlockType::Wood) >= 2 && countInventory(BlockType::Stick) >= 1) ra |= (1 << 5);
        // 6: StoneSword — need 2 Stone + 1 Stick
        if (countInventory(BlockType::Stone) >= 2 && countInventory(BlockType::Stick) >= 1) ra |= (1 << 6);
        ubo.recipeAvail = ra;
    }

    // World names (up to 8, 12 chars each = 3 ints)
    {
        int32_t* wnBase = &ubo.wn00;
        for (int w = 0; w < 8; ++w) {
            int32_t w0 = (100<<24)|(100<<16)|(100<<8)|100;
            int32_t w1 = w0, w2 = w0;
            if (w < static_cast<int>(worldList_.size()))
                WorldManager::packName12(worldList_[w].name, w0, w1, w2);
            wnBase[w*3+0] = w0;
            wnBase[w*3+1] = w1;
            wnBase[w*3+2] = w2;
        }
    }

    // Nickname
    WorldManager::packName12(WorldManager::nickname(), ubo.nick0, ubo.nick1, ubo.nick2);

    // Text input buffers
    {
        int32_t tb[6] = {};
        WorldManager::packText24(nameInput_, tb);
        ubo.tb0=tb[0]; ubo.tb1=tb[1]; ubo.tb2=tb[2];
        ubo.tb3=tb[3]; ubo.tb4=tb[4]; ubo.tb5=tb[5];
        ubo.textLen   = static_cast<int32_t>(nameInput_.size());
        ubo.textField = textField_;
    }
    {
        int32_t sb[4] = {};
        WorldManager::packHex16(seedInput_, sb);
        ubo.sb0=sb[0]; ubo.sb1=sb[1]; ubo.sb2=sb[2]; ubo.sb3=sb[3];
        ubo.seedLen = static_cast<int32_t>(seedInput_.size());
    }

    ubo.camWX   = static_cast<float>(cameraX_);
    ubo.camWY   = static_cast<float>(cameraY_);
    ubo.camWZ   = static_cast<float>(cameraZ_);
    ubo.camWPad = 0.0f;
    ubo.camWPad2= 0.0f;

    void* data = nullptr;
    vkMapMemory(device_, uniformBuffersMemory_[imageIndex], 0, sizeof(ubo), 0, &data);
    std::memcpy(data, &ubo, sizeof(ubo));
    vkUnmapMemory(device_, uniformBuffersMemory_[imageIndex]);
}
