#include "demos.h"
#include "demo.h"

#include "viz/graph_types.h"
#include "viz/graph_viewport.h"
#include "viz/graph_force_layout.h"
#include "core/button.h"
#include "core/tokens.h"

#include <imgui.h>
#include <cmath>
#include <cstdio>
#include <vector>

namespace gallery {

// Genera un grafo sintetico con N nodos en K clusters + aristas intra-cluster
// y unas pocas inter-cluster. Pensado para demostrar el rendimiento del
// pipeline graph_renderer + graph_force_layout + graph_viewport.
static void generate_synthetic_graph(int N, int K,
                                     std::vector<GraphNode>& nodes_out,
                                     std::vector<GraphEdge>& edges_out) {
    nodes_out.clear();
    edges_out.clear();
    nodes_out.reserve(N);
    edges_out.reserve(N * 3);

    unsigned seed = 0x1234abcd;
    auto rnd = [&]() {
        seed = seed * 1664525u + 1013904223u;
        return static_cast<float>((seed >> 8) & 0xffffff) / 16777216.0f;
    };

    // Paleta por cluster (ABGR)
    const uint32_t palette[] = {
        0xff5b8def, 0xff58ca8c, 0xfff5973e, 0xffd95150,
        0xffb87fe0, 0xff5fcdcc, 0xfff2cd52, 0xff99d161,
    };
    const int palette_n = sizeof(palette) / sizeof(palette[0]);

    // Asignar cluster + posicion inicial cerca del centroide del cluster
    std::vector<float> cluster_cx(K), cluster_cy(K);
    for (int k = 0; k < K; k++) {
        float angle = 2.0f * 3.14159f * k / K;
        cluster_cx[k] = std::cos(angle) * 200.0f;
        cluster_cy[k] = std::sin(angle) * 200.0f;
    }

    for (int i = 0; i < N; i++) {
        int k = i % K;
        GraphNode n = graph_node(static_cast<uint32_t>(i),
            cluster_cx[k] + (rnd() - 0.5f) * 80.0f,
            cluster_cy[k] + (rnd() - 0.5f) * 80.0f);
        n.size = 3.0f + rnd() * 2.0f;
        n.color = palette[k % palette_n];
        n.community = static_cast<uint32_t>(k);
        nodes_out.push_back(n);
    }

    // Aristas: ~3 por nodo dentro del cluster, +5% inter-cluster.
    auto add_edge = [&](uint32_t a, uint32_t b, float w) {
        if (a == b) return;
        edges_out.push_back(graph_edge(a, b, w));
    };
    int per_cluster = N / K;
    for (int k = 0; k < K; k++) {
        int base = k * per_cluster;
        int end  = (k == K - 1) ? N : (base + per_cluster);
        int size = end - base;
        if (size < 2) continue;
        // Dentro del cluster
        for (int i = base; i < end; i++) {
            for (int e = 0; e < 3; e++) {
                int j = base + static_cast<int>(rnd() * size);
                add_edge(static_cast<uint32_t>(i),
                         static_cast<uint32_t>(j), 1.0f);
            }
        }
    }
    // Inter-cluster (5% de los nodos)
    int inter = N / 20;
    for (int e = 0; e < inter; e++) {
        uint32_t a = static_cast<uint32_t>(rnd() * N);
        uint32_t b = static_cast<uint32_t>(rnd() * N);
        add_edge(a, b, 0.3f);
    }
}

void demo_graph() {
    demo_header("graph_viewport", "v1.0.0",
        "Pipeline completo de visualizacion de grafos: graph_renderer (instanced GPU) "
        "+ graph_force_layout (Barnes-Hut) + graph_spatial_hash (hit-testing). "
        "Render a FBO mostrado via ImGui::Image — escala a decenas de miles de nodos.");

    static int   s_n_nodes    = 1000;
    static int   s_n_clusters = 6;
    static float s_repulsion  = 3500.0f;   // fuerza de dispersion entre nodos
    static float s_attraction = 0.02f;     // muelle entre nodos conectados
    static float s_gravity    = 0.001f;    // tiron hacia el centro
    static std::vector<GraphNode> s_nodes;
    static std::vector<GraphEdge> s_edges;
    static GraphData s_graph{};
    static GraphViewportState s_state;
    static bool s_initialized = false;
    static bool s_needs_regen = true;

    if (s_needs_regen) {
        generate_synthetic_graph(s_n_nodes, s_n_clusters, s_nodes, s_edges);
        s_graph.nodes      = s_nodes.data();
        s_graph.node_count = static_cast<int>(s_nodes.size());
        s_graph.edges      = s_edges.data();
        s_graph.edge_count = static_cast<int>(s_edges.size());
        s_graph.update_bounds();
        s_state.layout_running = true;
        s_state.layout_energy  = 0.0f;
        s_needs_regen = false;
        s_initialized = true;
    }

    section("Controls");
    {
        using namespace fn_ui;
        // Sliders en dos filas para que quepan sin scrollbar
        ImGui::PushItemWidth(180);
        ImGui::SliderInt("Nodes",      &s_n_nodes,    100, 20000);
        ImGui::SameLine();
        ImGui::SliderInt("Clusters",   &s_n_clusters, 2,   16);
        ImGui::SliderFloat("Repulsion",   &s_repulsion,  100.0f, 20000.0f, "%.0f");
        ImGui::SameLine();
        ImGui::SliderFloat("Attraction",  &s_attraction, 0.001f, 0.5f,    "%.3f");
        ImGui::SameLine();
        ImGui::SliderFloat("Gravity",     &s_gravity,    0.0f,   0.05f,   "%.4f");
        ImGui::PopItemWidth();

        if (button("Regenerate", ButtonVariant::Primary)) s_needs_regen = true;
        ImGui::SameLine();
        if (button(s_state.layout_running ? "Pause layout" : "Resume layout",
                   ButtonVariant::Secondary)) {
            s_state.layout_running = !s_state.layout_running;
        }
        ImGui::SameLine();
        if (button("Fit view", ButtonVariant::Subtle)) {
            graph_viewport_fit(s_graph, s_state);
        }
    }

    section("Stats");
    {
        // Una sola linea fija — sin secciones condicionales que cambien la
        // altura del panel (eso provocaba que el viewport saltara al hacer
        // hover/select).
        char hover_buf[32];
        char sel_buf[32];
        if (s_state.hovered_node >= 0) {
            std::snprintf(hover_buf, sizeof(hover_buf), "#%d c%u",
                          s_state.hovered_node,
                          s_nodes[s_state.hovered_node].community);
        } else {
            std::snprintf(hover_buf, sizeof(hover_buf), "-");
        }
        if (s_state.selected_node >= 0) {
            std::snprintf(sel_buf, sizeof(sel_buf), "#%d", s_state.selected_node);
        } else {
            std::snprintf(sel_buf, sizeof(sel_buf), "-");
        }
        ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted);
        ImGui::Text("nodes=%d  edges=%d  energy=%.2f  fps=%.0f  |  hover=%s  sel=%s",
                    s_graph.node_count, s_graph.edge_count,
                    s_state.layout_energy, ImGui::GetIO().Framerate,
                    hover_buf, sel_buf);
        ImGui::PopStyleColor();
    }

    section("Viewport (drag = pan, wheel = zoom, click = select)");
    if (s_initialized) {
        // Avanzamos 1 paso de force layout cada frame mientras layout_running
        if (s_state.layout_running) {
            ForceLayoutConfig cfg;
            cfg.repulsion  = s_repulsion;
            cfg.attraction = s_attraction;
            cfg.gravity    = s_gravity;
            cfg.iterations = 1;
            s_state.layout_energy = graph_force_layout_step(s_graph, cfg);
        }
        graph_viewport("##graph_demo", s_graph, s_state, ImVec2(0, 460));
    }

    code_block(
        "static GraphData          graph;\n"
        "static GraphViewportState state;\n"
        "// ... rellenar graph.nodes / graph.edges ...\n"
        "graph.update_bounds();\n"
        "\n"
        "// Por frame:\n"
        "if (state.layout_running) {\n"
        "    ForceLayoutConfig cfg;\n"
        "    cfg.repulsion = 3500; cfg.gravity = 0.001f;\n"
        "    graph_force_layout_step(graph, cfg);\n"
        "}\n"
        "graph_viewport(\"##g\", graph, state, ImVec2(0, 460));"
    );
}

} // namespace gallery
