// terminal_panel.cpp — render + process_output + shared logic.
// Los backends (open/close/send) viven en terminal_panel_linux.cpp
// y terminal_panel_windows.cpp respectivamente.

#include "viz/terminal_panel/terminal_panel.h"
#include "core/logger.h"
#include "core/tokens.h"
#include "imgui.h"

#include <algorithm>
#include <cstring>
#include <string>

namespace fn_term {

namespace {

// Convierte índice de color fn_term (0-16) a ImU32 RGBA para ImGui.
// Usa la paleta kPalette16; fg=16 (default) → color de texto del tema ImGui.
ImU32 color_to_imu32(uint8_t idx, bool is_fg) {
    if (idx == kColorDefault) {
        // Usar color del tema: FG → Text, BG → transparente.
        if (is_fg) return ImGui::GetColorU32(ImGuiCol_Text);
        return IM_COL32(0, 0, 0, 0); // transparente
    }
    // kPalette16 está en formato ABGR (little-endian), ImU32 también es ABGR en ImGui.
    return static_cast<ImU32>(kPalette16[idx]);
}

// Renderiza una línea del scrollback con colores.
// Toma la línea como vector<AnsiCell> y escribe chunks de mismo color.
void render_line(const TermLine& line) {
    if (line.empty()) {
        ImGui::NewLine();
        return;
    }

    // Agrupar celdas consecutivas con mismo fg/bg/bold y emitir como texto.
    // Usamos un buffer temporal de la pila para evitar alloacs por línea.
    static char buf[4096];

    size_t i = 0;
    while (i < line.size()) {
        uint8_t fg   = line[i].fg;
        uint8_t bg   = line[i].bg;
        // uint8_t bold = line[i].bold; // TODO(0132): bold rendering v2

        // Acumular chars con mismo estilo.
        size_t j = i;
        int    pos = 0;
        while (j < line.size() && line[j].fg == fg && line[j].bg == bg) {
            char32_t ch = line[j].ch;
            if (ch >= 0x20 && ch < 0x7F && pos < (int)sizeof(buf) - 2) {
                buf[pos++] = static_cast<char>(ch);
            } else if (ch != U' ' && pos < (int)sizeof(buf) - 2) {
                buf[pos++] = '?'; // no-ASCII en v1
            } else if (pos < (int)sizeof(buf) - 2) {
                buf[pos++] = ' ';
            }
            j++;
        }
        buf[pos] = '\0';

        // Push color FG.
        ImU32 fg_col = color_to_imu32(fg, true);
        bool  has_fg = (fg != kColorDefault);
        if (has_fg) ImGui::PushStyleColor(ImGuiCol_Text, fg_col);

        // Fondo: si BG definido, usar InvisibleButton + DrawList rect antes del texto.
        // En v1 simplificamos: solo coloreamos el texto (FG). BG requiere DrawList.
        // TODO(0132): renderizar celdas BG con InvisibleButton + DrawList en v2.

        ImGui::TextUnformatted(buf, buf + pos);

        if (has_fg) ImGui::PopStyleColor();

        // Continuar en la misma línea si hay más celdas.
        if (j < line.size()) ImGui::SameLine(0.0f, 0.0f);

        i = j;
    }
}

} // namespace

TerminalPanel::TerminalPanel() {
    // Reservar una línea inicial vacía.
    lines.emplace_back();
}

TerminalPanel::~TerminalPanel() {
    if (is_open()) close(*this);
}

// ---------------------------------------------------------------------------
// process_output — llamado desde el reader thread.
// Parsea los bytes via AnsiParser y actualiza el scrollback buffer.
// ---------------------------------------------------------------------------
void process_output(TerminalPanel& panel, const char* data, size_t n) {
    std::lock_guard<std::mutex> lk(panel.buf_mutex);

    panel.parser.feed(data, n, [&](const AnsiEvent& ev) {
        switch (ev.type) {
        case AnsiEventType::Char: {
            // Asegurar que tenemos al menos cur_row+1 filas.
            while ((int)panel.lines.size() <= panel.cur_row)
                panel.lines.emplace_back();
            TermLine& line = panel.lines[panel.cur_row];
            // Asegurar que la fila tiene al menos cur_col+1 celdas.
            while ((int)line.size() <= panel.cur_col)
                line.push_back(AnsiCell{});
            line[panel.cur_col] = ev.cell;
            panel.cur_col++;
            break;
        }
        case AnsiEventType::Newline: {
            panel.cur_row++;
            // Scrollback circular: si excede el límite, eliminar la primera fila.
            while ((int)panel.lines.size() <= panel.cur_row)
                panel.lines.emplace_back();
            if ((int)panel.lines.size() > panel.scrollback_lines) {
                int excess = (int)panel.lines.size() - panel.scrollback_lines;
                panel.lines.erase(panel.lines.begin(),
                                   panel.lines.begin() + excess);
                panel.cur_row -= excess;
                if (panel.cur_row < 0) panel.cur_row = 0;
            }
            panel.scroll_to_bottom = true;
            break;
        }
        case AnsiEventType::CarriageReturn: {
            panel.cur_col = 0;
            break;
        }
        case AnsiEventType::Backspace: {
            if (panel.cur_col > 0) panel.cur_col--;
            break;
        }
        case AnsiEventType::CursorAbsolute: {
            panel.cur_row = std::max(0, ev.cursor_abs.row);
            panel.cur_col = std::max(0, ev.cursor_abs.col);
            // Extender líneas si necesario.
            while ((int)panel.lines.size() <= panel.cur_row)
                panel.lines.emplace_back();
            break;
        }
        case AnsiEventType::CursorMove: {
            switch (ev.cursor_rel.dir) {
            case CursorDir::Up:
                panel.cur_row = std::max(0, panel.cur_row - ev.cursor_rel.n);
                break;
            case CursorDir::Down:
                panel.cur_row += ev.cursor_rel.n;
                while ((int)panel.lines.size() <= panel.cur_row)
                    panel.lines.emplace_back();
                break;
            case CursorDir::Forward:
                panel.cur_col += ev.cursor_rel.n;
                break;
            case CursorDir::Back:
                panel.cur_col = std::max(0, panel.cur_col - ev.cursor_rel.n);
                break;
            }
            break;
        }
        case AnsiEventType::EraseDisplay: {
            panel.lines.clear();
            panel.lines.emplace_back();
            panel.cur_row = 0;
            panel.cur_col = 0;
            panel.parser.reset();
            break;
        }
        case AnsiEventType::EraseLine: {
            while ((int)panel.lines.size() <= panel.cur_row)
                panel.lines.emplace_back();
            panel.lines[panel.cur_row].clear();
            panel.cur_col = 0;
            break;
        }
        }
    });
}

// ---------------------------------------------------------------------------
// render — debe llamarse dentro de un frame ImGui activo.
// ---------------------------------------------------------------------------
void render(TerminalPanel& panel) {
    // --- Toolbar ---
    ImGui::PushID("##term_toolbar");

    if (ImGui::SmallButton("Clear")) {
        std::lock_guard<std::mutex> lk(panel.buf_mutex);
        panel.lines.clear();
        panel.lines.emplace_back();
        panel.cur_row = 0;
        panel.cur_col = 0;
    }
    ImGui::SameLine();

    if (ImGui::SmallButton("Copy")) {
        // Copiar todo el scrollback como texto plano al portapapeles.
        std::string text;
        std::lock_guard<std::mutex> lk(panel.buf_mutex);
        for (const auto& line : panel.lines) {
            for (const auto& cell : line) {
                if (cell.ch >= 0x20 && cell.ch < 0x7F)
                    text += static_cast<char>(cell.ch);
                else if (cell.ch != U' ')
                    text += '?';
                else
                    text += ' ';
            }
            text += '\n';
        }
        ImGui::SetClipboardText(text.c_str());
    }
    ImGui::SameLine();

    if (ImGui::SmallButton("Reset") && panel.is_open()) {
        fn_term::close(panel);
        fn_term::open(panel);
    }
    ImGui::SameLine();

    bool lock = !panel.scroll_to_bottom;
    if (ImGui::Checkbox("Lock scroll", &lock)) {
        panel.scroll_to_bottom = !lock;
    }
    ImGui::SameLine();

    // Indicador de estado del proceso.
    if (!panel.is_open()) {
        ImGui::TextDisabled("[closed]");
    } else if (panel.process_exited.load()) {
        ImGui::TextDisabled("[exited %d]", panel.exit_code);
    } else {
        ImGui::TextDisabled("[running]");
    }

    ImGui::PopID();

    // --- Scrollback area — fondo negro con texto gris claro ---
    ImVec2 avail = ImGui::GetContentRegionAvail();

    // Reservar hueco para el input prompt si no es readonly.
    // GetFrameHeightWithSpacing() cubre una línea de InputText + padding.
    const float input_reserve = (!panel.readonly)
        ? (ImGui::GetFrameHeightWithSpacing() + 6.0f)
        : 0.0f;
    float child_h = std::max(avail.y - input_reserve, 32.0f);

    // Estilos del area terminal: fondo casi negro + texto gris claro.
    ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(10, 10, 10, 255));
    ImGui::PushStyleColor(ImGuiCol_Text,    IM_COL32(220, 220, 220, 255));
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 6.0f));

    ImGui::BeginChild("##term_scroll", ImVec2(0, child_h),
                      ImGuiChildFlags_Borders,
                      ImGuiWindowFlags_HorizontalScrollbar);

    {
        std::lock_guard<std::mutex> lk(panel.buf_mutex);
        // Usar un clipper para evitar renderizar líneas fuera de vista.
        ImGuiListClipper clipper;
        clipper.Begin((int)panel.lines.size());
        while (clipper.Step()) {
            for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
                render_line(panel.lines[i]);
            }
        }
        clipper.End();
    }

    if (panel.scroll_to_bottom && ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 4.0f) {
        ImGui::SetScrollHereY(1.0f);
    }

    ImGui::EndChild();

    ImGui::PopStyleVar();     // WindowPadding
    ImGui::PopStyleColor(2);  // ChildBg + Text

    // --- Input prompt (visible siempre que readonly=false) ---
    if (!panel.readonly) {
        // Mostrar un prefijo "$ " antes del input box.
        ImGui::TextUnformatted("$ ");
        ImGui::SameLine(0.0f, 4.0f);

        static char s_input[1024] = {};
        ImGui::SetNextItemWidth(-1.0f);

        // Si el shell está cerrado, desactivar el input.
        if (!panel.is_open()) ImGui::BeginDisabled();
        bool enter = ImGui::InputText("##term_input", s_input, sizeof(s_input),
                                     ImGuiInputTextFlags_EnterReturnsTrue);
        if (!panel.is_open()) ImGui::EndDisabled();

        if (enter && panel.is_open()) {
            std::string cmd = std::string(s_input) + "\n";
            fn_term::send(panel, cmd);
            s_input[0] = '\0';
            ImGui::SetKeyboardFocusHere(-1);
        }
    }
}

} // namespace fn_term
