#include "viz/bar_chart.h"
#include "viz/plot_static.h"
#include "imgui.h"
#include "imgui_internal.h"
#include "implot.h"

#include <cmath>
#include <vector>

namespace {

constexpr float kPI = 3.14159265358979323846f;

// Dibuja `text` rotado `angle` rad alrededor de `pivot` usando el font atlas
// actual. Origen = primer glyph anclado en pivot; el texto avanza en la
// direccion (cos(angle), sin(angle)) en coords de pantalla (Y abajo).
void draw_text_rotated(ImDrawList* dl, ImFont* font, float font_size,
                       ImVec2 pivot, float angle, ImU32 col, const char* text) {
    if (!text || !*text) return;
    // En ImGui >= 1.92 los glyphs viven en ImFontBaked, no en ImFont. Hay
    // que pedir el baked al tamano deseado y usar sus coords (ya en px).
    ImFontBaked* baked = font->GetFontBaked(font_size);
    if (!baked) return;

    const float cos_a = std::cos(angle);
    const float sin_a = std::sin(angle);

    dl->PushTexture(font->OwnerAtlas->TexRef);

    float pen_x = 0.0f;
    for (const char* p = text; *p; ) {
        unsigned int c = 0;
        int len = ImTextCharFromUtf8(&c, p, nullptr);
        if (len <= 0) break;
        p += len;
        if (c < 0x20) continue;

        ImFontGlyph* g = baked->FindGlyph(static_cast<ImWchar>(c));
        if (!g) continue;

        if (g->Visible) {
            const float x0 = pen_x + g->X0;
            const float y0 = g->Y0;
            const float x1 = pen_x + g->X1;
            const float y1 = g->Y1;

            auto rot = [&](float x, float y) -> ImVec2 {
                return ImVec2(pivot.x + x * cos_a - y * sin_a,
                              pivot.y + x * sin_a + y * cos_a);
            };

            const ImVec2 p0 = rot(x0, y0);
            const ImVec2 p1 = rot(x1, y0);
            const ImVec2 p2 = rot(x1, y1);
            const ImVec2 p3 = rot(x0, y1);

            dl->PrimReserve(6, 4);
            dl->PrimQuadUV(p0, p1, p2, p3,
                           ImVec2(g->U0, g->V0), ImVec2(g->U1, g->V0),
                           ImVec2(g->U1, g->V1), ImVec2(g->U0, g->V1),
                           col);
        }
        pen_x += g->AdvanceX;
    }

    dl->PopTexture();
}

// Mide el ancho total de los labels como si fueran horizontales + padding,
// y decide si caben en `plot_width`. Si no caben, el chart los rotara 45
// grados en vez de mostrarlos horizontales (ver draw_bars).
bool labels_need_rotation(const char* const* labels, int count, float plot_width) {
    if (count <= 1 || plot_width <= 1.0f) return false;
    float total = 0.0f;
    constexpr float pad = 12.0f;
    for (int i = 0; i < count; i++) {
        total += ImGui::CalcTextSize(labels[i]).x + pad;
    }
    return total > plot_width;
}

// Barras verticales con ejes pineados, altura explicita, tooltip al pasar el
// mouse y labels rotados a 45 grados cuando no caben horizontalmente.
template <typename T>
void draw_bars(const char* title, const char* const* labels, const T* values,
               int count, double bar_width, float height) {
    if (count <= 0) return;

    double y_max = 0.0;
    for (int i = 0; i < count; i++) {
        if (static_cast<double>(values[i]) > y_max) y_max = static_cast<double>(values[i]);
    }
    if (y_max <= 0.0) y_max = 1.0;
    y_max *= 1.15;

    const float hint_width = ImGui::GetContentRegionAvail().x;
    const bool rotate = labels_need_rotation(labels, count, hint_width);

    // Reservamos ~48px abajo para los labels rotados (aprox 34px verticales
    // + padding). Sin rotacion, el plot ocupa toda la altura disponible.
    const float total_h = height > 0.0f ? height : 200.0f;
    const float label_reserve = rotate ? 48.0f : 0.0f;
    const ImVec2 plot_size(-1.0f, total_h - label_reserve);

    std::vector<double> positions(count);
    for (int i = 0; i < count; i++) positions[i] = i;

    if (ImPlot::BeginPlot(title, plot_size, plot_static::kPlotFlags)) {
        const ImPlotAxisFlags x_flags = plot_static::kAxisFlags | ImPlotAxisFlags_NoGridLines;
        const ImPlotAxisFlags y_flags = plot_static::kAxisFlags;

        ImPlot::SetupAxes(nullptr, nullptr, x_flags, y_flags);
        ImPlot::SetupAxisLimits(ImAxis_X1, -0.5, static_cast<double>(count) - 0.5,
                                ImPlotCond_Always);
        ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0, y_max, ImPlotCond_Always);

        if (rotate) {
            // Tick positions con labels vacios: ImPlot dibuja ticks pero no
            // texto. Los labels los dibujamos a mano rotados mas abajo.
            std::vector<const char*> empty(count, "");
            ImPlot::SetupAxisTicks(ImAxis_X1, positions.data(), count, empty.data());
        } else {
            ImPlot::SetupAxisTicks(ImAxis_X1, positions.data(), count, labels);
        }

        ImPlot::PlotBars("##data", values, count, bar_width);

        // Tooltip
        if (ImPlot::IsPlotHovered()) {
            ImPlotPoint mp = ImPlot::GetPlotMousePos();
            int idx = static_cast<int>(std::round(mp.x));
            if (idx >= 0 && idx < count
                && std::fabs(mp.x - idx) <= bar_width * 0.5) {
                ImGui::BeginTooltip();
                ImGui::TextUnformatted(labels[idx]);
                ImGui::Separator();
                ImGui::Text("%.0f", static_cast<double>(values[idx]));
                ImGui::EndTooltip();
            }
        }

        // Labels rotados: solo si decidimos rotar. Los dibujamos usando el
        // drawlist de la VENTANA (no el del plot) para que no se clipen al
        // rectangulo del plot y puedan extenderse en el margen inferior.
        if (rotate) {
            ImDrawList* dl = ImGui::GetWindowDrawList();
            ImFont* font = ImGui::GetFont();
            const float font_size = ImGui::GetFontSize();
            const ImU32 col = ImGui::GetColorU32(ImGuiCol_Text);
            const float angle = kPI * 0.25f; // 45 grados en pantalla (Y abajo)

            for (int i = 0; i < count; i++) {
                ImVec2 px = ImPlot::PlotToPixels(ImPlotPoint(static_cast<double>(i), 0.0));
                // Desplazamos el pivote un pelin abajo del eje X para que el
                // label no pise los ticks.
                ImVec2 pivot(px.x, px.y + 6.0f);
                draw_text_rotated(dl, font, font_size, pivot, angle, col, labels[i]);
            }
        }

        ImPlot::EndPlot();
    }
}

} // namespace

void bar_chart(const char* title, const char* const* labels, const float* values,
               int count, float bar_width, float height) {
    draw_bars<float>(title, labels, values, count, static_cast<double>(bar_width), height);
}

void bar_chart(const char* title, const char* const* labels, const double* values,
               int count, double bar_width, float height) {
    draw_bars<double>(title, labels, values, count, bar_width, height);
}
