#include "gfx/gl_loader.h"
#include "gfx/dag_node_previews.h"
#include "gfx/dag_catalog.h"
#include "gfx/gl_framebuffer.h"
#include "gfx/fullscreen_quad.h"

#include <unordered_map>

namespace fn::gfx {

struct PreviewSlot {
    Framebuffer fb;
    bool inited = false;
};

static std::unordered_map<unsigned, PreviewSlot> s_slots;
static Quad                                       s_quad;
static bool                                       s_quad_init = false;

static PreviewSlot& slot_for(unsigned uid) {
    auto& slot = s_slots[uid];
    if (!slot.inited) {
        fb_init(slot.fb);
        slot.inited = true;
    }
    return slot;
}

void dag_previews_render(const std::vector<DagStep>& pipeline,
                         unsigned program,
                         int width, int height) {
    if (!program || pipeline.empty()) return;

    if (!s_quad_init) {
        quad_init(s_quad);
        s_quad_init = true;
    }

    // Save GL state we'll touch
    GLint prev_fbo = 0;
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo);
    GLint prev_vp[4];
    glGetIntegerv(GL_VIEWPORT, prev_vp);
    GLint prev_program = 0;
    glGetIntegerv(GL_CURRENT_PROGRAM, &prev_program);

    glUseProgram(program);
    GLint loc_target = glGetUniformLocation(program, "u_preview_target");
    GLint loc_resolution = glGetUniformLocation(program, "u_resolution");
    GLint loc_time = glGetUniformLocation(program, "u_time");

    if (loc_resolution >= 0) {
        glUniform2f(loc_resolution, static_cast<float>(width), static_cast<float>(height));
    }
    // u_time and u_params are already set by the main canvas pass for this frame.

    for (int i = 0; i < static_cast<int>(pipeline.size()); ++i) {
        const DagStep& step = pipeline[static_cast<size_t>(i)];
        if (!step.preview_open) continue;
        const DagNodeDef* def = dag_find(step.name);
        if (!def || def->kind == DagKind::Output) continue;

        PreviewSlot& slot = slot_for(step.editor_uid);
        fb_resize(slot.fb, width, height);

        glBindFramebuffer(GL_FRAMEBUFFER, slot.fb.fbo);
        glViewport(0, 0, width, height);

        if (loc_target >= 0)     glUniform1i(loc_target, i);
        if (loc_resolution >= 0) glUniform2f(loc_resolution, static_cast<float>(width), static_cast<float>(height));

        quad_draw(s_quad);
    }

    // Reset preview target so the main canvas pass renders the real Output
    if (loc_target >= 0) glUniform1i(loc_target, -1);

    // Restore state
    glUseProgram(static_cast<GLuint>(prev_program));
    glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prev_fbo));
    glViewport(prev_vp[0], prev_vp[1], prev_vp[2], prev_vp[3]);
    if (loc_time >= 0) {} // silence unused
}

unsigned dag_preview_texture(unsigned editor_uid) {
    auto it = s_slots.find(editor_uid);
    if (it == s_slots.end()) return 0;
    return it->second.fb.tex;
}

void dag_previews_destroy() {
    for (auto& kv : s_slots) {
        fb_destroy(kv.second.fb);
    }
    s_slots.clear();
    if (s_quad_init) {
        quad_destroy(s_quad);
        s_quad_init = false;
    }
}

} // namespace fn::gfx
