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

#include "network_tab.h"

#include "../../app.h"
#include "../../config/settings.h"
#include "../../data/wallet_state.h"
#include "../../util/i18n.h"
#include "../../util/lite_server_probe.h"
#include "../../wallet/lite_connection_service.h"
#include "../theme.h"
#include "../layout.h"
#include "../material/type.h"
#include "../material/draw_helpers.h"
#include "../material/colors.h"
#include "../effects/theme_effects.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <map>
#include <string>
#include <vector>

namespace dragonx {
namespace ui {

using namespace material;
using LiteMode = config::Settings::LiteServerSelectionPreferenceMode;

namespace {

util::LiteServerProbe s_probe;
bool  s_probeStarted = false;
bool  s_showHidden   = false;
char  s_addUrl[256]  = "";
char  s_addLabel[128] = "";
std::string s_addError;

// (Re)probe every server in the list (visible + hidden), so latency is ready when unhidden too.
void startProbe(config::Settings* st)
{
    std::vector<std::string> urls;
    for (const auto& s : st->getLiteServers()) urls.push_back(s.url);
    s_probe.start(std::move(urls));
}

ImU32 latencyColor(int ms)
{
    if (ms < 100) return Success();
    if (ms < 500) return Warning();
    return Error();
}

} // namespace

void RenderLiteNetworkTab(App* app)
{
    if (!app || !app->settings()) return;
    config::Settings* st = app->settings();
    const float dp = Layout::dpiScale();

    if (!s_probeStarted) { startProbe(st); s_probeStarted = true; }
    const auto probe = s_probe.results();

    const bool randomMode = st->getLiteServerSelectionMode() == LiteMode::Random;
    const std::string sticky = st->getLiteStickyServerUrl();

    auto applyAndRebuild = [&]() { st->save(); app->rebuildLiteWallet(/*force=*/true); };
    auto pinServer = [&](const std::string& url) {
        st->setLiteServerSelectionMode(LiteMode::Sticky);
        st->setLiteStickyServerUrl(url);
        applyAndRebuild();
    };

    ImFont* sub2  = Type().subtitle2();
    ImFont* body2 = Type().body2();
    ImFont* cap   = Type().caption();

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

    // ── Connection + sync status panel ─────────────────────────────────────────
    {
        const WalletState& ws = app->state();
        const SyncInfo& sync = ws.sync;
        const bool connected = ws.connected;
        ImDrawList* sdl = ImGui::GetWindowDrawList();
        const float panelH = 56.0f * dp;
        const float availPanelW = ImGui::GetContentRegionAvail().x;
        const float panelW = std::min(1000.0f * dp, availPanelW);
        const float panelOffsetX = std::max(0.0f, (availPanelW - panelW) * 0.5f);
        const float pad2 = 12.0f * dp;
        ImVec2 pMin = ImGui::GetCursorScreenPos();
        pMin.x += panelOffsetX;
        ImVec2 pMax(pMin.x + panelW, pMin.y + panelH);
        GlassPanelSpec sspec; sspec.rounding = 8.0f * dp;
        DrawGlassPanel(sdl, pMin, pMax, sspec);

        // Top row: status dot + connection label (left); in-use server + latency (right).
        ImU32 dotCol; const char* connLabel;
        if (!connected)        { dotCol = OnSurfaceDisabled(); connLabel = TR("lite_net_disconnected"); }
        else if (sync.syncing) { dotCol = Warning();           connLabel = TR("lite_net_syncing"); }
        else                   { dotCol = Success();           connLabel = TR("lite_net_synced"); }
        const float dotR = 5.0f * dp;
        sdl->AddCircleFilled(ImVec2(pMin.x + pad2 + dotR, pMin.y + 17.0f * dp), dotR, dotCol);
        sdl->AddText(sub2, sub2->LegacySize, ImVec2(pMin.x + pad2 + dotR * 2 + 8.0f * dp, pMin.y + 9.0f * dp),
                     OnSurface(), connLabel);

        std::string inUse = randomMode ? std::string(TR("lite_net_random_server"))
                                       : (sticky.empty() ? std::string("—") : util::liteServerHost(sticky));
        if (!randomMode && !sticky.empty()) {
            const auto it = probe.find(sticky);
            if (it != probe.end() && it->second.online)
                inUse += "  ·  " + std::to_string(it->second.latencyMs) + "ms";
        }
        const ImVec2 inUseSz = cap->CalcTextSizeA(cap->LegacySize, FLT_MAX, 0, inUse.c_str());
        sdl->AddText(cap, cap->LegacySize, ImVec2(pMax.x - pad2 - inUseSz.x, pMin.y + 12.0f * dp),
                     OnSurfaceMedium(), inUse.c_str());

        // Bottom row: sync detail + (while syncing) a thin progress bar.
        char syncBuf[160];
        if (!connected) {
            // If an auto-open actually failed, show why (e.g. server unreachable) instead of a
            // bare "no wallet open" — otherwise the disconnected state is silent/confusing.
            const std::string& openErr = app->liteOpenError();
            snprintf(syncBuf, sizeof(syncBuf), "%s: %s", TR("lite_net_sync_label"),
                     openErr.empty() ? TR("lite_net_no_wallet") : openErr.c_str());
        } else if (sync.syncing)
            snprintf(syncBuf, sizeof(syncBuf), "%s: %.1f%%  ·  %d / %d", TR("lite_net_sync_label"),
                     sync.verification_progress * 100.0, sync.blocks, sync.headers);
        else
            snprintf(syncBuf, sizeof(syncBuf), "%s: %s  ·  block %d", TR("lite_net_sync_label"),
                     TR("lite_net_synced"), sync.blocks);
        sdl->AddText(cap, cap->LegacySize, ImVec2(pMin.x + pad2, pMin.y + 32.0f * dp),
                     OnSurfaceMedium(), syncBuf);
        if (connected && sync.syncing) {
            const float barY = pMin.y + panelH - 6.0f * dp;
            const float barX = pMin.x + pad2;
            const float barW = panelW - pad2 * 2;
            const float barH = 2.5f * dp;
            const float pct = static_cast<float>(std::max(0.0, std::min(1.0, sync.verification_progress)));
            sdl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barH),
                               WithAlpha(OnSurface(), 25), barH * 0.5f);
            if (pct > 0)
                sdl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW * pct, barY + barH),
                                   Primary(), barH * 0.5f);
        }

        ImGui::Dummy(ImVec2(availPanelW, panelH));  // reserve the full row (proper boundary growth)
        ImGui::Spacing();
    }

    bool useRandom = randomMode;
    if (ImGui::Checkbox(TR("lite_net_use_random"), &useRandom)) {
        st->setLiteServerSelectionMode(useRandom ? LiteMode::Random : LiteMode::Sticky);
        applyAndRebuild();
    }
    ImGui::SameLine(ImGui::GetContentRegionAvail().x - 110.0f * dp);
    // While a probe is in flight, disable + relabel Refresh so there's clear in-progress feedback.
    const bool probing = s_probe.busy();
    ImGui::BeginDisabled(probing);
    if (TactileButton(probing ? TR("lite_net_checking") : TR("lite_net_refresh"), ImVec2(100.0f * dp, 0)))
        startProbe(st);
    ImGui::EndDisabled();
    if (randomMode)
        Type().textColored(TypeStyle::Caption, Primary(), TR("lite_net_random_active"));
    ImGui::Spacing();

    // ── Add custom server ──────────────────────────────────────────────────────
    {
        const float availW = ImGui::GetContentRegionAvail().x;
        const float addBtnW = 80.0f * dp;
        const float urlW = std::min(550.0f * dp, (availW - addBtnW - 16.0f * dp) * 0.6f);
        const float lblW = std::min(300.0f * dp, (availW - addBtnW - 16.0f * dp) * 0.4f);
        ImGui::SetNextItemWidth(urlW);
        ImGui::InputTextWithHint("##LiteAddUrl", TR("lite_net_add_url_hint"), s_addUrl, sizeof(s_addUrl));
        ImGui::SameLine();
        ImGui::SetNextItemWidth(lblW);
        ImGui::InputTextWithHint("##LiteAddLabel", TR("lite_net_add_label_hint"), s_addLabel, sizeof(s_addLabel));
        ImGui::SameLine();
        if (TactileButton(TR("lite_net_add"), ImVec2(addBtnW, 0))) {
            auto trimStr = [](std::string s) {
                while (!s.empty() && (s.front()==' '||s.front()=='\t'||s.front()=='\n'||s.front()=='\r')) s.erase(s.begin());
                while (!s.empty() && (s.back()==' '||s.back()=='\t'||s.back()=='\n'||s.back()=='\r')) s.pop_back();
                return s;
            };
            std::string url = trimStr(s_addUrl);
            std::string addLabel = trimStr(s_addLabel);
            if (!wallet::isLiteServerUrlUsable(url)) {
                s_addError = TR("lite_net_invalid_url");
            } else {
                auto servers = st->getLiteServers();
                bool exists = false;
                for (const auto& s : servers) if (s.url == url) { exists = true; break; }
                if (exists) {
                    // Duplicate: keep the inputs (so it doesn't look like the add succeeded); only
                    // clear any stale invalid-URL error since this URL is valid. The user-facing
                    // "already in list" message needs a shared i18n key added centrally, so no new
                    // text is set here.
                    s_addError.clear();
                } else {
                    config::Settings::LiteServerPreference p;
                    p.url = url;
                    p.label = !addLabel.empty() ? addLabel : url;
                    p.enabled = true;
                    servers.push_back(p);
                    st->setLiteServers(servers);
                    st->save();
                    startProbe(st);
                    s_addUrl[0] = '\0'; s_addLabel[0] = '\0'; s_addError.clear();
                }
            }
        }
        if (!s_addError.empty())
            Type().textColored(TypeStyle::Caption, Error(), s_addError.c_str());
    }
    ImGui::Spacing();

    // ── Card renderer ──────────────────────────────────────────────────────────
    const float cardH = 58.0f * dp;
    const float pad   = 12.0f * dp;
    const float gap   = 8.0f * dp;
    const float hideW = 44.0f * dp;
    ImDrawList* dl = ImGui::GetWindowDrawList();

    auto drawCard = [&](const config::Settings::LiteServerPreference& sv, bool hiddenList) {
        const std::string host = util::liteServerHost(sv.url);
        const bool official = wallet::isOfficialLiteServer(sv.url);
        const bool selected = !randomMode && sv.url == sticky;

        util::LiteServerProbeResult pr;
        auto it = probe.find(sv.url);
        if (it != probe.end()) pr = it->second;

        const float availCardW = ImGui::GetContentRegionAvail().x;
        const float cardW = std::min(1000.0f * dp, availCardW);
        const float cardOffsetX = std::max(0.0f, (availCardW - cardW) * 0.5f);
        ImVec2 cardMin = ImGui::GetCursorScreenPos();
        cardMin.x += cardOffsetX;
        ImVec2 cardMax(cardMin.x + cardW, cardMin.y + cardH);
        const float rnd = 8.0f * dp;
        const float cardBtnW = cardW - hideW;

        // Background + selection/official emphasis.
        GlassPanelSpec spec;
        spec.rounding = rnd;
        spec.fillAlpha = selected ? 34 : 18;
        DrawGlassPanel(dl, cardMin, cardMax, spec);
        // Glow only the ACTIVE (selected/in-use) node — officials are distinguished by their pill.
        if (selected) {
            auto& fx = effects::ThemeEffects::instance();
            if (fx.isEnabled()) {
                fx.drawGlowPulse(dl, cardMin, cardMax, rnd);
            }
            // Always-visible pulsing outline so the active node stands out even without effects.
            float pulse = 0.75f + 0.25f * (float)std::sin(ImGui::GetTime() * 2.0);
            dl->AddRect(cardMin, cardMax, WithAlpha(Primary(), (int)(150 * pulse)), rnd, 0, 1.6f * dp);
        }

        // Main click area selects the server (left of the hide strip).
        ImGui::SetCursorScreenPos(cardMin);
        ImGui::InvisibleButton(("##litecard_" + sv.url).c_str(), ImVec2(cardBtnW, cardH));
        const bool cardHovered = ImGui::IsItemHovered();
        const bool cardClicked = ImGui::IsItemClicked();
        if (cardHovered && !hiddenList) {
            ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
            if (!selected) dl->AddRectFilled(cardMin, cardMax, WithAlpha(OnSurface(), 8), rnd);
        }
        if (cardClicked && !hiddenList) pinServer(sv.url);

        // Left: label + host + ip.
        float tx = cardMin.x + pad + 4.0f * dp;
        dl->AddText(sub2, sub2->LegacySize, ImVec2(tx, cardMin.y + 9.0f * dp),
                    OnSurface(), sv.label.c_str());
        std::string sub = host;
        if (!pr.ip.empty() && pr.ip != host) sub += "  ·  " + pr.ip;
        dl->AddText(cap, cap->LegacySize, ImVec2(tx, cardMin.y + 9.0f * dp + sub2->LegacySize + 3.0f * dp),
                    OnSurfaceMedium(), sub.c_str());

        // Right (within the card-button area): latency dot + text, official/custom pill.
        float rx = cardMin.x + cardBtnW - pad;
        // latency text
        char lat[24];
        ImU32 latCol;
        if (!pr.probed)      { snprintf(lat, sizeof(lat), "%s", TR("lite_net_checking")); latCol = OnSurfaceDisabled(); }
        else if (!pr.online) { snprintf(lat, sizeof(lat), "%s", TR("lite_net_offline"));  latCol = Error(); }
        else                 { snprintf(lat, sizeof(lat), "%dms", pr.latencyMs);          latCol = latencyColor(pr.latencyMs); }
        ImVec2 latSz = cap->CalcTextSizeA(cap->LegacySize, FLT_MAX, 0, lat);
        float latX = rx - latSz.x;
        dl->AddText(cap, cap->LegacySize, ImVec2(latX, cardMin.y + cardH * 0.5f + 2.0f * dp), latCol, lat);
        // status dot left of latency text
        if (pr.probed) {
            float dotR = 3.5f * dp;
            dl->AddCircleFilled(ImVec2(latX - dotR - 4.0f * dp, cardMin.y + cardH * 0.5f + 2.0f * dp + cap->LegacySize * 0.5f),
                                dotR, pr.online ? latencyColor(pr.latencyMs) : Error());
        }
        // official/custom + in-use pills (top-right)
        const char* tag = official ? TR("lite_net_official") : TR("lite_net_custom");
        ImU32 tagBg = official ? WithAlpha(Primary(), 40) : WithAlpha(Secondary(), 30);
        ImU32 tagFg = official ? WithAlpha(Primary(), 230) : WithAlpha(Secondary(), 220);
        ImVec2 tagSz = cap->CalcTextSizeA(cap->LegacySize, FLT_MAX, 0, tag);
        float tagX = rx - tagSz.x;
        float tagY = cardMin.y + 9.0f * dp;
        material::DrawPill(dl, ImVec2(tagX - 6 * dp, tagY - 1 * dp), tag, cap, tagFg, tagBg, 0,
                           ImVec2(6 * dp, 1 * dp), 4.0f * dp);
        if (selected) {
            const char* inUse = TR("lite_net_in_use");
            ImVec2 uSz = cap->CalcTextSizeA(cap->LegacySize, FLT_MAX, 0, inUse);
            dl->AddText(cap, cap->LegacySize, ImVec2(tagX - uSz.x - 10 * dp, tagY), Success(), inUse);
        }

        // Hide / unhide button (far-right strip).
        ImGui::SetCursorScreenPos(ImVec2(cardMin.x + cardBtnW, cardMin.y));
        material::IconButtonStyle hideSt;
        hideSt.color = OnSurfaceDisabled();
        hideSt.hoverColor = OnSurface();
        hideSt.tooltip = hiddenList ? TR("lite_net_unhide") : TR("lite_net_hide");
        if (material::IconButton(("##litehide_" + sv.url).c_str(),
                                 hiddenList ? ICON_MD_VISIBILITY : ICON_MD_VISIBILITY_OFF,
                                 Type().iconSmall(), ImVec2(hideW, cardH), hideSt)) {
            if (hiddenList) st->unhideLiteServer(sv.url);
            else            st->hideLiteServer(sv.url);
            st->save();
        }

        // Advance to the next card via a real item (Dummy) below the card, so the scroll region's
        // content height actually grows — ImGui won't extend bounds from a bare SetCursorScreenPos.
        // Reset X to the (unshifted) region origin so next card's GetContentRegionAvail() isn't
        // cumulatively narrowed by this card's centering offset.
        ImGui::SetCursorScreenPos(ImVec2(cardMin.x - cardOffsetX, cardMax.y));
        ImGui::Dummy(ImVec2(availCardW, gap));
    };

    // ── Visible servers ────────────────────────────────────────────────────────
    ImVec2 scrollAvail = ImGui::GetContentRegionAvail();
    ImGui::BeginChild("##LiteServerScroll", scrollAvail, false,
                      ImGuiWindowFlags_NoBackground);
    for (const auto& sv : st->getLiteServers()) {
        if (st->isLiteServerHidden(sv.url)) continue;
        drawCard(sv, /*hiddenList=*/false);
    }

    // ── Hidden servers (toggle) ─────────────────────────────────────────────────
    {
        bool anyHidden = false;
        for (const auto& sv : st->getLiteServers())
            if (st->isLiteServerHidden(sv.url)) { anyHidden = true; break; }
        if (anyHidden) {
            ImGui::Spacing();
            ImGui::Checkbox(TR("lite_net_show_hidden"), &s_showHidden);
            if (s_showHidden) {
                Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("lite_net_hidden_section"));
                ImGui::Spacing();
                for (const auto& sv : st->getLiteServers()) {
                    if (!st->isLiteServerHidden(sv.url)) continue;
                    drawCard(sv, /*hiddenList=*/true);
                }
            }
        }
    }
    ImGui::EndChild();

    (void)body2;
}

} // namespace ui
} // namespace dragonx
