#include "viz/chord.h"

#include <cmath>
#include <vector>

namespace {

ImU32 chord_palette(int idx) {
    static const ImU32 P[] = {
        IM_COL32(120, 144, 252, 230),
        IM_COL32( 92, 200, 200, 230),
        IM_COL32(250, 176,  92, 230),
        IM_COL32(180, 120, 200, 230),
        IM_COL32( 92, 200, 130, 230),
        IM_COL32(250, 120, 130, 230),
        IM_COL32(180, 200,  92, 230),
        IM_COL32(120, 200, 230, 230),
    };
    constexpr int N = sizeof(P) / sizeof(P[0]);
    return P[((idx % N) + N) % N];
}

} // namespace

void chord(const char*       id,
           const float*      matrix,
           int               n,
           const char* const* labels,
           ImVec2            size) {
    ImGui::PushID(id);

    ImVec2 avail = ImGui::GetContentRegionAvail();
    float  W     = (size.x > 0.0f) ? size.x : (avail.x > 0.0f ? avail.x : 400.0f);
    float  H     = (size.y > 0.0f) ? size.y : 400.0f;

    ImVec2 origin = ImGui::GetCursorScreenPos();
    ImGui::Dummy(ImVec2(W, H));

    if (n <= 0 || matrix == nullptr) { ImGui::PopID(); return; }

    ImVec2 center(origin.x + W * 0.5f, origin.y + H * 0.5f);
    float  r_outer = 0.5f * std::min(W, H) - 30.0f;
    float  r_inner = r_outer - 12.0f;
    if (r_inner < 10.0f) { ImGui::PopID(); return; }

    // Sumas por nodo
    std::vector<float> totals(n, 0.0f);
    float grand = 0.0f;
    for (int i = 0; i < n; i++) {
        float s = 0.0f;
        for (int j = 0; j < n; j++) s += matrix[i * n + j];
        totals[i] = s;
        grand += s;
    }
    if (grand <= 0.0f) { ImGui::PopID(); return; }

    constexpr float kTAU = 6.28318530717958647692f;
    const float gap = 0.02f; // radianes entre arcos

    // angulos start/end por nodo
    std::vector<float> a_start(n), a_end(n);
    {
        float total_gap = gap * (float)n;
        float available = kTAU - total_gap;
        float a = -kTAU * 0.25f; // arrancar arriba
        for (int i = 0; i < n; i++) {
            float sweep = (totals[i] / grand) * available;
            a_start[i]  = a;
            a_end[i]    = a + sweep;
            a           = a_end[i] + gap;
        }
    }

    ImDrawList* dl = ImGui::GetWindowDrawList();

    // Render arcos exteriores (gruesos)
    for (int i = 0; i < n; i++) {
        if (a_end[i] - a_start[i] < 1e-4f) continue;
        const int   STEPS = std::max(8, (int)((a_end[i] - a_start[i]) * 64.0f));
        ImU32       col   = chord_palette(i);
        // Polygon entre r_inner y r_outer
        std::vector<ImVec2> poly;
        poly.reserve(STEPS * 2 + 2);
        for (int s = 0; s <= STEPS; s++) {
            float t = (float)s / (float)STEPS;
            float a = a_start[i] + (a_end[i] - a_start[i]) * t;
            poly.push_back(ImVec2(center.x + std::cos(a) * r_outer,
                                  center.y + std::sin(a) * r_outer));
        }
        for (int s = STEPS; s >= 0; s--) {
            float t = (float)s / (float)STEPS;
            float a = a_start[i] + (a_end[i] - a_start[i]) * t;
            poly.push_back(ImVec2(center.x + std::cos(a) * r_inner,
                                  center.y + std::sin(a) * r_inner));
        }
        dl->AddConvexPolyFilled(poly.data(), (int)poly.size(), col);
    }

    // Cuerdas: para cada par (i, j) con i <= j, area dentro del arco i proporcional
    // a matrix[i,j] / totals[i] del rango angular del nodo i. Idem para j.
    // Usamos dos puntos en el arco y bezier hacia el centro.
    // Distribuimos sub-arcos por nodo.
    std::vector<float> cursor(n, 0.0f);
    for (int i = 0; i < n; i++) cursor[i] = a_start[i];

    // Pasamos por todas las celdas (incluido diag y j<i): para no duplicar las
    // cuerdas, dibujamos solo i<=j combinando matrix[i,j]+matrix[j,i] donde aplica.
    // Para simplicidad: dibujamos matrix[i,j] como cuerda i->j independiente (asume simetrica
    // o el caller acepta que las cuerdas se solapen).
    for (int i = 0; i < n; i++) {
        if (totals[i] <= 0.0f) continue;
        float arc_i_span = a_end[i] - a_start[i];
        for (int j = 0; j < n; j++) {
            float v = matrix[i * n + j];
            if (v <= 0.0f || i == j) continue;
            float frac    = v / totals[i];
            float a_i_end = cursor[i] + frac * arc_i_span;

            // sub-arco en j ocupando proporcion de v / totals[j], partiendo del cursor de j
            float arc_j_span = a_end[j] - a_start[j];
            float frac_j     = (totals[j] > 0.0f) ? (v / totals[j]) : 0.0f;
            float a_j_end    = cursor[j] + frac_j * arc_j_span;

            // 4 puntos en el inner radius
            ImVec2 P0(center.x + std::cos(cursor[i]) * r_inner,
                      center.y + std::sin(cursor[i]) * r_inner);
            ImVec2 P1(center.x + std::cos(a_i_end)   * r_inner,
                      center.y + std::sin(a_i_end)   * r_inner);
            ImVec2 P2(center.x + std::cos(cursor[j]) * r_inner,
                      center.y + std::sin(cursor[j]) * r_inner);
            ImVec2 P3(center.x + std::cos(a_j_end)   * r_inner,
                      center.y + std::sin(a_j_end)   * r_inner);

            // poligono con bezier cubico desde P1 -> P2 (control hacia el centro) y P3 -> P0
            const int   STEPS = 24;
            std::vector<ImVec2> poly;
            poly.reserve(STEPS * 2 + 4);
            // arco inner desde P0 -> P1 (en el arco de i)
            for (int s = 0; s <= STEPS / 2; s++) {
                float t = (float)s / (float)(STEPS / 2);
                float a = cursor[i] + (a_i_end - cursor[i]) * t;
                poly.push_back(ImVec2(center.x + std::cos(a) * r_inner,
                                      center.y + std::sin(a) * r_inner));
            }
            // bezier P1 -> P2 con control en center
            for (int s = 1; s <= STEPS; s++) {
                float t = (float)s / (float)STEPS;
                float u = 1.0f - t;
                ImVec2 b;
                b.x = u*u*u * P1.x + 3*u*u*t * center.x + 3*u*t*t * center.x + t*t*t * P2.x;
                b.y = u*u*u * P1.y + 3*u*u*t * center.y + 3*u*t*t * center.y + t*t*t * P2.y;
                poly.push_back(b);
            }
            // arco inner P2 -> P3 (en el arco de j)
            for (int s = 1; s <= STEPS / 2; s++) {
                float t = (float)s / (float)(STEPS / 2);
                float a = cursor[j] + (a_j_end - cursor[j]) * t;
                poly.push_back(ImVec2(center.x + std::cos(a) * r_inner,
                                      center.y + std::sin(a) * r_inner));
            }
            // bezier P3 -> P0
            for (int s = 1; s <= STEPS; s++) {
                float t = (float)s / (float)STEPS;
                float u = 1.0f - t;
                ImVec2 b;
                b.x = u*u*u * P3.x + 3*u*u*t * center.x + 3*u*t*t * center.x + t*t*t * P0.x;
                b.y = u*u*u * P3.y + 3*u*u*t * center.y + 3*u*t*t * center.y + t*t*t * P0.y;
                poly.push_back(b);
            }

            ImU32 col = chord_palette(i);
            ImU32 band = (col & 0x00FFFFFF) | (60u << 24);
            // ImDrawList exige convexo; un chord no es estrictamente convexo pero
            // visualmente queda razonable. Fallback: usar AddPolyline.
            dl->AddConvexPolyFilled(poly.data(), (int)poly.size(), band);

            cursor[i] = a_i_end;
            cursor[j] = a_j_end;
        }
    }

    // Labels alrededor del circulo
    if (labels) {
        for (int i = 0; i < n; i++) {
            float am = (a_start[i] + a_end[i]) * 0.5f;
            float lr = r_outer + 8.0f;
            float lx = center.x + std::cos(am) * lr;
            float ly = center.y + std::sin(am) * lr;
            const char* lbl = labels[i] ? labels[i] : "";
            ImVec2 ts = ImGui::CalcTextSize(lbl);
            // alinear segun el lado
            float ax = (std::cos(am) < 0.0f) ? -ts.x : 0.0f;
            float ay = -ts.y * 0.5f;
            dl->AddText(ImVec2(lx + ax, ly + ay),
                        IM_COL32(220, 222, 235, 230), lbl);
        }
    }

    ImGui::PopID();
}
