// views_jobs.cpp — Jobs panel (issue 0026).
//
// Migration history (issue 0081-J):
//   v1: ImGui::BeginTable inline with 6 columns (status, enricher, target,
//       progress, time, ##actions).
//   v2: data_table::render for 5 data columns (status, enricher, target,
//       progress, time) with declarative CellRenderer (Badge, Progress,
//       Duration). Action buttons (Cancel/Delete) rendered in a separate
//       small ImGui::BeginTable below — option (a) from issue 0081-J spec.
//       Rationale: data_table::State does not expose selected_row_idx yet
//       (Phase 2 feature); a dedicated actions bar keeps all action logic
//       isolated and works with any filter state in the main table.
//       TODO(0081-J Phase 2): when data_table::State gets selected_row_idx,
//       migrate actions to option (b) toolbar per selected row.

#include "views.h"
#include "jobs.h"

#include "core/icons_tabler.h"
#include "core/tokens.h"
#include "core/data_table_types.h"
#include "viz/data_table.h"

#include "imgui.h"

#include <cfloat>
#include <chrono>
#include <cstdio>
#include <string>
#include <vector>

namespace ge {

namespace {

// Cache de la lista de jobs. Se refresca cada N frames para no abrir SQLite
// en cada frame. ~10 Hz es suficiente para una progress bar fluida.
struct JobsCache {
    std::vector<JobRow> rows;
    int     last_frame_refresh = -100;
    int     filter_idx         = 0;       // 0=all 1=active 2=done 3=error
};
JobsCache g_jobs_cache;

ImVec4 status_color(const std::string& s) {
    if (s == "running")   return ImVec4(0.36f, 0.78f, 1.0f, 1.0f);
    if (s == "done")      return ImVec4(0.40f, 0.85f, 0.55f, 1.0f);
    if (s == "error")     return ImVec4(0.95f, 0.45f, 0.45f, 1.0f);
    if (s == "cancelled") return ImVec4(0.65f, 0.65f, 0.65f, 1.0f);
    if (s == "queued")    return ImVec4(0.85f, 0.78f, 0.45f, 1.0f);
    return ImVec4(0.7f, 0.7f, 0.7f, 1.0f);
}

bool filter_match(const std::string& status, int idx) {
    switch (idx) {
        case 0: return true;
        case 1: return status == "queued" || status == "running";
        case 2: return status == "done";
        case 3: return status == "error" || status == "cancelled";
        default: return true;
    }
}

// Duration helper: computes elapsed ms for running jobs (finished_at=0).
long long job_duration_ms(const JobRow& r) {
    if (r.started_at <= 0) return 0;
    long long end = r.finished_at > 0
        ? r.finished_at
        : std::chrono::duration_cast<std::chrono::milliseconds>(
              std::chrono::system_clock::now().time_since_epoch()).count();
    long long ms = end - r.started_at;
    return ms < 0 ? 0 : ms;
}

} // namespace

void views_jobs(AppState& app) {
    if (!app.panel_jobs) return;

    if (!ImGui::Begin("Jobs", &app.panel_jobs)) {
        ImGui::End();
        return;
    }

    // Refresh cache cada ~10 frames (~6 Hz a 60fps).
    int frame = ImGui::GetFrameCount();
    if (frame - g_jobs_cache.last_frame_refresh > 10) {
        jobs_list(&g_jobs_cache.rows, 200);
        g_jobs_cache.last_frame_refresh = frame;
    }

    // Header: counters + filtro.
    JobCounters c = jobs_counters();
    ImGui::TextColored(status_color("running"), "%s", TI_PLAYER_PLAY);
    ImGui::SameLine(); ImGui::Text("%d", c.running);
    ImGui::SameLine(0, 16);
    ImGui::TextColored(status_color("queued"), "%s", TI_HOURGLASS);
    ImGui::SameLine(); ImGui::Text("%d", c.queued);
    ImGui::SameLine(0, 16);
    ImGui::TextColored(status_color("done"), "%s", TI_CHECK);
    ImGui::SameLine(); ImGui::Text("%d", c.done);
    ImGui::SameLine(0, 16);
    ImGui::TextColored(status_color("error"), "%s", TI_ALERT_CIRCLE);
    ImGui::SameLine(); ImGui::Text("%d", c.error + c.cancelled);

    ImGui::SameLine(0, 24);
    const char* filter_labels[] = { "All", "Active", "Done", "Errors" };
    ImGui::SetNextItemWidth(100);
    ImGui::Combo("##jobs_filter", &g_jobs_cache.filter_idx,
                 filter_labels, IM_ARRAYSIZE(filter_labels));

    ImGui::Separator();

    // -------------------------------------------------------------------------
    // Actions mini-table (option a — Fase 1; TODO: migrate to selected-row
    // toolbar when data_table::State exposes selected_row_idx in Phase 2).
    //
    // Shows Cancel/Delete buttons for every row that passes the current filter.
    // Fixed-height child so it doesn't push the data_table below the panel.
    // We pre-compute the list of actionable rows (active or terminal).
    // -------------------------------------------------------------------------

    // Collect visible rows for actions.
    std::vector<const JobRow*> visible;
    visible.reserve(g_jobs_cache.rows.size());
    for (const auto& r : g_jobs_cache.rows) {
        if (filter_match(r.status, g_jobs_cache.filter_idx))
            visible.push_back(&r);
    }

    // Actions bar height: up to 5 rows visible, ~22px each + header ~22px.
    // Only render if there are any rows with actionable buttons.
    bool any_actionable = false;
    for (const auto* rp : visible) {
        if (rp->status == "queued" || rp->status == "running" ||
            rp->status == "done"   || rp->status == "error"   ||
            rp->status == "cancelled") {
            any_actionable = true;
            break;
        }
    }

    if (any_actionable && !visible.empty()) {
        int visible_rows = (int)visible.size();
        int capped       = visible_rows < 5 ? visible_rows : 5;
        float row_h      = ImGui::GetTextLineHeightWithSpacing();
        float child_h    = row_h * (float)(capped + 1) + ImGui::GetStyle().ItemSpacing.y * 2.0f;
        child_h          = child_h < 120.0f ? child_h : 120.0f; // hard cap

        ImGui::TextDisabled("Actions");
        if (ImGui::BeginChild("##jobs_actions", ImVec2(0.0f, child_h),
                              ImGuiChildFlags_Borders)) {
            ImGuiTableFlags aflags = ImGuiTableFlags_Borders |
                                     ImGuiTableFlags_RowBg   |
                                     ImGuiTableFlags_SizingStretchProp |
                                     ImGuiTableFlags_ScrollY;
            if (ImGui::BeginTable("##jobs_actions_tbl", 3, aflags)) {
                ImGui::TableSetupScrollFreeze(0, 1);
                ImGui::TableSetupColumn("Job",    ImGuiTableColumnFlags_WidthStretch, 3.0f);
                ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed,  70.0f);
                ImGui::TableSetupColumn("",       ImGuiTableColumnFlags_WidthFixed,  64.0f);
                ImGui::TableHeadersRow();

                for (const auto* rp : visible) {
                    const JobRow& r = *rp;
                    ImGui::PushID(r.id.c_str());
                    ImGui::TableNextRow();

                    // Job label (enricher + target truncated).
                    ImGui::TableSetColumnIndex(0);
                    std::string label = r.enricher_id;
                    if (!r.node_name.empty()) label += " / " + r.node_name;
                    else if (!r.node_id.empty()) label += " / " + r.node_id;
                    if (label.size() > 40) label = label.substr(0, 38) + "…";
                    ImGui::TextUnformatted(label.c_str());

                    // Status badge.
                    ImGui::TableSetColumnIndex(1);
                    ImGui::TextColored(status_color(r.status), "%s", r.status.c_str());

                    // Action button.
                    ImGui::TableSetColumnIndex(2);
                    if (r.status == "queued" || r.status == "running") {
                        if (ImGui::SmallButton("Cancel")) {
                            jobs_cancel(r.id.c_str());
                            // Invalidate cache immediately.
                            g_jobs_cache.last_frame_refresh = -100;
                        }
                    } else {
                        if (ImGui::SmallButton("Delete")) {
                            jobs_delete(r.id.c_str());
                            g_jobs_cache.last_frame_refresh = -100;
                        }
                    }
                    ImGui::PopID();
                }
                ImGui::EndTable();
            }
        }
        ImGui::EndChild();
        ImGui::Spacing();
    }

    // -------------------------------------------------------------------------
    // Main data table via data_table::render (issue 0081-J migration).
    //
    // Columns:
    //   0: status   — Badge renderer (queued/running/done/error/cancelled)
    //   1: enricher — Text (default)
    //   2: target   — Text (default)
    //   3: progress — Progress renderer, 0..1 scale
    //   4: time_ms  — Duration renderer, warn=1000ms error=10000ms
    //
    // Rows are filtered by g_jobs_cache.filter_idx BEFORE populating
    // cells, so data_table only sees the subset the user wants.
    //
    // Cell backing: static vectors rebuilt when cache refreshes.
    // -------------------------------------------------------------------------

    static const std::vector<std::string> k_headers = {
        "status", "enricher", "target", "progress", "time_ms"
    };
    constexpr int k_ncols = 5;

    static std::vector<std::string> s_cell_backing;
    static std::vector<const char*> s_cells;
    static int s_rows_cached = -1;
    static int s_filter_cached = -1;

    // Rebuild when cache was refreshed or filter changed.
    bool needs_rebuild = (s_rows_cached != (int)g_jobs_cache.rows.size()) ||
                         (s_filter_cached != g_jobs_cache.filter_idx) ||
                         (frame - g_jobs_cache.last_frame_refresh <= 10); // just refreshed

    if (needs_rebuild) {
        // Collect filtered rows.
        std::vector<const JobRow*> frows;
        frows.reserve(g_jobs_cache.rows.size());
        for (const auto& r : g_jobs_cache.rows) {
            if (filter_match(r.status, g_jobs_cache.filter_idx))
                frows.push_back(&r);
        }

        const int nrows = (int)frows.size();
        s_cell_backing.resize((size_t)nrows * k_ncols);
        s_cells.resize((size_t)nrows * k_ncols);

        for (int i = 0; i < nrows; ++i) {
            const JobRow& r = *frows[(size_t)i];
            const size_t base = (size_t)i * k_ncols;

            // status
            s_cell_backing[base + 0] = r.status;

            // enricher
            s_cell_backing[base + 1] = r.enricher_id;

            // target: node_name > node_id > "(global)"
            if (!r.node_name.empty())
                s_cell_backing[base + 2] = r.node_name;
            else if (!r.node_id.empty())
                s_cell_backing[base + 2] = r.node_id;
            else
                s_cell_backing[base + 2] = "(global)";

            // progress: float 0..1 -> string (data_table Progress renderer
            // parses via strtof; value is in [0,1]).
            char pbuf[16];
            std::snprintf(pbuf, sizeof(pbuf), "%.4f", (float)r.progress);
            s_cell_backing[base + 3] = pbuf;

            // time_ms: duration in milliseconds as float string.
            char tbuf[24];
            long long ms = job_duration_ms(r);
            std::snprintf(tbuf, sizeof(tbuf), "%lld", ms);
            s_cell_backing[base + 4] = tbuf;
        }

        for (size_t k = 0; k < s_cell_backing.size(); ++k)
            s_cells[k] = s_cell_backing[k].c_str();

        s_rows_cached   = (int)g_jobs_cache.rows.size();
        s_filter_cached = g_jobs_cache.filter_idx;
    }

    // Build column specs with declarative renderers.
    static const std::vector<data_table::ColumnSpec> k_col_specs = []() {
        std::vector<data_table::ColumnSpec> specs(k_ncols);

        // Col 0: status — Badge
        specs[0].id       = "status";
        specs[0].renderer = data_table::CellRenderer::Badge;
        specs[0].badges   = {
            { "running",   "#3b82f6", "Running"   },
            { "done",      "#22c55e", "Done"       },
            { "error",     "#ef4444", "Error"      },
            { "cancelled", "#9ca3af", "Cancelled"  },
            { "queued",    "#f59e0b", "Queued"     },
        };

        // Col 1: enricher — Text (default)
        specs[1].id       = "enricher";
        specs[1].renderer = data_table::CellRenderer::Text;

        // Col 2: target — Text (default)
        specs[2].id       = "target";
        specs[2].renderer = data_table::CellRenderer::Text;

        // Col 3: progress — Progress bar, values are 0..1
        specs[3].id                   = "progress";
        specs[3].renderer             = data_table::CellRenderer::Progress;
        specs[3].progress_scale_100   = false;

        // Col 4: time_ms — Duration, warn=1000ms error=10000ms
        specs[4].id                   = "time_ms";
        specs[4].renderer             = data_table::CellRenderer::Duration;
        specs[4].duration_warn_ms     = 1000.0f;
        specs[4].duration_error_ms    = 10000.0f;

        return specs;
    }();

    // Compute available height for main table.
    float avail_h = ImGui::GetContentRegionAvail().y - 4.0f;
    if (avail_h < 80.0f) avail_h = 80.0f;

    ImGui::TextDisabled("Data");
    ImGui::BeginChild("##jobs_data", ImVec2(0.0f, avail_h), ImGuiChildFlags_None);

    int nrows_vis = s_rows_cached; // total cached — filtered subset
    // Recompute actual filtered row count for the data child.
    int nrows_data = 0;
    for (const auto& r : g_jobs_cache.rows) {
        if (filter_match(r.status, g_jobs_cache.filter_idx)) ++nrows_data;
    }
    (void)nrows_vis;

    if (nrows_data == 0) {
        ImGui::TextDisabled("(no jobs match current filter)");
    } else {
        data_table::TableInput tbl;
        tbl.name         = "jobs";
        tbl.headers      = k_headers;
        tbl.types        = {
            data_table::ColumnType::String,  // status
            data_table::ColumnType::String,  // enricher
            data_table::ColumnType::String,  // target
            data_table::ColumnType::Float,   // progress
            data_table::ColumnType::Float,   // time_ms
        };
        tbl.cells        = s_cells.empty() ? nullptr : s_cells.data();
        tbl.rows         = nrows_data;
        tbl.cols         = k_ncols;
        tbl.column_specs = k_col_specs;

        data_table::render("##jobs_dt", {tbl}, app.jobs_dt_state);
    }

    ImGui::EndChild();
    ImGui::End();
}

} // namespace ge
