#include "gfx/gl_loader.h"
#include "gfx/gl_shader.h"

#include <cstdio>
#include <cstring>
#include <regex>
#include <string>

namespace fn::gfx {

static const char* k_vert_src = R"glsl(
#version 330 core
const vec2 verts[6] = vec2[](
    vec2(-1,-1), vec2(1,-1), vec2(-1,1),
    vec2(1,-1),  vec2(1,1),  vec2(-1,1)
);
void main() { gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0); }
)glsl";

static const char* k_frag_preamble =
    "#version 330 core\n"
    "out vec4 fragColor;\n"
    "uniform vec2 u_resolution;\n"
    "uniform float u_time;\n"
    "uniform vec2 u_mouse;\n";

static int parse_err_line(const char* log) {
    // Try "ERROR: 0:<line>:" format
    std::regex re1(R"(ERROR:\s*\d+:(\d+):)");
    // Try "0(<line>)" format
    std::regex re2(R"(\d+\((\d+)\))");
    std::cmatch m;
    if (std::regex_search(log, m, re1)) {
        return std::stoi(m[1].str());
    }
    if (std::regex_search(log, m, re2)) {
        return std::stoi(m[1].str());
    }
    return -1;
}

static unsigned int compile_shader(GLenum type, const char** srcs, int count, std::string& out_err, int& out_line) {
    unsigned int s = glCreateShader(type);
    glShaderSource(s, count, srcs, nullptr);
    glCompileShader(s);
    GLint ok = 0;
    glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
    if (!ok) {
        GLint len = 0;
        glGetShaderiv(s, GL_INFO_LOG_LENGTH, &len);
        out_err.resize(static_cast<size_t>(len));
        glGetShaderInfoLog(s, len, nullptr, &out_err[0]);
        out_line = parse_err_line(out_err.c_str());
        glDeleteShader(s);
        return 0;
    }
    return s;
}

CompileResult compile_fragment(const std::string& user_fragment_src) {
    CompileResult result;

    // Vertex shader (no preamble needed — it's a standalone complete shader)
    std::string vert_err;
    int vert_line = -1;
    unsigned int vert = compile_shader(GL_VERTEX_SHADER, &k_vert_src, 1, vert_err, vert_line);
    if (!vert) {
        result.err_msg = "vertex: " + vert_err;
        result.err_line = vert_line;
        return result;
    }

    // Fragment shader — prepend preamble, then user body
    const char* frag_srcs[2] = { k_frag_preamble, user_fragment_src.c_str() };
    std::string frag_err;
    int frag_line = -1;
    unsigned int frag = compile_shader(GL_FRAGMENT_SHADER, frag_srcs, 2, frag_err, frag_line);
    if (!frag) {
        glDeleteShader(vert);
        result.err_msg = frag_err;
        // Adjust line: subtract preamble lines (4 lines)
        if (frag_line > 4) result.err_line = frag_line - 4;
        else result.err_line = frag_line;
        return result;
    }

    // Link
    unsigned int prog = glCreateProgram();
    glAttachShader(prog, vert);
    glAttachShader(prog, frag);
    glLinkProgram(prog);
    glDeleteShader(vert);
    glDeleteShader(frag);

    GLint ok = 0;
    glGetProgramiv(prog, GL_LINK_STATUS, &ok);
    if (!ok) {
        GLint len = 0;
        glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
        result.err_msg.resize(static_cast<size_t>(len));
        glGetProgramInfoLog(prog, len, nullptr, &result.err_msg[0]);
        result.err_line = parse_err_line(result.err_msg.c_str());
        glDeleteProgram(prog);
        return result;
    }

    result.program = prog;
    result.ok = true;
    return result;
}

void delete_program(unsigned int program) {
    if (program) glDeleteProgram(program);
}

} // namespace fn::gfx
