// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3

#include "lite_console_tab.h"
#include "console_input_model.h"  // reuse the generic command-history helpers

#include "../../app.h"
#include "../../data/wallet_state.h"
#include "../../util/i18n.h"
#include "../../wallet/lite_diagnostics.h"
#include "../../wallet/lite_wallet_controller.h"
#include "../layout.h"
#include "../material/type.h"
#include "../material/colors.h"
#include "imgui.h"

#include <algorithm>
#include <cctype>
#include <cfloat>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>

namespace dragonx {
namespace ui {

using namespace material;

namespace {

struct ConsoleLine {
    std::string text;
    ImU32 color;
};

// Unified, in-memory console buffer: it interleaves the automatic diagnostics log (lifecycle /
// connection / sync events) with the user's interactive commands and their responses, the same
// way the full-node Console interleaves the daemon log with RPC I/O. NOTE: this buffer can hold
// secret command output (e.g. `seed`/`export`) and is NEVER persisted to LiteDiagnostics.
std::vector<ConsoleLine> s_lines;
std::uint64_t s_diagGen = static_cast<std::uint64_t>(-1);  // last consumed diagnostics generation
char s_input[512] = "";
std::vector<std::string> s_history;
int s_historyIndex = -1;
bool s_autoScroll = true;
bool s_scrollToBottom = false;
bool s_focusInput = false;

// Colour error/success lines for at-a-glance scanning (substring match on the messages the
// controller emits). Anything else renders in the muted default colour.
ImU32 logColor(const std::string& line)
{
    const auto has = [&line](const char* s) { return line.find(s) != std::string::npos; };
    if (has("failed") || has(" unreachable") || has("blocked") || has("could not") ||
        has("Error") || has("error"))
        return Error();
    if (has(": connected") || has("opened") || has("wallet ready") || has("Ready"))
        return Success();
    return OnSurfaceMedium();
}

// Append text as one or more lines (splitting on '\n' so multi-line JSON responses render row by row).
void appendLines(const std::string& text, ImU32 color)
{
    std::size_t start = 0;
    while (start <= text.size()) {
        std::size_t nl = text.find('\n', start);
        std::string line = text.substr(start, nl == std::string::npos ? std::string::npos : nl - start);
        while (!line.empty() && (line.back() == '\r')) line.pop_back();
        s_lines.push_back({std::move(line), color});
        if (nl == std::string::npos) break;
        start = nl + 1;
    }
}

std::string lowerFirstToken(const std::string& cmd)
{
    std::size_t b = cmd.find_first_not_of(" \t");
    if (b == std::string::npos) return {};
    std::size_t e = cmd.find_first_of(" \t", b);
    std::string tok = cmd.substr(b, e == std::string::npos ? std::string::npos : e - b);
    std::transform(tok.begin(), tok.end(), tok.begin(),
                   [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
    return tok;
}

// InputText history navigation (Up/Down) — reuses the generic console history helpers.
int inputCallback(ImGuiInputTextCallbackData* data)
{
    if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory) {
        const bool up = (data->EventKey == ImGuiKey_UpArrow);
        s_historyIndex = NavigateConsoleHistoryIndex(s_historyIndex, s_history.size(), up);
        const std::string entry = ConsoleHistoryEntry(s_history, s_historyIndex);
        data->DeleteChars(0, data->BufTextLen);
        if (!entry.empty()) data->InsertChars(0, entry.c_str());
    }
    return 0;
}

}  // namespace

void RenderLiteConsoleTab(App* app)
{
    if (!app) return;
    wallet::LiteWalletController* lw = app->liteWallet();

    // ── Header ──────────────────────────────────────────────────────────────────
    Type().text(TypeStyle::H6, TR("lite_console_title"));
    Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("lite_console_intro"));
    ImGui::Spacing();

    // ── Live status (read straight from the controller — always meaningful) ───────
    {
        const char* connText;
        ImU32 connCol;
        if (!lw) {
            connText = "Lite backend not linked in this build (rebuild with --lite-backend)";
            connCol = Error();
        } else if (lw->walletOpen()) {
            connText = "Connected";
            connCol = Success();
        } else if (lw->openInProgress()) {
            connText = "Connecting\xE2\x80\xA6";  // ellipsis
            connCol = Warning();
        } else {
            connText = "Disconnected";
            connCol = Error();
        }
        Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("lite_console_status"));
        ImGui::SameLine();
        Type().textColored(TypeStyle::Body2, connCol, connText);

        if (lw && lw->walletOpen()) {
            const SyncInfo& sync = app->state().sync;
            char buf[96];
            if (sync.syncing && !sync.isSynced()) {
                double vp = sync.verification_progress;
                if (vp < 0.0) vp = 0.0; else if (vp > 1.0) vp = 1.0;
                std::snprintf(buf, sizeof(buf), "Syncing %.1f%%  (block %d / %d)",
                              vp * 100.0, sync.blocks, sync.headers);
            } else {
                std::snprintf(buf, sizeof(buf), "Synced  (block %d)", sync.blocks);
            }
            Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), buf);
        }
        const std::string& openErr = app->liteOpenError();
        if (!openErr.empty() && (!lw || !lw->walletOpen())) {
            Type().textColored(TypeStyle::Caption, Error(),
                               (std::string("Last error: ") + openErr).c_str());
        }
    }
    ImGui::Spacing();

    // ── Ingest new automatic diagnostics events into the unified buffer ───────────
    auto& diag = wallet::LiteDiagnostics::instance();
    const std::uint64_t gen = diag.generation();
    if (gen != s_diagGen) {
        const auto snap = diag.snapshot();
        // First pass dumps the whole ring; later passes append only the delta (generation counts
        // total events ever added; the ring is bounded, so cap the delta at the snapshot size).
        std::size_t startIdx = 0;
        if (s_diagGen != static_cast<std::uint64_t>(-1)) {
            const std::uint64_t added = gen - s_diagGen;
            startIdx = (added >= snap.size()) ? 0 : snap.size() - static_cast<std::size_t>(added);
        }
        for (std::size_t i = startIdx; i < snap.size(); ++i)
            s_lines.push_back({snap[i], logColor(snap[i])});
        if (gen != s_diagGen && !snap.empty()) s_scrollToBottom = true;
        s_diagGen = gen;
    }

    // ── Drain a completed interactive command ────────────────────────────────────
    if (lw) {
        wallet::LiteConsoleResult res;
        if (lw->takeConsoleResult(res)) {
            appendLines(res.response.empty() ? "(no output)" : res.response,
                        res.ok ? OnSurface() : Error());
            s_scrollToBottom = true;
        }
    }

    // ── Toolbar ─────────────────────────────────────────────────────────────────
    if (ImGui::Button(TR("lite_console_clear"))) {
        s_lines.clear();
        s_diagGen = diag.generation();  // don't re-dump the whole ring on the next frame
    }
    ImGui::SameLine();
    if (ImGui::Button(TR("lite_console_copy"))) {
        std::string all;
        for (const auto& l : s_lines) { all += l.text; all.push_back('\n'); }
        ImGui::SetClipboardText(all.c_str());
    }
    ImGui::SameLine();
    ImGui::Checkbox(TR("lite_console_autoscroll"), &s_autoScroll);
    ImGui::Spacing();

    // ── Output log (terminal-styled scroll region) ───────────────────────────────
    const float inputRowH = ImGui::GetFrameHeightWithSpacing() + Layout::spacingSm();
    ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 90));
    ImGui::BeginChild("##LiteConsoleLog", ImVec2(0, -inputRowH), true,
                      ImGuiWindowFlags_HorizontalScrollbar);
    ImGui::PushFont(Type().caption());
    if (s_lines.empty()) {
        ImGui::PushStyleColor(ImGuiCol_Text, OnSurfaceDisabled());
        ImGui::TextUnformatted(TR("lite_console_empty"));
        ImGui::PopStyleColor();
    } else {
        for (const auto& line : s_lines) {
            ImGui::PushStyleColor(ImGuiCol_Text, line.color);
            ImGui::TextUnformatted(line.text.c_str());  // not format-interpreted — safe for any content
            ImGui::PopStyleColor();
        }
    }
    if (s_scrollToBottom && s_autoScroll) ImGui::SetScrollHereY(1.0f);
    else if (s_autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) ImGui::SetScrollHereY(1.0f);
    s_scrollToBottom = false;
    ImGui::PopFont();
    ImGui::EndChild();
    ImGui::PopStyleColor();

    // ── Command input row ────────────────────────────────────────────────────────
    const bool busy = lw && lw->consoleCommandInProgress();
    auto submit = [&]() {
        std::string cmd = s_input;
        // trim
        const std::size_t b = cmd.find_first_not_of(" \t");
        if (b == std::string::npos) { s_input[0] = '\0'; return; }
        cmd = cmd.substr(b);
        while (!cmd.empty() && (cmd.back() == ' ' || cmd.back() == '\t')) cmd.pop_back();

        s_lines.push_back({"> " + cmd, Primary()});
        AppendConsoleHistory(s_history, cmd);
        s_historyIndex = -1;

        const std::string first = lowerFirstToken(cmd);
        if (first == "clear" || first == "cls") {
            // The backend's `clear` wipes wallet tx history — NOT the screen. Intercept it as a
            // view-clear (what the user expects) so a stray "clear" can't destroy history.
            s_lines.clear();
            s_diagGen = diag.generation();
        } else if (first == "quit" || first == "exit") {
            appendLines(TR("lite_console_quit_note"), OnSurfaceMedium());
        } else if (!lw) {
            appendLines("Lite backend not linked in this build.", Error());
        } else if (!lw->runConsoleCommand(cmd)) {
            appendLines(TR("lite_console_busy"), Warning());
        }
        s_input[0] = '\0';
        s_scrollToBottom = true;
        s_focusInput = true;  // keep focus for the next command
    };

    ImGui::PushFont(Type().caption());
    Type().textColored(TypeStyle::Caption, busy ? Warning() : Primary(),
                       busy ? TR("lite_console_running") : "\xE2\x80\xBA");  // ‹running…› / "›"
    ImGui::SameLine();
    ImGui::SetNextItemWidth(-FLT_MIN);
    if (busy) ImGui::BeginDisabled();
    const ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue |
                                      ImGuiInputTextFlags_CallbackHistory;
    if (s_focusInput) { ImGui::SetKeyboardFocusHere(); s_focusInput = false; }
    if (ImGui::InputTextWithHint("##LiteConsoleInput", TR("lite_console_input_hint"),
                                 s_input, sizeof(s_input), flags, inputCallback)) {
        submit();
    }
    if (busy) ImGui::EndDisabled();
    ImGui::PopFont();
}

}  // namespace ui
}  // namespace dragonx
