#include "app_base.h"
#include "imgui.h"

#include "gfx/shader_canvas.h"
#include "gfx/gl_shader.h"
#include "gfx/uniform_parser.h"
#include "gfx/uniform_panel.h"
#include "gfx/dag_catalog.h"
#include "gfx/dag_compile.h"
#include "gfx/dag_uniforms.h"
#include "gfx/dag_panel.h"
#include "gfx/dag_node_editor.h"
#include "gfx/dag_palette.h"
#include "gfx/dag_node_previews.h"
#include "gfx/code_to_generator.h"
#include "gfx/shaderlab_db.h"
#include "core/panel_menu.h"
#include "core/layouts_menu.h"
#include "core/layout_storage.h"
#include "core/modal_dialog.h"
#include "core/text_input.h"
#include "core/button.h"
#include "core/tokens.h"

#include "compiler.h"

#include <chrono>
#include <cctype>
#include <cstring>
#include <string>
#include <utility>
#include <vector>

// Globals: linked extern desde compiler.cpp. NO `static` aqui.
fn::gfx::ShaderCanvas g_canvas_code;
fn::gfx::ShaderCanvas g_canvas_dag;

// Default placeholder so the Code panel does something useful on first launch
// without committing to one specific look.
static const char* CODE_PLACEHOLDER = R"glsl(// Escribe tu fragment shader aqui.
// Declara uniforms con anotaciones (// @slider, @color, @xy)
// para que aparezcan como controles al guardar como generator.

uniform vec3 u_color; // @color default=0.5,0.2,0.8
uniform float u_speed; // @slider min=0.1 max=5 default=1

void main() {
    vec2 uv = gl_FragCoord.xy / u_resolution;
    float t = u_time * u_speed;
    vec3 c = u_color * (0.5 + 0.5 * cos(t + uv.xyx + vec3(0.0, 2.0, 4.0)));
    fragColor = vec4(c, 1.0);
}
)glsl";

std::string g_source = CODE_PLACEHOLDER;
std::string g_code_err;
int g_code_err_line = -1;
std::chrono::steady_clock::time_point g_code_last_edit;
bool g_code_dirty = true;
std::vector<fn::gfx::UniformDescriptor> g_descs;
fn::gfx::UniformStore g_store;

std::vector<fn::gfx::DagStep> g_pipeline;
std::string g_dag_glsl;
std::string g_dag_err;
int g_dag_err_line = -1;
static bool g_dag_dirty = true;  // solo lo usa main.cpp

// ── Panel visibility (toggled from View menu and panel close button) ──────
static bool g_show_code      = true;
static bool g_show_dag       = true;
static bool g_show_canvas_c  = true;
static bool g_show_canvas_d  = true;
static bool g_show_controls  = true;
static bool g_show_functions = true;
static bool g_show_generated = true;

// Tabla de paneles toggleables que fn::run_app pasa a app_menubar cada frame.
// Vive en el ambito del archivo para poder referenciarse desde main().
static constexpr fn_ui::PanelToggle k_panels[] = {
    {"Code",          "Ctrl+1", &g_show_code},
    {"DAG Pipeline",  "Ctrl+2", &g_show_dag},
    {"Canvas Code",   "Ctrl+3", &g_show_canvas_c},
    {"Canvas DAG",    "Ctrl+4", &g_show_canvas_d},
    {"Controls",      "Ctrl+5", &g_show_controls},
    {"Functions",     "Ctrl+6", &g_show_functions},
    {"Generated GLSL","Ctrl+7", &g_show_generated},
};

// ── Layouts (named ImGui ini snapshots persisted in shaders_lab.db) ───────
// El storage opaco encapsula la BD y el blob pendiente. Los callbacks
// envuelven save/apply/delete/reset y se pasan a app_menubar tal cual.
static fn_ui::LayoutStorage*  g_layouts = nullptr;
static fn_ui::LayoutCallbacks g_layout_cb;

// ── Save-as-generator modal state ─────────────────────────────────────────
static bool   g_save_modal_open = false;
static char   g_save_name[64]   = "my_shader";
static char   g_save_label[64]  = "my shader";
static char   g_save_desc[256]  = "";
static char   g_save_tags[128]  = "shaders_lab,user";
static std::string g_save_err;

// compile_code, compile_dag, mark_code_dirty viven en compiler.cpp
using shaders_lab::compile_code;
using shaders_lab::compile_dag;
using shaders_lab::mark_code_dirty;

static void ensure_dag_default() {
    if (g_pipeline.empty()) {
        const fn::gfx::DagNodeDef* plasma = fn::gfx::dag_find("plasma");
        if (plasma) {
            fn::gfx::DagStep s;
            s.id     = "n_plasma";
            s.name   = plasma->name;
            s.params = plasma->param_defaults;
            g_pipeline.push_back(s);
        }
    }
    bool has_output = false;
    for (const auto& s : g_pipeline) {
        const fn::gfx::DagNodeDef* d = fn::gfx::dag_find(s.name);
        if (d && d->kind == fn::gfx::DagKind::Output) { has_output = true; break; }
    }
    if (!has_output) {
        const fn::gfx::DagNodeDef* out = fn::gfx::dag_find("output");
        if (out) {
            fn::gfx::DagStep s;
            s.id   = "n_output";
            s.name = out->name;
            s.editor_pos_x = 500.0f;
            if (!g_pipeline.empty()) s.source_ids[0] = g_pipeline.front().id;
            g_pipeline.push_back(s);
        }
    }
}

static void draw_err(const std::string& msg, int line) {
    if (msg.empty()) return;
    ImGui::Separator();
    if (line > 0) {
        ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "line %d: %s", line, msg.c_str());
    } else {
        ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "%s", msg.c_str());
    }
}

// snake_case validation: lowercase letters, digits, underscores; first char a-z.
static bool valid_id(const char* s) {
    if (!s || !*s) return false;
    if (!(*s >= 'a' && *s <= 'z')) return false;
    for (const char* p = s; *p; ++p) {
        char c = *p;
        if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_')) return false;
    }
    return true;
}

// Build a DagNodeDef from current Code source + form fields, persist it, and
// register in the live catalog. Returns "" on success or an error message.
static std::string save_current_as_generator() {
    if (!valid_id(g_save_name)) return "name must be snake_case (a-z, 0-9, _) and start with a letter";

    if (fn::gfx::dag_find(g_save_name)) {
        const fn::gfx::DagNodeDef* existing = fn::gfx::dag_find(g_save_name);
        if (existing && existing->is_builtin) {
            return std::string("name '") + g_save_name + "' collides with a built-in node";
        }
        // user node with same name → overwrite is allowed
    }

    auto tr = fn::gfx::code_to_generator(g_source);
    if (!tr.ok) return tr.err;

    fn::gfx::GeneratorRecord rec;
    rec.id              = g_save_name;
    rec.label           = g_save_label[0] ? g_save_label : g_save_name;
    rec.description     = g_save_desc;
    rec.source_glsl     = g_source;
    rec.body_glsl       = tr.body_template;
    rec.param_count     = tr.param_count;
    rec.param_defaults  = tr.param_defaults;
    rec.param_names     = tr.param_names;
    rec.controls        = tr.controls;
    rec.tags            = g_save_tags;

    std::string err;
    if (!fn::gfx::shaderlab_db_save_generator(rec, &err)) {
        return std::string("db save failed: ") + err;
    }

    fn::gfx::DagNodeDef def = fn::gfx::make_generator_def(rec.id, rec.label, rec.description, tr);
    if (!fn::gfx::dag_register_node(def)) {
        return std::string("could not register node '") + rec.id + "'";
    }
    return "";
}

// Reconstitute every persisted generator and inject it into the live catalog.
static void load_user_generators_into_catalog() {
    for (const auto& rec : fn::gfx::shaderlab_db_list_generators()) {
        // Re-translate body_template from source to keep the lambda fresh.
        // (We could trust rec.body_glsl, but re-running ensures forward-compat
        //  when we tweak the translator.)
        auto tr = fn::gfx::code_to_generator(rec.source_glsl);
        if (!tr.ok) continue; // skip broken records

        fn::gfx::DagNodeDef def = fn::gfx::make_generator_def(rec.id, rec.label, rec.description, tr);
        fn::gfx::dag_register_node(def);
    }
}

static void render() {
    // Apply pending layout BEFORE any ImGui::Begin this frame.
    // (LoadIniSettingsFromMemory must happen before windows are submitted.)
    std::string applied = fn_ui::layout_storage_apply_pending(g_layouts);
    if (!applied.empty()) g_layout_cb.active_name = applied;

    if (!g_canvas_code.initialized) fn::gfx::canvas_init(g_canvas_code);
    if (!g_canvas_dag.initialized)  fn::gfx::canvas_init(g_canvas_dag);

    if (g_code_dirty) {
        auto now = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - g_code_last_edit).count();
        if (elapsed > 250) {
            compile_code();
            g_code_dirty = false;
        }
    }
    if (g_dag_dirty) {
        compile_dag();
        g_dag_dirty = false;
    }

    ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());

    // Menubar (View + Layouts + Settings + About) la invoca fn::run_app a
    // partir de AppConfig::panels y AppConfig::layouts_cb.

    // --- Code window ---
    if (g_show_code) {
    if (ImGui::Begin("Code", &g_show_code)) {
        if (fn_ui::button("Save as generator...", fn_ui::ButtonVariant::Secondary)) {
            g_save_modal_open = true;
            g_save_err.clear();
        }

        if (fn_ui::modal_dialog_begin("Save as generator", &g_save_modal_open,
                                      ImVec2(420, 0))) {
            ImGui::TextUnformatted("Guardar shader actual como nodo Gen del DAG.");
            ImGui::Spacing();
            fn_ui::text_input("name (snake_case)", g_save_name, sizeof(g_save_name),
                              "ej: my_shader");
            fn_ui::text_input("label", g_save_label, sizeof(g_save_label));
            ImGui::InputTextMultiline("description", g_save_desc, sizeof(g_save_desc),
                                      ImVec2(380, 60));
            fn_ui::text_input("tags (CSV)", g_save_tags, sizeof(g_save_tags));

            if (!g_save_err.empty()) {
                ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::error);
                ImGui::TextWrapped("%s", g_save_err.c_str());
                ImGui::PopStyleColor();
            }

            ImGui::Separator();
            if (fn_ui::button("Cancel", fn_ui::ButtonVariant::Subtle)) {
                g_save_modal_open = false;
            }
            ImGui::SameLine();
            if (fn_ui::button("Save", fn_ui::ButtonVariant::Primary)) {
                g_save_err = save_current_as_generator();
                if (g_save_err.empty()) {
                    g_save_modal_open = false;
                }
            }
        }
        fn_ui::modal_dialog_end();

        ImVec2 avail = ImGui::GetContentRegionAvail();
        float footer_h = g_code_err.empty() ? 0.0f : ImGui::GetTextLineHeightWithSpacing() + 8.0f;
        ImVec2 editor_size(avail.x, avail.y - footer_h);

        char buf[1 << 16];
        size_t copy_len = g_source.size() < sizeof(buf) - 1 ? g_source.size() : sizeof(buf) - 1;
        memcpy(buf, g_source.c_str(), copy_len);
        buf[copy_len] = '\0';

        if (ImGui::InputTextMultiline("##code", buf, sizeof(buf), editor_size,
                                      ImGuiInputTextFlags_AllowTabInput)) {
            g_source = buf;
            mark_code_dirty();
        }

        draw_err(g_code_err, g_code_err_line);
    }
    ImGui::End();
    }

    // --- DAG Pipeline window ---
    if (g_show_dag) {
    if (ImGui::Begin("DAG Pipeline", &g_show_dag)) {
        if (fn::gfx::dag_node_editor(g_pipeline)) {
            g_dag_dirty = true;
        }
        draw_err(g_dag_err, g_dag_err_line);
    }
    ImGui::End();
    }

    // --- Canvas Code ---
    // Code is fully independent from the DAG: only the uniforms declared in
    // the Code source itself (parsed via parse_uniforms) get fed. To reproduce
    // a DAG render here, paste the *baked* "Generated GLSL" — its u_params live
    // as a const array inside the source.
    if (g_show_canvas_c) {
    if (ImGui::Begin("Canvas Code", &g_show_canvas_c)) {
        fn::gfx::canvas_render(g_canvas_code, static_cast<float>(ImGui::GetTime()),
            [](unsigned int program) {
                fn::gfx::uniforms_apply(g_store, g_descs, program);
            });
    }
    ImGui::End();
    }

    // --- Canvas DAG ---
    if (g_show_canvas_d) {
    if (ImGui::Begin("Canvas DAG", &g_show_canvas_d)) {
        fn::gfx::canvas_render(g_canvas_dag, static_cast<float>(ImGui::GetTime()),
            [](unsigned int program) {
                fn::gfx::dag_uniforms_apply(g_pipeline, program);
            });
    }
    ImGui::End();
    }

    if (g_canvas_dag.program) {
        fn::gfx::dag_previews_render(g_pipeline, g_canvas_dag.program);
    }

    // --- Controls window (Code uniforms) ---
    if (g_show_controls) {
    if (ImGui::Begin("Controls", &g_show_controls)) {
        if (g_descs.empty()) {
            ImGui::TextDisabled("No uniforms declared in Code.");
            ImGui::TextDisabled("Use // @slider, @color, @toggle, @xy annotations.");
        } else {
            fn::gfx::uniforms_panel(g_store, g_descs);
        }
        // fps_overlay ahora se renderiza desde fn::run_app cuando el usuario
        // lo activa en Settings → Show FPS overlay.
    }
    ImGui::End();
    }

    // --- Functions palette (drag into DAG Pipeline) ---
    if (g_show_functions) {
    if (ImGui::Begin("Functions", &g_show_functions)) {
        fn::gfx::dag_palette();
    }
    ImGui::End();
    }

    // --- Generated GLSL window (self-contained DAG → paste-able into Code) ---
    // We bake the live params into a `const vec4 u_params[]` so the displayed
    // text is a complete shader: copy-pasting it into the Code editor yields
    // the same render at the moment of the copy, and nothing in the DAG can
    // change the Code canvas afterwards.
    if (g_show_generated) {
    if (ImGui::Begin("Generated GLSL", &g_show_generated)) {
        if (g_pipeline.empty()) {
            ImGui::TextDisabled("(DAG not compiled yet)");
        } else {
            static std::string s_baked;
            s_baked = fn::gfx::compile_dag_to_glsl_baked(g_pipeline);
            ImVec2 avail = ImGui::GetContentRegionAvail();
            ImGui::InputTextMultiline("##dag_glsl",
                const_cast<char*>(s_baked.c_str()),
                s_baked.size() + 1,
                avail,
                ImGuiInputTextFlags_ReadOnly);
        }
    }
    ImGui::End();
    }
}

int main() {
    fn::gfx::shaderlab_db_open("shaders_lab.db");
    load_user_generators_into_catalog();
    ensure_dag_default();

    // Layout persistence: handle opaco que crea su propia tabla
    // imgui_layouts en shaders_lab.db (CREATE IF NOT EXISTS, no toca la
    // tabla ui_layouts heredada). Cualquier app del registry puede usar
    // este patron.
    g_layouts = fn_ui::layout_storage_open("shaders_lab.db");
    fn_ui::layout_storage_make_callbacks(g_layouts, g_layout_cb);

    // Override de on_reset: ademas de limpiar el INI, re-mostrar todos
    // los paneles especificos de shaders_lab.
    g_layout_cb.on_reset = []() {
        g_show_code = g_show_dag = g_show_canvas_c = g_show_canvas_d =
            g_show_controls = g_show_functions = g_show_generated = true;
        ImGui::LoadIniSettingsFromMemory("", 0);
        ImGui::GetIO().WantSaveIniSettings = true;
        g_layout_cb.active_name.clear();
    };

    fn::AppConfig cfg;
    cfg.title       = "shaders_lab";
    cfg.width       = 1600;
    cfg.height      = 900;
    cfg.about       = {.name        = "shaders_lab",
                       .version     = "0.3.0",
                       .description = "Live GLSL shader playground with DAG pipeline. layout_storage publico, compiler extraido, AppConfig estandar, multi-viewport, modal save-as via modal_dialog."};
    cfg.panels         = k_panels;
    cfg.panel_count    = sizeof(k_panels) / sizeof(k_panels[0]);
    cfg.layouts_cb     = &g_layout_cb;
    cfg.log            = {"shaders_lab.log", 1};
    cfg.auto_dockspace = false; // shaders_lab gestiona su propio DockSpace en render()
    int rc = fn::run_app(cfg, render);

    fn::gfx::canvas_destroy(g_canvas_code);
    fn::gfx::canvas_destroy(g_canvas_dag);
    fn::gfx::dag_node_editor_destroy();
    fn::gfx::dag_previews_destroy();
    fn_ui::layout_storage_close(g_layouts);
    fn::gfx::shaderlab_db_close();
    return rc;
}
