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

#include "mining_earnings.h"
#include "mining_tab_helpers.h"  // EstimateHoursToBlock

#include "../../app.h"
#include "../../config/version.h"
#include "../../util/i18n.h"
#include "../../util/platform.h"
#include "../../config/settings.h"
#include "../schema/ui_schema.h"
#include "../material/type.h"
#include "../material/draw_helpers.h"
#include "../material/colors.h"
#include "../layout.h"
#include "../notifications.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"
#include "imgui_internal.h"

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

namespace dragonx {
namespace ui {

using namespace material;

// Earnings filter: 0 = All, 1 = Solo, 2 = Pool. (Moved here with the card it belongs to.)
static int s_earnings_filter = 0;

void RenderMiningEarnings(App* app, const WalletState& state, const MiningInfo& mining,
                          ImDrawList* dl, ImFont* capFont, ImFont* sub1, ImFont* ovFont,
                          float dp, float vs, float gap, float pad, bool isMiningActive,
                          bool poolMode, float availWidth,
                          const GlassPanelSpec& glassSpec, float sHdr, float gapOver)
{
        const bool s_pool_mode = poolMode;  // alias: keep the moved body's identifier verbatim
        char buf[128];                      // section-local scratch (was shared in the parent)
        // Gather mining transactions from state
        double minedToday = 0.0, minedYesterday = 0.0, minedAllTime = 0.0;
        int minedTodayCount = 0, minedYesterdayCount = 0, minedAllTimeCount = 0;
        int64_t now = (int64_t)std::time(nullptr);

        // Calendar-day boundaries (local time)
        time_t nowT = (time_t)now;
        struct tm local;
#ifdef _WIN32
        localtime_s(&local, &nowT);
#else
        localtime_r(&nowT, &local);
#endif
        local.tm_hour = 0;
        local.tm_min = 0;
        local.tm_sec = 0;
        int64_t todayStart = (int64_t)mktime(&local);
        int64_t yesterdayStart = todayStart - 86400;

        struct MinedTx {
            int64_t timestamp;
            double amount;
            int confirmations;
            bool mature;
            std::string txid;
            bool isPoolPayout;
        };
        std::vector<MinedTx> recentMined;

        for (const auto& tx : state.transactions) {
            bool isSoloMined = (tx.type == "generate" || tx.type == "immature" || tx.type == "mined");
            bool isPoolPayout = (tx.type == "receive"
                                 && !tx.memo.empty()
                                 && tx.memo.find("Mining Pool payout") != std::string::npos);
            if (isSoloMined || isPoolPayout) {
                // Apply earnings filter
                if (!app->supportsSoloMining()) {
                    if (s_earnings_filter == 1 && !isPoolPayout) continue;
                } else {
                    if (s_earnings_filter == 1 && !isSoloMined) continue;
                    if (s_earnings_filter == 2 && !isPoolPayout) continue;
                }

                double amt = std::abs(tx.amount);
                minedAllTime += amt;
                minedAllTimeCount++;
                if (tx.timestamp >= todayStart) {
                    minedToday += amt;
                    minedTodayCount++;
                } else if (tx.timestamp >= yesterdayStart) {
                    minedYesterday += amt;
                    minedYesterdayCount++;
                }
                // Separate solo blocks from pool payouts based on current mode
                bool showInCurrentMode = s_pool_mode ? isPoolPayout : isSoloMined;
                if (showInCurrentMode && recentMined.size() < 4) {
                    recentMined.push_back({tx.timestamp, amt, tx.confirmations, tx.confirmations >= 100, tx.txid, isPoolPayout});
                }
            }
        }

        // Est. Daily = expected reward for YOUR own hashrate share of the network. In pool mode this uses
        // your local miner rate (pool_mining.hashrate_10s), NOT the pool's aggregate rate: pool payouts are
        // share-proportional, so your expected daily is the same solo-equivalent value (a rough estimate,
        // before the pool fee). Using the pool's total rate here would show the POOL's earnings, not yours. (M-05)
        double estHashrate = s_pool_mode ? state.pool_mining.hashrate_10s : mining.localHashrate;
        double est_hours_2 = EstimateHoursToBlock(estHashrate, mining.networkHashrate, mining.difficulty);
        double estDailyBlocks = (est_hours_2 > 0) ? (24.0 / est_hours_2) : 0.0;
        double blockReward = schema::UI().drawElement("business", "block-reward").size;
        double estDaily = estDailyBlocks * blockReward;
        bool estActive = isMiningActive && estDaily > 0;

        ImU32 greenCol2 = Success();

        // --- Combined Earnings + Details card ---
        float earningsRowH = ovFont->LegacySize + Layout::spacingXs() + sub1->LegacySize + pad * 1.5f;
        float detailsContentH = pad * 0.5f + capFont->LegacySize + pad * 0.5f;
        float barH_est = capFont->LegacySize + Layout::spacingMd() * 2.0f;
        float combinedCardH = earningsRowH + detailsContentH + barH_est + pad;
        combinedCardH = std::max(combinedCardH,
            schema::UI().drawElement("tabs.mining", "details-card-min-height").size + earningsRowH);

        ImVec2 cardMin = ImGui::GetCursorScreenPos();
        ImVec2 cardMax(cardMin.x + availWidth, cardMin.y + combinedCardH);
        DrawGlassPanel(dl, cardMin, cardMax, glassSpec);

        // === Earnings filter toggle button (top-right of card) ===
        {
            const bool soloMiningAvailable = app->supportsSoloMining();
            const char* filterLabels[] = { TR("mining_filter_all"), soloMiningAvailable ? TR("mining_solo") : TR("mining_pool"), TR("mining_pool") };
            const char* filterIcons[] = { ICON_MD_FUNCTIONS, soloMiningAvailable ? ICON_MD_MEMORY : ICON_MD_CLOUD, ICON_MD_CLOUD };
            const int filterCount = soloMiningAvailable ? 3 : 2;
            const char* curIcon = filterIcons[s_earnings_filter];
            const char* curLabel = filterLabels[s_earnings_filter];

            ImFont* icoFont = Type().iconSmall();
            float icoH = icoFont->LegacySize;
            ImVec2 icoSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, curIcon);
            ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, curLabel);
            float padH = Layout::spacingSm();
            float btnW = padH + icoSz.x + Layout::spacingXs() + lblSz.x + padH;
            float btnH = icoH + 8.0f * dp;
            float btnX = cardMax.x - pad - btnW;
            float btnY = cardMin.y + (earningsRowH - btnH) * 0.5f;

            ImVec2 bMin(btnX, btnY), bMax(btnX + btnW, btnY + btnH);
            bool hov = material::IsRectHovered(bMin, bMax);

            // Pill background
            ImU32 pillBg = s_earnings_filter != 0
                ? WithAlpha(Primary(), 60)
                : WithAlpha(OnSurface(), hov ? 25 : 12);
            dl->AddRectFilled(bMin, bMax, pillBg, btnH * 0.5f);

            // Icon
            ImU32 icoCol = s_earnings_filter != 0 ? Primary() : (hov ? OnSurface() : OnSurfaceDisabled());
            float cx = bMin.x + padH;
            float cy = bMin.y + (btnH - icoSz.y) * 0.5f;
            dl->AddText(icoFont, icoFont->LegacySize, ImVec2(cx, cy), icoCol, curIcon);

            // Label
            ImU32 lblCol = s_earnings_filter != 0 ? Primary() : (hov ? OnSurface() : OnSurfaceMedium());
            float lx = cx + icoSz.x + Layout::spacingXs();
            float ly = bMin.y + (btnH - lblSz.y) * 0.5f;
            dl->AddText(ovFont, ovFont->LegacySize, ImVec2(lx, ly), lblCol, curLabel);

            // Click target
            ImVec2 savedCur = ImGui::GetCursorScreenPos();
            ImGui::SetCursorScreenPos(bMin);
            ImGui::InvisibleButton("##EarningsFilter", ImVec2(btnW, btnH));
            if (ImGui::IsItemClicked()) {
                s_earnings_filter = (s_earnings_filter + 1) % filterCount;
            }
            if (ImGui::IsItemHovered()) {
                ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                const char* tips[] = {
                    TR("mining_filter_tip_all"),
                    soloMiningAvailable ? TR("mining_filter_tip_solo") : TR("mining_filter_tip_pool"),
                    TR("mining_filter_tip_pool")
                };
                material::Tooltip("%s", tips[s_earnings_filter]);
            }
            ImGui::SetCursorScreenPos(savedCur);
        }

        // === Earnings section (top of combined card) ===
        {
            const int numCols = 4;
            float colW = (availWidth - pad * 2) / (float)numCols;
            float ey = cardMin.y + pad * 0.5f;
            char valBuf[64], subBuf2[64];

            struct EarningsEntry {
                const char* label;
                const char* value;
                const char* sub;
                ImU32 col;
            };

            snprintf(valBuf, sizeof(valBuf), "+%.4f", minedToday);
            snprintf(subBuf2, sizeof(subBuf2), "(%d txn)", minedTodayCount);
            char todayVal[64], todaySub[64];
            strncpy(todayVal, valBuf, sizeof(todayVal));
            strncpy(todaySub, subBuf2, sizeof(todaySub));

            char yesterdayVal[64], yesterdaySub[64];
            snprintf(yesterdayVal, sizeof(yesterdayVal), "+%.4f", minedYesterday);
            snprintf(yesterdaySub, sizeof(yesterdaySub), "(%d txn)", minedYesterdayCount);

            char allVal[64], allSub[64];
            snprintf(allVal, sizeof(allVal), "+%.4f", minedAllTime);
            snprintf(allSub, sizeof(allSub), "(%d txn)", minedAllTimeCount);

            char estVal[64];
            if (estActive)
                snprintf(estVal, sizeof(estVal), "~%.4f", estDaily);
            else
                snprintf(estVal, sizeof(estVal), "%s", TR("grpc_na"));
            // Disclose in pool mode that Est. Daily is a rough solo-equivalent (before the pool fee), so the
            // number isn't silently mismatched to its plain "Est. Daily" label. (M-05)
            const char* estSub = (s_pool_mode && estActive) ? TR("mining_est_daily_pool_sub") : nullptr;

            EarningsEntry entries[] = {
                { TR("mining_today"),      todayVal,      todaySub,      greenCol2 },
                { TR("mining_yesterday"),  yesterdayVal,   yesterdaySub,   OnSurface() },
                { TR("mining_all_time"),   allVal,         allSub,         OnSurface() },
                { TR("mining_est_daily"), estVal,         estSub,         estActive ? greenCol2 : OnSurfaceDisabled() },
            };

            for (int ei = 0; ei < numCols; ei++) {
                float sx = cardMin.x + pad + ei * colW;
                float centerX = sx + colW * 0.5f;

                // Overline label (centered)
                ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, 10000, 0, entries[ei].label);
                dl->AddText(ovFont, ovFont->LegacySize,
                            ImVec2(centerX - lblSz.x * 0.5f, ey), OnSurfaceMedium(), entries[ei].label);

                // Value (centered)
                float valY = ey + ovFont->LegacySize + Layout::spacingXs();
                ImVec2 valSz = sub1->CalcTextSizeA(sub1->LegacySize, 10000, 0, entries[ei].value);

                if (entries[ei].sub) {
                    // Value + sub annotation side by side, centered together
                    ImVec2 subSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, entries[ei].sub);
                    float totalW = valSz.x + 4 * dp + subSz.x;
                    float startX = centerX - totalW * 0.5f;
                    dl->AddText(sub1, sub1->LegacySize, ImVec2(startX, valY), entries[ei].col, entries[ei].value);
                    dl->AddText(capFont, capFont->LegacySize,
                                ImVec2(startX + valSz.x + 4 * dp, valY + (sub1->LegacySize - capFont->LegacySize) * 0.5f),
                                OnSurfaceDisabled(), entries[ei].sub);
                } else {
                    dl->AddText(sub1, sub1->LegacySize,
                                ImVec2(centerX - valSz.x * 0.5f, valY), entries[ei].col, entries[ei].value);
                }
            }
        }

        // === Separator between earnings & details ===
        float earningsSepY = cardMin.y + earningsRowH;
        {
            float rnd = glassSpec.rounding;
            dl->AddLine(ImVec2(cardMin.x + rnd * 0.5f, earningsSepY),
                        ImVec2(cardMax.x - rnd * 0.5f, earningsSepY),
                        WithAlpha(OnSurface(), 15), 1.0f * dp);
        }

        // === Details section (below separator) ===
        {
            float cx = cardMin.x + pad;
            float cy = earningsSepY + pad * 0.5f;

            // Three equal columns: Difficulty | Block | Mining Address
            float colW = availWidth / 3.0f;
            float valOffX = availWidth * schema::UI().drawElement("tabs.mining", "stats-col1-value-x-ratio").size;

            float col1X = cx;
            float col2X = cx + colW;
            float col3X = cx + colW * 2.0f;

            // -- Difficulty --
            dl->AddText(capFont, capFont->LegacySize, ImVec2(col1X, cy), OnSurfaceMedium(), TR("difficulty"));
            if (mining.difficulty > 0) {
                snprintf(buf, sizeof(buf), "%.4f", mining.difficulty);
                dl->AddText(capFont, capFont->LegacySize, ImVec2(col1X + valOffX, cy), OnSurface(), buf);
                ImVec2 diffSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
                if (material::CopyableTextOverlay("##DiffCopy", ImVec2(col1X + valOffX, cy), diffSz,
                                                  capFont->LegacySize, buf, TR("mining_click_copy_difficulty"),
                                                  ImVec2(Layout::spacingMd(), 4 * dp))) {
                    Notifications::instance().info(TR("mining_difficulty_copied"));
                }
            }

            // -- Block --
            dl->AddText(capFont, capFont->LegacySize, ImVec2(col2X, cy), OnSurfaceMedium(), TR("block"));
            if (mining.blocks > 0) {
                snprintf(buf, sizeof(buf), "%d", mining.blocks);
                dl->AddText(capFont, capFont->LegacySize, ImVec2(col2X + valOffX, cy), OnSurface(), buf);
                ImVec2 blkSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, buf);
                if (material::CopyableTextOverlay("##BlockCopy", ImVec2(col2X + valOffX, cy), blkSz,
                                                  capFont->LegacySize, buf, TR("mining_click_copy_block"),
                                                  ImVec2(Layout::spacingMd(), 4 * dp))) {
                    Notifications::instance().info(TR("mining_block_copied"));
                }
            }

            // -- Mining Address --
            dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X, cy), OnSurfaceMedium(), TR("mining_mining_addr"));
            std::string mining_address = "";
            for (const auto& addr : state.addresses) {
                if (addr.type == "transparent" && !addr.address.empty()) {
                    mining_address = addr.address;
                    break;
                }
            }
            if (!mining_address.empty()) {
                float addrAvailW = colW - pad - valOffX;
                float charW = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, "M").x;
                if (charW <= 0.0f) charW = 8.0f;
                int maxChars = std::max(8, (int)(addrAvailW / charW));
                std::string truncAddr = mining_address;
                if ((int)truncAddr.length() > maxChars && maxChars > 5) {
                    int half = (maxChars - 3) / 2;
                    if (half > 0 && (size_t)half < truncAddr.length())
                        truncAddr = truncAddr.substr(0, half) + "..." + truncAddr.substr(truncAddr.length() - half);
                }
                dl->AddText(capFont, capFont->LegacySize, ImVec2(col3X + valOffX, cy), OnSurface(), truncAddr.c_str());

                ImVec2 addrTextSz = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, truncAddr.c_str());
                if (material::CopyableTextOverlay("##MiningAddrCopy", ImVec2(col3X + valOffX, cy), addrTextSz,
                                                  capFont->LegacySize, mining_address.c_str(),
                                                  TR("mining_click_copy_address"),
                                                  ImVec2(Layout::spacingMd(), 4 * dp))) {
                    Notifications::instance().info(TR("mining_address_copied"));
                }
            }
        }

        // ---- Memory bar — centered in remaining space below details ----
        {
            double totalRAM  = dragonx::util::Platform::getTotalSystemRAM_MB();
            double usedRAM   = dragonx::util::Platform::getUsedSystemRAM_MB();
            double selfRAM   = dragonx::util::Platform::getSelfMemoryUsageMB();
            double daemonRAM = app->getDaemonMemoryUsageMB();
            // Include xmrig memory when pool mining
            double xmrigRAM  = state.pool_mining.memory_used / (1024.0 * 1024.0); // bytes -> MB
            double appRAM    = selfRAM + daemonRAM + xmrigRAM; // App + daemon + xmrig combined

            // Fixed bar height (text + padding)
            float barH = capFont->LegacySize + Layout::spacingMd() * 2.0f;
            float barRnd  = barH * 0.5f; // fully rounded corners

            // Details content ends here
            float detailsEndY = earningsSepY + detailsContentH;

            // Subtle top separator at the boundary
            float rnd = glassSpec.rounding;
            float stripY = detailsEndY;
            dl->AddLine(ImVec2(cardMin.x + rnd * 0.5f, stripY),
                        ImVec2(cardMax.x - rnd * 0.5f, stripY),
                        WithAlpha(OnSurface(), 15), 1.0f * dp);
            float remainingH = cardMax.y - stripY;
            float barX = cardMin.x + pad;
            float barW = cardMax.x - pad - barX;
            float barY = stripY + (remainingH - barH) * 0.5f;
            float textY = barY + (barH - capFont->LegacySize) * 0.5f;
            float textPadX = Layout::spacingMd();

            // Background track
            dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barH),
                              WithAlpha(OnSurface(), 20), barRnd);

            float sysFrac = 0.0f, appFrac = 0.0f;
            float sysFillW = 0.0f, appFillW = 0.0f;

            // Helper: draw a fill bar that perfectly matches the track's rounded
            // left edge regardless of fill width.  We draw a full-width rounded
            // rect (same shape as the track) but clip it to just the fill portion.
            auto drawFillBar = [&](float fillW, ImU32 col) {
                if (fillW <= 1.0f) return;
                dl->PushClipRect(ImVec2(barX, barY), ImVec2(barX + fillW, barY + barH), true);
                dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barH),
                                  col, barRnd);
                dl->PopClipRect();
            };

            if (usedRAM > 0 && totalRAM > 0) {
                sysFrac = std::clamp((float)(usedRAM / totalRAM), 0.0f, 1.0f);
                sysFillW = barW * sysFrac;

                // System memory bar (subtle fill)
                ImU32 ramBarCol = schema::UI().resolveColor("var(--ram-bar-system)",
                    IsDarkTheme() ? IM_COL32(255, 255, 255, 46) : IM_COL32(0, 0, 0, 46));
                drawFillBar(sysFillW, ramBarCol);

                // App+daemon memory bar (saturated accent)
                if (appRAM > 0) {
                    appFrac = std::clamp((float)(appRAM / totalRAM), 0.0f, sysFrac);
                    appFillW = barW * appFrac;
                    ImU32 appBarCol = schema::UI().resolveColor("var(--ram-bar-app)",
                        ImGui::ColorConvertFloat4ToU32(ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]));
                    drawFillBar(appFillW, appBarCol);
                }
            }

            // --- Text overlaying the bar ---
            float accentEdge = barX + appFillW;
            float whiteEdge  = barX + sysFillW;

            // Determine text colors for bar segments
            // On dark themes: white text on empty, dark on filled bars (both system and app)
            // On light themes: dark text on empty/system, white on app accent bar
            ImU32 barTextOnEmpty = IsDarkTheme() ? IM_COL32(255, 255, 255, 230)
                                                 : IM_COL32(30, 30, 30, 230);
            ImU32 barTextOnFill  = IsDarkTheme() ? IM_COL32(30, 30, 30, 230)
                                                 : IM_COL32(255, 255, 255, 230);
            ImU32 barTextOnAccent = IsDarkTheme() ? IM_COL32(0, 0, 0, 210)
                                                  : IM_COL32(255, 255, 255, 230);

            // App usage on the left
            char appBuf[64] = {};
            if (appRAM > 0) {
                if (appRAM >= 1024.0)
                    snprintf(appBuf, sizeof(appBuf), "%.1f GB", appRAM / 1024.0);
                else
                    snprintf(appBuf, sizeof(appBuf), "%.0f MB", appRAM);

                float appTextX = barX + textPadX;
                float appTextW = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, appBuf).x;
                float appTextEnd = appTextX + appTextW;

                // Inside accent bar: white | inside white bar: dark | outside: white
                if (appTextEnd <= accentEdge) {
                    dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
                                barTextOnAccent, appBuf);
                } else if (appTextX >= whiteEdge) {
                    dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
                                barTextOnEmpty, appBuf);
                } else {
                    // Part in accent bar: white
                    if (accentEdge > appTextX) {
                        dl->PushClipRect(ImVec2(appTextX, barY), ImVec2(accentEdge, barY + barH));
                        dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
                                    barTextOnAccent, appBuf);
                        dl->PopClipRect();
                    }
                    // Part in white bar (past accent): dark
                    float dkS = std::max(appTextX, accentEdge);
                    float dkE = std::min(appTextEnd, whiteEdge);
                    if (dkE > dkS) {
                        dl->PushClipRect(ImVec2(dkS, barY), ImVec2(dkE, barY + barH));
                        dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
                                    barTextOnFill, appBuf);
                        dl->PopClipRect();
                    }
                    // Part outside white bar: white
                    if (appTextEnd > whiteEdge) {
                        dl->PushClipRect(ImVec2(whiteEdge, barY), ImVec2(appTextEnd + 1, barY + barH));
                        dl->AddText(capFont, capFont->LegacySize, ImVec2(appTextX, textY),
                                    barTextOnEmpty, appBuf);
                        dl->PopClipRect();
                    }
                }
            }

            // System usage on the right
            char sysBuf[64] = {};
            if (usedRAM > 0 && totalRAM > 0)
                snprintf(sysBuf, sizeof(sysBuf), "%.1f / %.0f GB", usedRAM / 1024.0, totalRAM / 1024.0);
            else if (totalRAM > 0)
                snprintf(sysBuf, sizeof(sysBuf), "-- / %.0f GB", totalRAM / 1024.0);
            else
                snprintf(sysBuf, sizeof(sysBuf), "%s", TR("grpc_na"));

            float sysTextW = capFont->CalcTextSizeA(capFont->LegacySize, 10000, 0, sysBuf).x;
            float sysTextX = barX + barW - textPadX - sysTextW;

            if (sysTextX >= whiteEdge) {
                dl->AddText(capFont, capFont->LegacySize, ImVec2(sysTextX, textY),
                            barTextOnEmpty, sysBuf);
            } else if (sysTextX + sysTextW <= whiteEdge) {
                dl->AddText(capFont, capFont->LegacySize, ImVec2(sysTextX, textY),
                            barTextOnFill, sysBuf);
            } else {
                dl->PushClipRect(ImVec2(sysTextX, barY), ImVec2(whiteEdge, barY + barH));
                dl->AddText(capFont, capFont->LegacySize, ImVec2(sysTextX, textY),
                            barTextOnFill, sysBuf);
                dl->PopClipRect();
                dl->PushClipRect(ImVec2(whiteEdge, barY), ImVec2(sysTextX + sysTextW + 1, barY + barH));
                dl->AddText(capFont, capFont->LegacySize, ImVec2(sysTextX, textY),
                            barTextOnEmpty, sysBuf);
                dl->PopClipRect();
            }

            // Invisible button over the bar for tooltip interaction
            ImGui::SetCursorScreenPos(ImVec2(barX, barY));
            ImGui::InvisibleButton("##rambar", ImVec2(barW, barH));
            if (ImGui::IsItemHovered()) {
                material::BeginTooltip();
                if (selfRAM >= 1024.0)
                    ImGui::Text(TR("ram_wallet_gb"), selfRAM / 1024.0);
                else
                    ImGui::Text(TR("ram_wallet_mb"), selfRAM);
                if (daemonRAM >= 1024.0)
                    ImGui::Text(TR("ram_daemon_gb"), daemonRAM / 1024.0, app->getDaemonMemDiag().c_str());
                else
                    ImGui::Text(TR("ram_daemon_mb"), daemonRAM, app->getDaemonMemDiag().c_str());
                ImGui::Separator();
                ImGui::Text(TR("ram_system_gb"), usedRAM / 1024.0, totalRAM / 1024.0);
                material::EndTooltip();
            }
        }

        ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
        ImGui::Dummy(ImVec2(availWidth, 0));
        ImGui::Dummy(ImVec2(0, gap));

        // ============================================================
        // RECENT BLOCKS — last 4 mined blocks (always shown in pool mode)
        // ============================================================
        if (!recentMined.empty() || s_pool_mode) {
            Type().textColored(TypeStyle::Overline, OnSurfaceMedium(),
                s_pool_mode ? TR("mining_recent_payouts") : TR("mining_recent_blocks"));
            ImGui::Dummy(ImVec2(0, Layout::spacingXs()));

            float rowH_blocks = std::max(schema::UI().drawElement("tabs.mining", "recent-row-min-height").size, schema::UI().drawElement("tabs.mining", "recent-row-height").size * vs);
            // Size to remaining space — proportional budget ensures fit
            float recentAvailH = ImGui::GetContentRegionAvail().y - sHdr - gapOver;
            float minRows = recentMined.empty() ? 2.0f : (float)recentMined.size();
            float contentH_blocks = rowH_blocks * minRows + pad * 2.5f;
            // Lower bound for the panel height. When the list is empty the centred empty-state
            // (top pad + icon + gap + caption + bottom pad) needs more vertical room than the bare
            // 30*dp floor, otherwise the caption is clipped once the thread-tile grid wraps to two
            // rows and recentAvailH shrinks (font metrics/Layout helpers are already DPI-scaled — do
            // not multiply them by dp; pad is a scaled param).
            float recentMinH = 30.0f * dp;
            if (recentMined.empty()) {
                recentMinH = std::max(recentMinH,
                    pad * 0.5f + Type().iconMed()->LegacySize + Layout::spacingXs()
                        + capFont->LegacySize + pad * 0.5f);
            }
            float recentH = std::clamp(contentH_blocks, recentMinH, std::max(recentMinH, recentAvailH));

            // Glass panel wrapping the list + scroll-edge mask state
            ImVec2 recentPanelMin = ImGui::GetCursorScreenPos();
            ImVec2 recentPanelMax(recentPanelMin.x + availWidth, recentPanelMin.y + recentH);
            GlassPanelSpec recentGlass;
            recentGlass.rounding = Layout::glassRounding();
            DrawGlassPanel(dl, recentPanelMin, recentPanelMax, recentGlass);

            float miningScrollY = 0.0f, miningScrollMaxY = 0.0f;
            int miningParentVtx = dl->VtxBuffer.Size;

            ImGui::BeginChild("##RecentBlocks", ImVec2(availWidth, recentH), false,
                ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar);
            ImDrawList* miningChildDL = ImGui::GetWindowDrawList();
            int miningChildVtx = miningChildDL->VtxBuffer.Size;

            miningScrollY    = ImGui::GetScrollY();
            miningScrollMaxY = ImGui::GetScrollMaxY();

            // Top padding inside glass card
            ImGui::Dummy(ImVec2(0, pad * 0.5f));

            if (recentMined.empty()) {
                // Empty state — card is visible but no rows yet
                float emptyY = ImGui::GetCursorScreenPos().y;
                float emptyX = ImGui::GetCursorScreenPos().x;
                float centerX = emptyX + availWidth * 0.5f;
                ImFont* icoFont = Type().iconMed();
                const char* emptyIcon = ICON_MD_HOURGLASS_EMPTY;
                ImVec2 iSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, emptyIcon);
                miningChildDL->AddText(icoFont, icoFont->LegacySize,
                    ImVec2(centerX - iSz.x * 0.5f, emptyY),
                    OnSurfaceDisabled(), emptyIcon);
                const char* emptyMsg = s_pool_mode
                    ? TR("mining_no_payouts_yet")
                    : TR("mining_no_blocks_yet");
                ImVec2 msgSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, emptyMsg);
                miningChildDL->AddText(capFont, capFont->LegacySize,
                    ImVec2(centerX - msgSz.x * 0.5f, emptyY + iSz.y + Layout::spacingXs()),
                    OnSurfaceDisabled(), emptyMsg);
            }

            for (size_t mi = 0; mi < recentMined.size(); mi++) {
                const auto& mtx = recentMined[mi];

                ImVec2 rMin = ImGui::GetCursorScreenPos();
                float rH = std::max(schema::UI().drawElement("tabs.mining", "recent-row-min-height").size, schema::UI().drawElement("tabs.mining", "recent-row-height").size * vs);
                ImVec2 rMax(rMin.x + availWidth, rMin.y + rH);

                // Subtle background on hover (inset from card edges)
                bool hovered = material::IsRectHovered(rMin, rMax);
                bool isClickable = !mtx.txid.empty();
                if (hovered) {
                    dl->AddRectFilled(ImVec2(rMin.x + pad * 0.5f, rMin.y),
                                      ImVec2(rMax.x - pad * 0.5f, rMax.y),
                                      IM_COL32(255, 255, 255, 8), 3.0f * dp);
                    if (isClickable) {
                        ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                    }
                }

                float rx = rMin.x + pad;
                float ry = rMin.y + Layout::spacingXs();

                // Mining icon — Material Design
                ImFont* iconFont = Type().iconSmall();
                const char* mIcon = ICON_MD_CONSTRUCTION;
                ImVec2 iSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, mIcon);
                float iconX = rx + 2 * dp, iconY = ry + 2 * dp;
                dl->AddText(iconFont, iconFont->LegacySize,
                            ImVec2(iconX, iconY),
                            WithAlpha(Warning(), 200), mIcon);

                // Time
                int64_t diff = now - mtx.timestamp;
                if (diff < 60)
                    snprintf(buf, sizeof(buf), TR("time_seconds_ago"), (long long)diff);
                else if (diff < 3600)
                    snprintf(buf, sizeof(buf), TR("time_minutes_ago"), (long long)(diff / 60));
                else if (diff < 86400)
                    snprintf(buf, sizeof(buf), TR("time_hours_ago"), (long long)(diff / 3600));
                else
                    snprintf(buf, sizeof(buf), TR("time_days_ago"), (long long)(diff / 86400));
                dl->AddText(capFont, capFont->LegacySize, ImVec2(rx + iSz.x + 8 * dp, ry), OnSurfaceDisabled(), buf);

                // Amount
                snprintf(buf, sizeof(buf), "+%.8f %s", mtx.amount, DRAGONX_TICKER);
                float amtX = rMin.x + pad + (availWidth - pad * 2) * 0.35f;
                dl->AddText(capFont, capFont->LegacySize, ImVec2(amtX, ry), greenCol2, buf);

                // Maturity badge — inset from right edge
                float badgeX = rMax.x - pad - Layout::spacingXl() * 3.5f;
                if (mtx.mature) {
                    dl->AddText(capFont, capFont->LegacySize, ImVec2(badgeX, ry),
                                WithAlpha(Success(), 180), TR("mature"));
                } else {
                    snprintf(buf, sizeof(buf), TR("conf_count"), mtx.confirmations);
                    dl->AddText(capFont, capFont->LegacySize, ImVec2(badgeX, ry),
                                WithAlpha(Warning(), 200), buf);
                }

                // Click to open in block explorer
                ImGui::SetCursorScreenPos(rMin);
                char blockBtnId[32];
                snprintf(blockBtnId, sizeof(blockBtnId), "##RecentBlock%zu", mi);
                ImGui::InvisibleButton(blockBtnId, ImVec2(availWidth, rH));
                if (ImGui::IsItemClicked() && !mtx.txid.empty()) {
                    std::string url = app->settings()->getTxExplorerUrl() + mtx.txid;
                    dragonx::util::Platform::openUrl(url);
                }
                if (ImGui::IsItemHovered() && !mtx.txid.empty()) {
                    material::Tooltip("%s", TR("mining_open_in_explorer"));
                }
            }

            ImGui::EndChild(); // ##RecentBlocks

            // CSS-style clipping mask
            {
                float fadeZone = std::min(capFont->LegacySize * 3.0f, recentH * 0.18f);
                ApplyScrollEdgeMask(dl, miningParentVtx, miningChildDL, miningChildVtx,
                                    recentPanelMin.y, recentPanelMax.y, fadeZone, miningScrollY, miningScrollMaxY);
            }

            ImGui::Dummy(ImVec2(0, gap));
        }
}

} // namespace ui
} // namespace dragonx
