

#include "Renderer.h"
#include "Components/SpriteComponent.h"
#include "Components/TilemapComponent.h"

#include "utils/Shader.h"
#include "core/utils/Logging.h"
#include "core/utils/EngineConfig.h"
#include "core/utils/utils.h"
#include "core/utils/Profiler.h"
#include "Entitys/Object.h"

#include "stb_image.h"

#include <GL/glew.h>
#include <glm/glm.hpp>
#include <iostream>
#include <algorithm>
#include <map>

static Shader spriteShader;
static Shader unlitShader;
static Shader tilemapShader;

static Shader extractShader;
static Shader blurShader;
static Shader compositeShader;

static GLuint extractFBO, extractTexture;
static GLuint blurFBO_H, blurTexture_H;
static GLuint blurFBO_V, blurTexture_V;
static GLuint bloomFBO, bloomTexture;

GLuint Renderer::fbo = 0;
GLuint Renderer::textureColorBuffer = 0;
GLuint Renderer::defaultNormalMap = 0;
GLuint Renderer::rbo = 0;
GLuint Renderer::quadVAO = 0;
GLuint Renderer::quadVBO = 0;
int Renderer::width = 1280;
int Renderer::height = 720;
int Renderer::s_DrawCalls = 0;
int Renderer::s_LightsCount = 0;
std::unique_ptr<ColorCorrection> Renderer::s_ColorCorrection = nullptr;

GLuint Renderer::s_QuadVAO = 0;
GLuint Renderer::s_QuadVBO = 0;
GLuint Renderer::s_QuadInstanceVBO = 0;
Shader Renderer::s_UnlitQuadShader;
std::vector<QuadInstance> Renderer::s_QuadBatch;

void *Renderer::s_QuadMappedPtr = nullptr;
GLuint Renderer::s_QuadPersistentFlags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;

std::vector<Light> Renderer::s_Lights;

std::vector<Renderer::Cluster> Renderer::s_Clusters;
GLuint Renderer::s_ClusterSSBO = 0;
int Renderer::s_ClusterCols = 0;
int Renderer::s_ClusterRows = 0;

GLuint Renderer::bloomFBO = 0;
GLuint Renderer::bloomTexture = 0;

struct CachedLightUniforms
{
    std::string posName;
    std::string colorName;
    std::string intensityName;
    std::string radiusName;
};

struct BatchedSprite
{
    glm::vec2 screenPos;
    glm::vec2 size;
    float rotationRad;
    glm::vec4 texCoords;
    GLuint textureID;
    GLuint normalMapID;
    RenderType renderType;
    SpriteComponent *sprite;
};

struct SortedDrawEntry
{
    BatchedSprite sprite;
    Shader *shader = nullptr;
    bool useLighting = true;
    bool usesUV = false;
};

std::vector<CachedLightUniforms> s_LightUniforms;

void InitLightUniforms(int maxLights)
{
    s_LightUniforms.clear();
    s_LightUniforms.reserve(maxLights);
    for (int i = 0; i < maxLights; ++i)
    {
        s_LightUniforms.push_back({"uLightPos[" + std::to_string(i) + "]",
                                   "uLightColor[" + std::to_string(i) + "]",
                                   "uLightIntensity[" + std::to_string(i) + "]",
                                   "uLightRadius[" + std::to_string(i) + "]"});
    }
}

std::vector<SortedDrawEntry> sortedDrawList;

void Renderer::InitQuad()
{
    float vertices[] = {
        // pos      // uv
        -1.f, -1.f, 0.f, 0.f,
        1.f, -1.f, 1.f, 0.f,
        1.f, 1.f, 1.f, 1.f,
        -1.f, 1.f, 0.f, 1.f};

    glGenVertexArrays(1, &quadVAO);
    glBindVertexArray(quadVAO);

    glGenBuffers(1, &quadVBO);
    glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glEnableVertexAttribArray(0); // position
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(1); // UV
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)(2 * sizeof(float)));

    glBindVertexArray(0);
}

void Renderer::InitQuadBatch()
{
    float quadVerts[] = {
        -0.5f, -0.5f,
        0.5f, -0.5f,
        0.5f, 0.5f,
        -0.5f, 0.5f};

    glGenVertexArrays(1, &s_QuadVAO);
    glBindVertexArray(s_QuadVAO);

    glGenBuffers(1, &s_QuadVBO);
    glBindBuffer(GL_ARRAY_BUFFER, s_QuadVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(quadVerts), quadVerts, GL_STATIC_DRAW);

    glEnableVertexAttribArray(0); // aPos
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0);

    glGenBuffers(1, &s_QuadInstanceVBO);
    glBindBuffer(GL_ARRAY_BUFFER, s_QuadInstanceVBO);
    glGenBuffers(1, &s_QuadInstanceVBO);
    glBindBuffer(GL_ARRAY_BUFFER, s_QuadInstanceVBO);
    glBufferStorage(GL_ARRAY_BUFFER, MAX_QUADS * sizeof(QuadInstance), nullptr, s_QuadPersistentFlags);

    // Map once for persistent write access
    s_QuadMappedPtr = glMapBufferRange(GL_ARRAY_BUFFER, 0, MAX_QUADS * sizeof(QuadInstance), s_QuadPersistentFlags);
    if (!s_QuadMappedPtr)
    {
        Logger::LogError("Persistent VBO mapping failed!");
    }

    std::size_t offset = 0;

    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(QuadInstance), (void *)offsetof(QuadInstance, screenPos));
    glVertexAttribDivisor(1, 1);

    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(QuadInstance), (void *)offsetof(QuadInstance, size));
    glVertexAttribDivisor(2, 1);

    glEnableVertexAttribArray(3);
    glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(QuadInstance), (void *)offsetof(QuadInstance, rotation));
    glVertexAttribDivisor(3, 1);

    glEnableVertexAttribArray(4);
    glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(QuadInstance), (void *)offsetof(QuadInstance, color));
    glVertexAttribDivisor(4, 1);

    glEnableVertexAttribArray(5);
    glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(QuadInstance), (void *)offsetof(QuadInstance, roundness));
    glVertexAttribDivisor(5, 1);

    glBindVertexArray(0);
}

void Renderer::Init()
{

    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    glGenTextures(1, &textureColorBuffer);
    glBindTexture(GL_TEXTURE_2D, textureColorBuffer);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorBuffer, 0);

    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        Logger::LogError("[Renderer::Init] FBO incomplete");

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    InitQuad();

    // Load lit shader

    // Core renderers
    {
        spriteShader.LoadFromFile("src/assets/shaders/sprite.vert", "src/assets/shaders/sprite.frag");
        unlitShader.LoadFromFile("src/assets/shaders/unlit.vert", "src/assets/shaders/unlit.frag");
        tilemapShader.LoadFromFile("src/assets/shaders/tilemap.vert", "src/assets/shaders/tilemap.frag");

        // Bloom pipeline
        extractShader.LoadFromFile("src/assets/shaders/fullscreen.vert", "src/assets/shaders/extract.frag");
        blurShader.LoadFromFile("src/assets/shaders/fullscreen.vert", "src/assets/shaders/blur.frag");
        compositeShader.LoadFromFile("src/assets/shaders/fullscreen.vert", "src/assets/shaders/composite.frag");

        s_UnlitQuadShader.LoadFromFile("src/assets/shaders/unlit_quad.vert", "src/assets/shaders/unlit_quad.frag");



        Logger::LogOk("Shader Core");
    }

    InitQuadBatch();

    SetColorCorrection(std::make_unique<ColorCorrection>());

    {
        Logger::LogVerbose("Renderer::InitLightUniforms(%d)", g_engineConfig.gl_maxLight);
        InitLightUniforms(g_engineConfig.gl_maxLight);
    }

    glGenBuffers(1, &s_ClusterSSBO);

    // Bloom textures and framebuffers
    glGenFramebuffers(1, &extractFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, extractFBO);
    glGenTextures(1, &extractTexture);
    glBindTexture(GL_TEXTURE_2D, extractTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, extractTexture, 0);

    glGenFramebuffers(1, &blurFBO_H);
    glBindFramebuffer(GL_FRAMEBUFFER, blurFBO_H);
    glGenTextures(1, &blurTexture_H);
    glBindTexture(GL_TEXTURE_2D, blurTexture_H);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, blurTexture_H, 0);

    glGenFramebuffers(1, &blurFBO_V);
    glBindFramebuffer(GL_FRAMEBUFFER, blurFBO_V);
    glGenTextures(1, &blurTexture_V);
    glBindTexture(GL_TEXTURE_2D, blurTexture_V);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, blurTexture_V, 0);

    // Final composite target
    glGenFramebuffers(1, &bloomFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, bloomFBO);
    glGenTextures(1, &bloomTexture);
    glBindTexture(GL_TEXTURE_2D, bloomTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, bloomTexture, 0);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // Create a 1x1 flat normal map (RGB: 128,128,255)
    unsigned char flatNormal[3] = {128, 128, 255};
    glGenTextures(1, &defaultNormalMap);
    glBindTexture(GL_TEXTURE_2D, defaultNormalMap);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, flatNormal);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    s_DrawCalls = 0;
    Logger::LogOk("Renderer Core");
}

void Renderer::UpdateClusterLights()
{
    PROFILE_ENGINE_SCOPE("Renderer::UpdateClusterLights");

    if (!g_engineConfig.settings.lighting_enabled)
        return;

    s_ClusterCols = (width + CLUSTER_SIZE - 1) / CLUSTER_SIZE;
    s_ClusterRows = (height + CLUSTER_SIZE - 1) / CLUSTER_SIZE;
    const int totalClusters = s_ClusterCols * s_ClusterRows;

    static int lastCols = 0, lastRows = 0;
    if (lastCols != s_ClusterCols || lastRows != s_ClusterRows)
    {
        lastCols = s_ClusterCols;
        lastRows = s_ClusterRows;
        s_Clusters.clear();
        s_Clusters.resize(totalClusters);
        for (auto &c : s_Clusters)
            c.lightIndices.reserve(MAX_LIGHTS_PER_CLUSTER);
    }

    {
        PROFILE_ENGINE_SCOPE("Clear");

        for (auto &c : s_Clusters)
        {
            c.lightIndices.clear();
        }
    }

    const float invCluster = 1.0f / float(CLUSTER_SIZE);
    const int maxCol = s_ClusterCols - 1;
    const int maxRow = s_ClusterRows - 1;
    {
        PROFILE_ENGINE_SCOPE("Assign");

        for (int i = 0, n = int(s_Lights.size()); i < n; ++i)
        {
            PROFILE_ENGINE_SCOPE("L" + std::to_string(i));

            const Light &light = s_Lights[i];
            glm::vec2 lo = light.screenPos - glm::vec2(light.radius);
            glm::vec2 hi = light.screenPos + glm::vec2(light.radius);

            int minX = std::max(int(lo.x * invCluster), 0);
            int maxX = std::min(int(hi.x * invCluster), maxCol);
            int minY = std::max(int(lo.y * invCluster), 0);
            int maxY = std::min(int(hi.y * invCluster), maxRow);

            for (int y = minY; y <= maxY; ++y)
            {
                for (int x = minX; x <= maxX; ++x)
                {
                    auto &cell = s_Clusters[y * s_ClusterCols + x].lightIndices;
                    if (int(cell.size()) < MAX_LIGHTS_PER_CLUSTER)
                        cell.emplace_back(i);
                }
            }
        }
    }

    static std::vector<int> packed;
    static size_t gpuBufferBytes = 0;
    const size_t packCount = size_t(totalClusters) * MAX_LIGHTS_PER_CLUSTER;
    const size_t byteSize = packCount * sizeof(int);

    {
        PROFILE_ENGINE_SCOPE("Pack");

        packed.resize(packCount);
        int *dst = packed.data();

        std::memset(dst, 0xFF, packCount * sizeof(int));

        for (int c = 0; c < totalClusters; ++c)
        {
            auto &src = s_Clusters[c].lightIndices;
            int n = int(src.size());
            if (n > MAX_LIGHTS_PER_CLUSTER)
                n = MAX_LIGHTS_PER_CLUSTER;

            std::memcpy(
                dst + c * MAX_LIGHTS_PER_CLUSTER,
                src.data(),
                n * sizeof(int));
        }
    }
    {
        PROFILE_ENGINE_SCOPE("Bind");

        glBindBuffer(GL_SHADER_STORAGE_BUFFER, s_ClusterSSBO);
        if (byteSize != gpuBufferBytes)
        {
            glBufferData(GL_SHADER_STORAGE_BUFFER, byteSize, packed.data(), GL_DYNAMIC_DRAW);
            gpuBufferBytes = byteSize;
        }
        else
        {
            glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, byteSize, packed.data());
        }
        glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, s_ClusterSSBO);
    }
}

void Renderer::Resize(int w, int h)
{
    if (w == width && h == height)
        return;
    width = w;
    height = h;

    // Resize all textures
    glBindTexture(GL_TEXTURE_2D, textureColorBuffer);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    glBindTexture(GL_TEXTURE_2D, extractTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);

    glBindTexture(GL_TEXTURE_2D, blurTexture_H);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);

    glBindTexture(GL_TEXTURE_2D, blurTexture_V);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);

    glBindTexture(GL_TEXTURE_2D, bloomTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);

    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
}

void Renderer::Begin()
{
    {
        PROFILE_ENGINE_SCOPE("glBindFramebuffer");
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    }

    glViewport(0, 0, width, height);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    s_DrawCalls = 0;
    ClearLights();
}

void Renderer::End()
{

    PROFILE_ENGINE_SCOPE("Renderer::End");
    FlushSprites();
    FlushQuads();
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void Renderer::ClearLights()
{
    PROFILE_ENGINE_SCOPE("Renderer::ClearLights");

    s_Lights.clear();
    s_LightsCount = 0;
}

void Renderer::AddLight(const glm::vec2 &screenPos, const glm::vec3 &color, float intensity, float radius)
{
    PROFILE_ENGINE_SCOPE("Engine::AddLight");

    if (s_Lights.size() >= g_engineConfig.gl_maxLight)
        return;
    s_Lights.push_back({screenPos, color, intensity, radius});
    s_LightsCount++;
}

void Renderer::DrawTilemap(TilemapComponent *tilemap,
                           const glm::vec2 & /*pos*/,
                           float zoom,
                           const glm::vec2 &cameraPos)
{
    PROFILE_DEEP_SCOPE("Renderer::DrawTilemap");
    if (!tilemap)
        return;

    // grab atlas + texture
    TextureAtlas *atlas = tilemap->GetAtlas();
    if (!atlas || !atlas->texture)
        return;

    // world-space size of one cell
    const auto &tileSize = tilemap->GetTileSize();
    glm::vec2 screenCenter{width * 0.5f, height * 0.5f};

    // draw every cell
    for (auto [key, tileIndex] : tilemap->GetTileData())
    {
        // skip bad indices
        if (tileIndex < 0 || tileIndex >= atlas->GetTotalFrames())
            continue;

        // decode grid coords
        int gx, gy;
        TilemapComponent::UnpackCoord(key, gx, gy);

        // world→screen position
        glm::vec2 worldPos = {gx * tileSize.x, gy * tileSize.y};
        glm::vec2 screenPos = (worldPos - cameraPos) * zoom + screenCenter;

        // quad size in screen‐space
        glm::vec2 finalSize = tileSize * zoom;

        // fetch UVs (already handles flipping for you)
        auto uvRect = atlas->GetFrameUVRect(tileIndex);

        // batch that sprite
        BatchedSprite entry{};
        entry.screenPos = screenPos;
        entry.size = finalSize;
        entry.rotationRad = 0.0f;
        entry.textureID = atlas->texture->GetID();
        entry.normalMapID = defaultNormalMap;
        entry.renderType = RenderType::Unlit;
        entry.sprite = nullptr;
        entry.texCoords = glm::vec4(
            uvRect.min.x, uvRect.min.y,
            uvRect.max.x, uvRect.max.y);

        SortedDrawEntry drawEntry{};
        drawEntry.sprite = entry;
        drawEntry.shader = &unlitShader;
        drawEntry.useLighting = false;
        drawEntry.usesUV = true;

        sortedDrawList.push_back(drawEntry);
    }
}

void Renderer::DrawQuad(const core::types::Vec2 &pos, const core::types::Vec2 &size, float rotation, const core::types::Color &color)
{
    PROFILE_DEEP_SCOPE("Renderer::DrawQuad");

    if (s_QuadBatch.size() >= MAX_QUADS)
        FlushQuads();

    s_QuadBatch.push_back({pos, size, rotation, color});
}

void Renderer::BatchQuad(const core::types::Vec2 &worldPos,
                         const core::types::Vec2 &size,
                         float rotation,
                         const core::types::Color &color,
                         const core::types::Vec2 &cameraPos,
                         float zoom,
                         float isCircle)
{
    if (s_QuadBatch.size() >= MAX_QUADS)
        FlushQuads();

    core::types::Vec2 screenPos = (worldPos - cameraPos) * zoom + core::types::Vec2(width * 0.5f, height * 0.5f);
    core::types::Vec2 finalSize = size * zoom;

    s_QuadBatch.push_back({screenPos, finalSize, rotation, color, isCircle});
}

void Renderer::BatchCircle(const core::types::Vec2 &worldPos,
                           const core::types::Vec2 &size,
                           const core::types::Color &color,
                           const core::types::Vec2 &cameraPos,
                           float zoom)
{
    BatchQuad(worldPos, size, 0.0f, color, cameraPos, zoom, 1.0f);
}

void Renderer::DrawSprite(SpriteComponent *sprite, const glm::vec2 &pos, float zoom, glm::vec2 &CameraPos)
{
    PROFILE_DEEP_SCOPE("DrawSprite");

    if (!sprite->HasTexture())
        return;

    glm::vec2 size = sprite->GetSize();
    glm::vec2 screenPos = (pos - CameraPos) * zoom + glm::vec2(width, height) * 0.5f - (size * zoom * 0.5f);
    float rotationDeg = sprite->GetOwner()->GetWorldRotation();

    BatchedSprite entry;
    entry.screenPos = screenPos;
    entry.size = size * zoom;
    entry.rotationRad = glm::radians(rotationDeg);
    entry.textureID = sprite->GetTextureID();
    entry.normalMapID = sprite->GetNormalMapID();
    entry.renderType = sprite->GetRenderType();
    entry.sprite = sprite;

    SortedDrawEntry drawEntry;
    drawEntry.sprite = entry;
    drawEntry.shader = (g_engineConfig.settings.lighting_enabled && entry.renderType == RenderType::Lit)
                           ? &spriteShader
                           : &unlitShader;
    drawEntry.useLighting = (drawEntry.shader == &spriteShader);

    sortedDrawList.push_back(drawEntry);
}

void Renderer::DrawTextureAtlas(const TextureAtlas *atlas, int index, const core::types::Vec2 &pos, float rotationDeg, float zoom)
{
    PROFILE_DEEP_SCOPE("Renderer::DrawTextureAtlas");

    if (!atlas || !atlas->texture || index >= atlas->GetTotalFrames())
        return;

    const auto &tex = atlas->texture;
    const core::types::Vec2 uvMin = atlas->GetFrameUV(index);
    const core::types::Vec2 uvMax = uvMin + atlas->GetFrameSizeUV();

    glm::vec2 screenPos = pos * zoom + glm::vec2(width, height) * 0.5f;

    glm::vec2 frameSize = atlas->GetFrameSizeUV() * tex->GetSize();

    glm::vec2 finalSize = frameSize * zoom;

    BatchedSprite entry;
    entry.screenPos = screenPos;
    entry.size = finalSize;
    entry.rotationRad = glm::radians(rotationDeg);
    entry.textureID = tex->GetID();
    entry.normalMapID = defaultNormalMap;
    entry.renderType = RenderType::Unlit;
    entry.sprite = nullptr;
    entry.texCoords = glm::vec4(uvMin.x, uvMin.y, uvMax.x, uvMax.y);

    SortedDrawEntry drawEntry;
    drawEntry.sprite = entry;
    drawEntry.shader = &unlitShader;
    drawEntry.useLighting = false;
    drawEntry.usesUV = true;

    sortedDrawList.push_back(drawEntry);
}

void Renderer::DrawAnimator(const AnimationComponent *anim, const core::types::Vec2 &worldPos, float zoom, const core::types::Vec2 &cameraPos)
{
    PROFILE_DEEP_SCOPE("Renderer::DrawAnimator");

    if (!anim || !anim->GetAtlas() || !anim->GetAtlas()->texture)
        return;

    const TextureAtlas *atlas = anim->GetAtlas();
    int frameIndex = anim->GetCurrentFrame();

    if (frameIndex >= atlas->GetTotalFrames())
        return;

    const auto &tex = atlas->texture;
    core::types::Vec2 uvMin = atlas->GetFrameUV(frameIndex);
    core::types::Vec2 uvMax = uvMin + atlas->GetFrameSizeUV();
    core::types::Vec2 texSize = tex->GetSize();

    glm::vec2 screenPos = (worldPos - cameraPos) * zoom + glm::vec2(width, height) * 0.5f;
    glm::vec2 frameSize = atlas->GetFrameSizeUV() * texSize;
    glm::vec2 finalSize = frameSize * zoom;

    BatchedSprite entry;
    entry.screenPos = screenPos;
    entry.size = finalSize;
    entry.rotationRad = 0.0f;
    entry.textureID = tex->GetID();
    entry.normalMapID = defaultNormalMap;
    entry.renderType = anim->renderType;
    entry.sprite = nullptr;
    entry.texCoords = glm::vec4(uvMin.x, uvMin.y, uvMax.x, uvMax.y);

    SortedDrawEntry drawEntry;
    drawEntry.sprite = entry;
    drawEntry.shader = &unlitShader;
    drawEntry.useLighting = false;
    drawEntry.usesUV = true;

    sortedDrawList.push_back(drawEntry);
}
void Renderer::FlushQuads()
{
    PROFILE_ENGINE_SCOPE("Renderer::FlushQuads");

    if (s_QuadBatch.empty())
        return;

    {
        PROFILE_DEEP_SCOPE("Upload");

        s_UnlitQuadShader.Use();
        s_UnlitQuadShader.SetVec2("uScreen", glm::vec2(width, height));

        glBindVertexArray(s_QuadVAO);
        glBindBuffer(GL_ARRAY_BUFFER, s_QuadInstanceVBO);

        std::memcpy(s_QuadMappedPtr, s_QuadBatch.data(), s_QuadBatch.size() * sizeof(QuadInstance));
    }

    {
        PROFILE_DEEP_SCOPE("DrawQuads");

        glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, static_cast<GLsizei>(s_QuadBatch.size()));
        ++s_DrawCalls;
    }

    glBindVertexArray(0);
    s_QuadBatch.clear();
}

void Renderer::FlushSprites()
{
    PROFILE_ENGINE_SCOPE("Renderer::FlushSprites");

    if (sortedDrawList.empty())
        return;

    glBindVertexArray(quadVAO);

    Shader *currentShader = nullptr;
    bool currentLighting = false;

    for (const auto &entry : sortedDrawList)
    {
        PROFILE_DEEP_SCOPE("DrawSprite");

        if (entry.shader != currentShader || entry.useLighting != currentLighting)
        {
            currentShader = entry.shader;
            currentLighting = entry.useLighting;

            currentShader->Use();
            currentShader->SetVec2("uScreen", glm::vec2(width, height));

            if (currentLighting)
            {
                PROFILE_DEEP_SCOPE("LightingSetup");
                s_ColorCorrection->Upload(*currentShader);
                currentShader->SetInt("uClusterWidth", CLUSTER_SIZE);
                currentShader->SetInt("uClusterHeight", CLUSTER_SIZE);
                currentShader->SetInt("uClusterCols", s_ClusterCols);
                currentShader->SetInt("uMaxLightsPerCluster", MAX_LIGHTS_PER_CLUSTER);
                currentShader->SetInt("uLightCount", static_cast<int>(s_Lights.size()));

                for (int i = 0; i < s_Lights.size(); ++i)
                {
                    const auto &u = s_LightUniforms[i];
                    currentShader->SetVec2(u.posName.c_str(), s_Lights[i].screenPos);
                    currentShader->SetVec3(u.colorName.c_str(), s_Lights[i].color);
                    currentShader->SetFloat(u.intensityName.c_str(), s_Lights[i].intensity);
                    currentShader->SetFloat(u.radiusName.c_str(), s_Lights[i].radius);
                }

                currentShader->SetInt("uNormalMap", 1);
                glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, s_ClusterSSBO);
            }
        }

        // Common uniforms
        currentShader->SetVec2("uPos", entry.sprite.screenPos);
        currentShader->SetVec2("uSize", entry.sprite.size);
        currentShader->SetFloat("uRotation", entry.sprite.rotationRad);
        currentShader->SetInt("uTex", 0);

        // Set UVs if applicable
        if (entry.usesUV)
        {
            currentShader->SetVec2("uUVMin", glm::vec2(entry.sprite.texCoords.x, entry.sprite.texCoords.y));
            currentShader->SetVec2("uUVMax", glm::vec2(entry.sprite.texCoords.z, entry.sprite.texCoords.w));
        }
        else
        {
            // Full UV range fallback
            currentShader->SetVec2("uUVMin", glm::vec2(0.0f));
            currentShader->SetVec2("uUVMax", glm::vec2(1.0f));
        }

        // Bind textures
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, entry.sprite.textureID);

        if (entry.useLighting)
        {
            glActiveTexture(GL_TEXTURE1);
            glBindTexture(GL_TEXTURE_2D, entry.sprite.normalMapID ? entry.sprite.normalMapID : defaultNormalMap);
        }

        {
            PROFILE_DEEP_SCOPE("GL Draw");
            glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
            ++s_DrawCalls;
        }
    }

    glBindVertexArray(0);
    sortedDrawList.clear();
}

int Renderer::GetDrawCallCount()
{
    return s_DrawCalls;
}

int Renderer::GetLightsCount()
{
    return s_LightsCount;
}

void Renderer::DrawEditorGrid(const glm::vec2 &cameraPos, float zoom)
{
    return; // This is stupid
    glUseProgram(0);
    glColor4f(0.5f, 0.5f, 0.5f, 0.25f);
    glLineWidth(1.0f);

    glBegin(GL_LINES);
    float spacing = 100.0f;
    float viewWidth = width / zoom;
    float viewHeight = height / zoom;

    float left = cameraPos.x - viewWidth / 2;
    float right = cameraPos.x + viewWidth / 2;
    float bottom = cameraPos.y + viewHeight / 2;
    float top = cameraPos.y - viewHeight / 2;

    int minX = static_cast<int>(std::floor(left / spacing)) * spacing;
    int maxX = static_cast<int>(std::ceil(right / spacing)) * spacing;
    int minY = static_cast<int>(std::floor(top / spacing)) * spacing;
    int maxY = static_cast<int>(std::ceil(bottom / spacing)) * spacing;

    for (int x = minX; x <= maxX; x += spacing)
    {
        glVertex2f((float)x, top);
        glVertex2f((float)x, bottom);
    }

    for (int y = minY; y <= maxY; y += spacing)
    {
        glVertex2f(left, (float)y);
        glVertex2f(right, (float)y);
    }
    glEnd();
}

void Renderer::DrawGizmoLine(
    const glm::vec2 &worldStart,
    const glm::vec2 &worldEnd,
    const glm::vec3 &color,
    const glm::vec2 &cameraPos,
    float zoom)
{
    // Transform to screen space
    glm::vec2 a = (worldStart - cameraPos) * zoom + glm::vec2(width * 0.5f, height * 0.5f);
    glm::vec2 b = (worldEnd - cameraPos) * zoom + glm::vec2(width * 0.5f, height * 0.5f);

    glUseProgram(0);
    glLineWidth(2.0f);
    glColor3f(color.r, color.g, color.b);

    glBegin(GL_LINES);
    glVertex2f(a.x, a.y);
    glVertex2f(b.x, b.y);
    glEnd();
}

void Renderer::DrawGizmoRect(
    const glm::vec2 &worldPos,
    const glm::vec2 &size,
    const glm::vec3 &color,
    const glm::vec2 &cameraPos,
    float zoom)
{
    glm::vec2 p0 = worldPos;
    glm::vec2 p1 = worldPos + glm::vec2(size.x, 0.0f);
    glm::vec2 p2 = worldPos + size;
    glm::vec2 p3 = worldPos + glm::vec2(0.0f, size.y);

    // draw four edges
    DrawGizmoLine(p0, p1, color, cameraPos, zoom);
    DrawGizmoLine(p1, p2, color, cameraPos, zoom);
    DrawGizmoLine(p2, p3, color, cameraPos, zoom);
    DrawGizmoLine(p3, p0, color, cameraPos, zoom);
}

void Renderer::DrawGizmoCircle(
    const glm::vec2 &worldCenter,
    float radius,
    int segments,
    const glm::vec3 &color,
    const glm::vec2 &cameraPos,
    float zoom)
{
    if (segments < 3)
        segments = 3;
    const float thetaStep = 2.0f * 3.14159265f / float(segments);

    glUseProgram(0);
    glLineWidth(2.0f);
    glColor3f(color.r, color.g, color.b);

    glBegin(GL_LINE_LOOP);
    for (int i = 0; i < segments; ++i)
    {
        float theta = thetaStep * float(i);
        glm::vec2 worldPoint = worldCenter + glm::vec2(std::cos(theta), std::sin(theta)) * radius;
        glm::vec2 screenPoint = (worldPoint - cameraPos) * zoom + glm::vec2(width * 0.5f, height * 0.5f);
        glVertex2f(screenPoint.x, screenPoint.y);
    }
    glEnd();
}

GLuint Renderer::GetFinalTexture()
{
    PROFILE_ENGINE_SCOPE("Renderer::GetFinalTexture");

    GLuint sceneTex = GetRenderTexture();

    if (!s_ColorCorrection || !s_ColorCorrection->bloom)
        return sceneTex;

    glBindFramebuffer(GL_FRAMEBUFFER, extractFBO);
    glViewport(0, 0, width, height);
    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    extractShader.Use();
    extractShader.SetInt("uScene", 0);
    extractShader.SetFloat("uThreshold", s_ColorCorrection->threshold);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, sceneTex);

    glBindVertexArray(quadVAO);
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

    // 2. Horizontal blur
    glBindFramebuffer(GL_FRAMEBUFFER, blurFBO_H);
    glClear(GL_COLOR_BUFFER_BIT);

    blurShader.Use();
    blurShader.SetInt("uTex", 0);
    blurShader.SetVec2("uDirection", glm::vec2(1.0f / width, 0.0f));

    glBindTexture(GL_TEXTURE_2D, extractTexture);
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

    // 3. Vertical blur
    glBindFramebuffer(GL_FRAMEBUFFER, blurFBO_V);
    glClear(GL_COLOR_BUFFER_BIT);

    blurShader.SetVec2("uDirection", glm::vec2(0.0f, 1.0f / height));
    glBindTexture(GL_TEXTURE_2D, blurTexture_H);
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

    // 4. Final composite
    glBindFramebuffer(GL_FRAMEBUFFER, bloomFBO);
    glClear(GL_COLOR_BUFFER_BIT);

    compositeShader.Use();
    compositeShader.SetInt("uScene", 0);
    compositeShader.SetInt("uBloom", 1);
    compositeShader.SetFloat("uIntensity", s_ColorCorrection->intensity);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, sceneTex);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, blurTexture_V);

    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    return bloomTexture;
}

GLuint Renderer::GetRenderTexture()
{
    return textureColorBuffer;
}

core::types::Vec2 Renderer::GetSize()
{
    return {width, height};
}
void Renderer::SetColorCorrection(std::unique_ptr<ColorCorrection> correction)
{
    s_ColorCorrection = std::move(correction);
}
ColorCorrection *Renderer::GetColorCorrection()
{
    return s_ColorCorrection.get();
}