#pragma once
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <vector>

// XZ floor grid, centered at origin
// SIZE  = half-extent (25 means grid goes -25 to +25)
// STEP  = spacing between lines
static constexpr int   GRID_SIZE = 25;
static constexpr float GRID_STEP = 1.0f;

struct GridRenderer {
    unsigned int VAO, VBO;
    unsigned int shader;
    int          vertex_count;
};

static const char *GRID_VERT_SRC = R"(
#version 330 core
layout(location = 0) in vec3 aPos;
uniform mat4 uMVP;
void main() {
    gl_Position = uMVP * vec4(aPos, 1.0);
}
)";

static const char *GRID_FRAG_SRC = R"(
#version 330 core
out vec4 FragColor;
void main() {
    FragColor = vec4(0.35, 0.35, 0.35, 1.0);
}
)";

inline GridRenderer grid_init() {
    GridRenderer r;

    // Build vertices on the CPU — two endpoints per line, X-parallel and Z-parallel
    std::vector<float> verts;
    verts.reserve(4 * 3 * (GRID_SIZE * 2 + 1) * 2); // rough upper bound

    float lo = -GRID_SIZE * GRID_STEP;
    float hi =  GRID_SIZE * GRID_STEP;

    for (int i = -GRID_SIZE; i <= GRID_SIZE; i++) {
        float t = i * GRID_STEP;

        // Line parallel to X axis (varies X, fixed Z=t)
        verts.insert(verts.end(), { lo, 0.0f, t,
                                    hi, 0.0f, t });

        // Line parallel to Z axis (fixed X=t, varies Z)
        verts.insert(verts.end(), { t, 0.0f, lo,
                                    t, 0.0f, hi });
    }

    r.vertex_count = (int)(verts.size() / 3);

    // Compile shaders
    int  success;
    char log[512];

    unsigned int vert = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vert, 1, &GRID_VERT_SRC, NULL);
    glCompileShader(vert);
    glGetShaderiv(vert, GL_COMPILE_STATUS, &success);
    if (!success) { glGetShaderInfoLog(vert, 512, NULL, log); fprintf(stderr, "Grid vert: %s\n", log); }

    unsigned int frag = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(frag, 1, &GRID_FRAG_SRC, NULL);
    glCompileShader(frag);
    glGetShaderiv(frag, GL_COMPILE_STATUS, &success);
    if (!success) { glGetShaderInfoLog(frag, 512, NULL, log); fprintf(stderr, "Grid frag: %s\n", log); }

    r.shader = glCreateProgram();
    glAttachShader(r.shader, vert);
    glAttachShader(r.shader, frag);
    glLinkProgram(r.shader);
    glGetProgramiv(r.shader, GL_LINK_STATUS, &success);
    if (!success) { glGetProgramInfoLog(r.shader, 512, NULL, log); fprintf(stderr, "Grid link: %s\n", log); }
    glDeleteShader(vert);
    glDeleteShader(frag);

    // Upload geometry
    glGenVertexArrays(1, &r.VAO);
    glGenBuffers(1, &r.VBO);

    glBindVertexArray(r.VAO);
    glBindBuffer(GL_ARRAY_BUFFER, r.VBO);
    glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.data(), GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    glBindVertexArray(0);
    return r;
}

inline void grid_draw(const GridRenderer &r, const glm::mat4 &mvp) {
    glUseProgram(r.shader);
    glUniformMatrix4fv(glGetUniformLocation(r.shader, "uMVP"), 1, GL_FALSE, glm::value_ptr(mvp));
    glBindVertexArray(r.VAO);
    glLineWidth(1.0f);
    glDrawArrays(GL_LINES, 0, r.vertex_count);
    glBindVertexArray(0);
}

inline void grid_destroy(GridRenderer &r) {
    glDeleteVertexArrays(1, &r.VAO);
    glDeleteBuffers(1, &r.VBO);
    glDeleteProgram(r.shader);
}
