#include "selectable_text.h"

#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <string>

namespace fn_ui {

namespace {

// Empuja un estilo "frame transparente" para que InputText quede
// visualmente como Text() — sin borde, sin fondo, sin padding interno.
struct ScopedTransparentFrame {
    ScopedTransparentFrame() {
        ImGui::PushStyleColor(ImGuiCol_FrameBg,        IM_COL32(0,0,0,0));
        ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0,0,0,0));
        ImGui::PushStyleColor(ImGuiCol_FrameBgActive,  IM_COL32(0,0,0,0));
        ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
        ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
        ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(0, 0));
    }
    ~ScopedTransparentFrame() {
        ImGui::PopStyleVar(3);
        ImGui::PopStyleColor(3);
    }
};

// Genera un id ImGui estable a partir del puntero del texto. Suficiente
// para evitar colisiones en el mismo frame mientras el caller no llame
// dos veces con la misma direccion.
void make_id(char* out, size_t n, const char* prefix, const char* text) {
    std::snprintf(out, n, "##%s%p", prefix, (const void*)text);
}

} // namespace

// ----------------------------------------------------------------------------
// Versión wrapped (InputTextMultiline + read-only)
// ----------------------------------------------------------------------------
void selectable_text_wrapped(const char* text) {
    if (!text) text = "";
    if (!*text) {
        ImGui::Dummy(ImVec2(0, ImGui::GetTextLineHeight()));
        return;
    }
    float wrap_w = ImGui::GetContentRegionAvail().x;
    if (wrap_w < 32.0f) wrap_w = 32.0f;

    // CalcTextSize con wrap_w nos da la altura exacta del bloque renderizado.
    ImVec2 sz = ImGui::CalcTextSize(text, nullptr, false, wrap_w);
    if (sz.y < ImGui::GetTextLineHeight()) sz.y = ImGui::GetTextLineHeight();
    // Pequeño margen para evitar que aparezca el scrollbar vertical.
    float h = sz.y + 2.0f;

    ScopedTransparentFrame _frame;
    char id[40];
    make_id(id, sizeof(id), "selw", text);
    size_t len = std::strlen(text);
    // ImGui requiere buffer no-const aunque ReadOnly impide escritura.
    ImGui::InputTextMultiline(id,
        const_cast<char*>(text), len + 1,
        ImVec2(wrap_w, h),
        ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_NoHorizontalScroll);
}

// ----------------------------------------------------------------------------
// Variante con wrap forzado (rompe palabras sin espacios)
// ----------------------------------------------------------------------------
namespace {

// Inserta '\n' donde el ancho acumulado de la linea supera wrap_w.
// Maneja UTF-8: no rompe en mitad de un codepoint multi-byte. Si la
// palabra actual cabe completa, espera al proximo separador; si no,
// fuerza un break al limite. Espacios y newlines existentes se
// respetan.
std::string force_wrap(const char* text, float wrap_w) {
    std::string out;
    if (!text || !*text || wrap_w <= 0.0f) {
        if (text) out = text;
        return out;
    }
    out.reserve(std::strlen(text) + std::strlen(text) / 32);

    auto is_utf8_cont = [](unsigned char b) { return (b & 0xC0) == 0x80; };
    auto width_of = [](const char* a, const char* b) -> float {
        return ImGui::CalcTextSize(a, b).x;
    };

    const char* line_start = text;
    const char* last_space = nullptr;
    const char* p          = text;

    while (*p) {
        if (*p == '\n') {
            out.append(line_start, p + 1);
            line_start = p + 1;
            last_space = nullptr;
            ++p;
            continue;
        }
        if (*p == ' ' || *p == '\t') last_space = p;

        // Avanzar al final del codepoint UTF-8 actual.
        const char* cp_end = p + 1;
        while (*cp_end && is_utf8_cont((unsigned char)*cp_end)) ++cp_end;

        float w = width_of(line_start, cp_end);
        if (w > wrap_w && cp_end != line_start) {
            if (last_space && last_space > line_start) {
                out.append(line_start, last_space);
                out.push_back('\n');
                line_start = last_space + 1;
                last_space = nullptr;
                p = line_start;
                continue;
            }
            // Sin espacio en la linea — break forzado al codepoint actual.
            // Si solo hay un codepoint, evitamos un loop infinito
            // dejando que se pinte aunque no quepa.
            if (p == line_start) {
                p = cp_end;
                continue;
            }
            out.append(line_start, p);
            out.push_back('\n');
            line_start = p;
            last_space = nullptr;
            continue;
        }
        p = cp_end;
    }
    if (line_start < p) out.append(line_start, p);
    return out;
}

} // namespace

void selectable_text_wrapped_force(const char* text) {
    if (!text) text = "";
    if (!*text) {
        ImGui::Dummy(ImVec2(0, ImGui::GetTextLineHeight()));
        return;
    }
    float wrap_w = ImGui::GetContentRegionAvail().x;
    if (wrap_w < 32.0f) wrap_w = 32.0f;
    // Margen para el padding interno del InputText — sin esto la
    // ultima letra de una linea larga puede quedar tras el borde.
    float effective_w = wrap_w - 4.0f;

    std::string wrapped = force_wrap(text, effective_w);

    ImVec2 sz = ImGui::CalcTextSize(wrapped.c_str(), nullptr, false, wrap_w);
    if (sz.y < ImGui::GetTextLineHeight()) sz.y = ImGui::GetTextLineHeight();
    float h = sz.y + 2.0f;

    ScopedTransparentFrame _frame;
    char id[40];
    make_id(id, sizeof(id), "selwf", text);
    ImGui::InputTextMultiline(id,
        const_cast<char*>(wrapped.c_str()), wrapped.size() + 1,
        ImVec2(wrap_w, h),
        ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_NoHorizontalScroll);
}

void selectable_text_wrapped_fmt(const char* fmt, ...) {
    char buf[4096];
    va_list ap;
    va_start(ap, fmt);
    int n = std::vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
    if (n < 0) return;
    selectable_text_wrapped(buf);
}

// ----------------------------------------------------------------------------
// Versión single-line (InputText + read-only)
// ----------------------------------------------------------------------------
void selectable_text(const char* text) {
    if (!text) text = "";
    if (!*text) return;
    ScopedTransparentFrame _frame;
    char id[40];
    make_id(id, sizeof(id), "sel", text);
    ImVec2 sz = ImGui::CalcTextSize(text);
    ImGui::SetNextItemWidth(sz.x + 4.0f);
    size_t len = std::strlen(text);
    ImGui::InputText(id, const_cast<char*>(text), len + 1,
                      ImGuiInputTextFlags_ReadOnly
                          | ImGuiInputTextFlags_AutoSelectAll);
}

void selectable_text_fmt(const char* fmt, ...) {
    char buf[1024];
    va_list ap;
    va_start(ap, fmt);
    int n = std::vsnprintf(buf, sizeof(buf), fmt, ap);
    va_end(ap);
    if (n < 0) return;
    selectable_text(buf);
}

// ----------------------------------------------------------------------------
// Versión ligera con right-click → Copy
// ----------------------------------------------------------------------------
void text_with_copy(const char* text) {
    if (!text) text = "";
    ImGui::TextUnformatted(text);
    if (ImGui::BeginPopupContextItem("##copy_one")) {
        if (ImGui::MenuItem("Copy")) ImGui::SetClipboardText(text);
        ImGui::EndPopup();
    }
}

void text_wrapped_with_copy(const char* text) {
    if (!text) text = "";
    ImGui::TextWrapped("%s", text);
    if (ImGui::BeginPopupContextItem("##copy_one_w")) {
        if (ImGui::MenuItem("Copy")) ImGui::SetClipboardText(text);
        ImGui::EndPopup();
    }
}

} // namespace fn_ui
