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

#include "mining_controls.h"
#include "mining_tab_helpers.h"
#include "mining_benchmark.h"
#include "xmrig_download_dialog.h"

#include "../../app.h"
#include "../../config/version.h"
#include "../../config/settings.h"
#include "../../util/i18n.h"
#include "../../util/platform.h"
#include "../../util/address_validation.h"   // isValidRecipientAddress — payout validation (M-01)
#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 <string>
#include <vector>
#include <cstdlib>   // std::atoi (xmrig version compare)

namespace dragonx {
namespace ui {

using namespace material;

void RenderMiningControls(App* app, const WalletState& state, const MiningInfo& mining,
                          ImDrawList* dl, ImFont* capFont, ImFont* sub1, ImFont* ovFont,
                          float dp, float hs, float vs, float gap, float pad, float availWidth,
                          const GlassPanelSpec& glassSpec, float controlsBudgetH,
                          int max_threads, bool isMiningActive, bool poolMode,
                          const char* poolWorker, const std::string& xmrigLatestTag,
                          ThreadBenchmark& benchmark, int& selectedThreads,
                          bool& dragActive, int& dragAnchorThread)
{
        // Aliases so the moved body keeps its original identifiers verbatim. Mutated state is a
        // reference to the real static; read-only state is a const copy/ref.
        const bool s_pool_mode = poolMode;
        const char* const s_pool_worker = poolWorker;
        const std::string& s_xmrig_latest_tag = xmrigLatestTag;
        ThreadBenchmark& s_benchmark = benchmark;
        int& s_selected_threads = selectedThreads;
        bool& s_drag_active = dragActive;
        int& s_drag_anchor_thread = dragAnchorThread;
        char buf[128];
        float miningBtnGap = gap;
        float miningBtnMaxW = availWidth * schema::UI().drawElement("tabs.mining", "btn-max-width-ratio").size;

        // Thread-count tiles at an adaptive step (multiples of 1/2/4/8, chosen by core count) plus 1 and the
        // max, so the tile row stays bounded (~<=24 tiles) instead of one tile per thread — a 192-thread
        // EPYC would otherwise overflow the card. Exact in-between counts are settable via the input box.
        int tileStep = 1;
        if      (max_threads > 96) tileStep = 8;
        else if (max_threads > 48) tileStep = 4;
        else if (max_threads > 24) tileStep = 2;
        std::vector<int> threadOptions;
        threadOptions.push_back(1);
        for (int v = tileStep; v < max_threads; v += tileStep)
            if (v > 1) threadOptions.push_back(v);
        if (max_threads > 1 && threadOptions.back() != max_threads) threadOptions.push_back(max_threads);
        const int nOpts = (int)threadOptions.size();

        // Custom drawlist hit-tests (the thread tiles + the Mine button) bypass ImGui's popup input capture,
        // so a click on an open dropdown (saved pools / payout addresses) would ALSO fire them. Gate on this
        // so clicking the dropdown's X (or a row) doesn't bleed through to the tiles/Mine button.
        const bool anyPopupOpen = ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel);

        // --- Compute thread grid layout based on controls card width ---
        // Estimate controlsW first to compute cols correctly
        // The Mine button is square (= card height, which scales with DPI), so the width we
        // reserve for it here must scale too — a RAW clamp under-reserves at >100% scaling, which
        // over-estimates the grid width and lets the thread cells overflow the card (e.g. at 150%).
        float estControlsW = availWidth - std::min(schema::UI().drawElement("tabs.mining", "button-max-width-clamp").size * dp, miningBtnMaxW) - miningBtnGap;
        float innerW  = estControlsW - pad * 2;
        float cellSz  = std::clamp(schema::UI().drawElement("tabs.mining", "cell-size").size * vs, schema::UI().drawElement("tabs.mining", "cell-min-size").size * dp, schema::UI().drawElement("tabs.mining", "cell-max-size").sizeOr(42.0f) * dp);
        float cellGap = std::max(schema::UI().drawElement("tabs.mining", "cell-gap-min").size, cellSz * schema::UI().drawElement("tabs.mining", "cell-gap-ratio").size);
        int   cols    = std::max(1, std::min(nOpts, (int)(innerW / (cellSz + cellGap))));
        int   rows    = (nOpts + cols - 1) / cols;
        float gridW   = cols * cellSz + (cols - 1) * cellGap;
        float gridH   = rows * cellSz + (rows - 1) * cellGap;

        // Card sections: header(label+info+RAM inline) | grid
        float headerH  = capFont->LegacySize + Layout::spacingXs();
        float secGap   = Layout::spacingLg();

        // Card height from actual content, capped by proportional budget
        float cardH    = pad + headerH + secGap + gridH + pad;
        cardH = std::clamp(cardH, schema::UI().drawElement("tabs.mining", "control-card-min-height").size, controlsBudgetH);

        // Mining button — sized to match card height (square)
        float miningBtnSz = cardH;
        if (miningBtnSz + miningBtnGap > miningBtnMaxW)
            miningBtnSz = miningBtnMaxW;
        float controlsW = availWidth - miningBtnSz - miningBtnGap;

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

        float curY = cardMin.y + pad;

        // --- Header row: "THREADS  4 / 16" + RAM est + active indicator ---
        {
            ImVec2 labelPos(cardMin.x + pad, curY);
            dl->AddText(ovFont, ovFont->LegacySize, labelPos, OnSurfaceMedium(), TR("mining_threads"));

            float labelW = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, TR("mining_threads")).x;
            snprintf(buf, sizeof(buf), "  %d / %d", s_selected_threads, max_threads);
            ImVec2 countPos(labelPos.x + labelW, curY);
            dl->AddText(sub1, sub1->LegacySize, countPos, OnSurface(), buf);

            // RAM estimate inline (after thread count)
            // Model matches DragonX RandomX: shared ~2080MB dataset + ~256MB cache (allocated once),
            // plus ~2MB scratchpad per mining thread VM.
            {
                float countW = sub1->CalcTextSizeA(sub1->LegacySize, FLT_MAX, 0, buf).x;
                double ram_estimate_mb = schema::UI().drawElement("business", "ram-base-mb").size
                                       + schema::UI().drawElement("business", "ram-dataset-mb").size
                                       + schema::UI().drawElement("business", "ram-cache-mb").size
                                       + s_selected_threads * schema::UI().drawElement("business", "ram-per-thread-mb").size;
                double ram_estimate_gb = ram_estimate_mb / 1024.0;
                snprintf(buf, sizeof(buf), "  RAM ~%.1fGB", ram_estimate_gb);
                dl->AddText(capFont, capFont->LegacySize,
                            ImVec2(countPos.x + countW, curY + (sub1->LegacySize - capFont->LegacySize) * 0.5f),
                            OnSurfaceDisabled(), buf);
            }

            // Centered, top-aligned thread-count stepper: [-]  centered editable number  [+]. Lets a
            // high-core CPU pick an EXACT count that isn't one of the tiles. The input commits on Enter
            // (not per keystroke) so it doesn't restart the miner while the user is still typing.
            {
                auto applyThreads = [&](int tc) {
                    tc = std::clamp(tc, 1, max_threads);
                    if (tc == s_selected_threads) return;
                    s_selected_threads = tc;
                    app->settings()->setPoolThreads(tc);
                    app->settings()->save();
                    if (mining.generate) app->startMining(tc);
                    if (s_pool_mode && state.pool_mining.xmrig_running) {
                        app->stopPoolMining();
                        app->startPoolMining(tc);
                    }
                };
                ImFont* stepFont  = Type().iconSmall();
                // Match the -/+ button height to the InputInt's actual frame height (GetFrameHeight ==
                // fontSize + 2*FramePadding.y, evaluated with the same font/style the input renders with,
                // since nothing pushes a font between here and the InputInt below). This keeps the buttons
                // the SAME height as the number box and top-aligned with it at sy — previously fieldH was
                // capFont+6px, shorter than the box, so they sat misaligned.
                const float fieldH = ImGui::GetFrameHeight();
                const float sideW  = fieldH;               // square -/+ buttons
                const float fieldW = 46.0f * dp;
                const float g      = 2.0f * dp;
                const float totalW = sideW + g + fieldW + g + sideW;
                float sx = cardMin.x + (controlsW - totalW) * 0.5f;
                const float sy = curY;
                ImVec2 savedCur = ImGui::GetCursorScreenPos();

                // -/+ buttons drawn in the same style as the thread tiles (rounded rect + border + centered
                // glyph, same fill/border/rounding). Hit-tested with a real InvisibleButton so they stay
                // popup-safe.
                const float stepRound = schema::UI().drawElement("tabs.mining", "cell-rounding").size;
                // atBound: the stepper can't move further (already at 1 for "-" or max for "+"). When
                // at the bound the control is a no-op, so render it disabled: dim glyph/border, no hand
                // cursor, no tooltip — and swallow the click so it reads as inert.
                auto rectStepBtn = [&](const char* id, const char* glyph, float x, const char* tip, bool atBound) -> bool {
                    ImGui::SetCursorScreenPos(ImVec2(x, sy));
                    ImGui::InvisibleButton(id, ImVec2(sideW, fieldH));
                    const bool hov = ImGui::IsItemHovered() && !atBound;
                    const bool clk = ImGui::IsItemClicked() && !atBound;
                    const ImVec2 mn(x, sy), mx(x + sideW, sy + fieldH);
                    dl->AddRectFilled(mn, mx, hov ? WithAlpha(OnSurface(), 25) : WithAlpha(OnSurface(), 8), stepRound);
                    dl->AddRect(mn, mx, WithAlpha(OnSurface(), atBound ? 15 : (hov ? 80 : 35)), stepRound);
                    const ImVec2 gsz = stepFont->CalcTextSizeA(stepFont->LegacySize, FLT_MAX, 0, glyph);
                    dl->AddText(stepFont, stepFont->LegacySize,
                                ImVec2(x + (sideW - gsz.x) * 0.5f, sy + (fieldH - gsz.y) * 0.5f),
                                WithAlpha(OnSurface(), atBound ? 30 : (hov ? 160 : 80)), glyph);
                    if (hov) {
                        ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                        if (tip && tip[0]) material::Tooltip("%s", tip);
                    }
                    return clk;
                };

                // "-" button (disabled at 1 thread)
                if (rectStepBtn("##ThreadMinus", ICON_MD_REMOVE, sx, TR("mining_threads_minus_tooltip"),
                                s_selected_threads <= 1))
                    applyThreads(s_selected_threads - 1);
                sx += sideW + g;

                // Centered editable number: symmetric FramePadding pushes the digits to the field centre.
                char tb[8]; snprintf(tb, sizeof(tb), "%d", s_selected_threads);
                float basePad   = ImGui::GetStyle().FramePadding.x;
                float txtW      = ImGui::CalcTextSize(tb).x;
                float centerPad = std::max(basePad, (fieldW - txtW) * 0.5f);
                ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(centerPad, ImGui::GetStyle().FramePadding.y));
                ImGui::SetCursorScreenPos(ImVec2(sx, sy));
                ImGui::SetNextItemWidth(fieldW);
                int tc = s_selected_threads;
                if (ImGui::InputInt("##ThreadCountInput", &tc, 0, 0, ImGuiInputTextFlags_EnterReturnsTrue))
                    applyThreads(tc);
                if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("mining_threads_input_tooltip"));
                ImGui::PopStyleVar();
                sx += fieldW + g;

                // "+" button (disabled at max_threads)
                if (rectStepBtn("##ThreadPlus", ICON_MD_ADD, sx, TR("mining_threads_plus_tooltip"),
                                s_selected_threads >= max_threads))
                    applyThreads(s_selected_threads + 1);

                ImGui::SetCursorScreenPos(savedCur);
            }

            // Idle mining toggle (top-right corner of card)
            float idleRightEdge = cardMax.x - pad;
            {
                bool idleOn = app->settings()->getMineWhenIdle();
                bool threadScaling = app->settings()->getIdleThreadScaling();
                ImFont* icoFont = Type().iconSmall();
                const char* idleIcon = ICON_MD_SCHEDULE;
                float icoH = icoFont->LegacySize;
                float btnSz = icoH + 8.0f * dp;
                float btnX = idleRightEdge - btnSz;
                float btnY = curY + (headerH - btnSz) * 0.5f;

                // Idle toggle: active pill + state-colored glyph (cursor saved/restored at block end).
                material::IconButtonStyle st;
                st.color = idleOn ? Primary() : OnSurfaceDisabled();
                st.restBg = idleOn ? WithAlpha(Primary(), 60) : 0;
                st.tooltip = idleOn ? TR("mining_idle_on_tooltip") : TR("mining_idle_off_tooltip");
                ImVec2 savedCur = ImGui::GetCursorScreenPos();
                ImGui::SetCursorScreenPos(ImVec2(btnX, btnY));
                if (material::IconButton("##IdleMining", idleIcon, icoFont, ImVec2(btnSz, btnSz), st)) {
                    app->settings()->setMineWhenIdle(!idleOn);
                    app->settings()->save();
                }

                idleRightEdge = btnX - 4.0f * dp;

                // Thread scaling mode toggle (to the left of idle icon, shown when idle is on)
                if (idleOn) {
                    const char* scaleIcon = threadScaling ? ICON_MD_TUNE : ICON_MD_POWER_SETTINGS_NEW;
                    float sBtnX = idleRightEdge - btnSz;
                    float sBtnY = btnY;
                    material::IconButtonStyle st;
                    st.color = threadScaling ? Primary() : OnSurfaceMedium();
                    st.restBg = threadScaling ? WithAlpha(Primary(), 40) : 0;
                    st.tooltip = threadScaling ? TR("mining_idle_scale_on_tooltip")
                                               : TR("mining_idle_scale_off_tooltip");
                    ImGui::SetCursorScreenPos(ImVec2(sBtnX, sBtnY));
                    if (material::IconButton("##IdleScaleMode", scaleIcon, icoFont, ImVec2(btnSz, btnSz), st)) {
                        app->settings()->setIdleThreadScaling(!threadScaling);
                        app->settings()->save();
                    }
                    idleRightEdge = sBtnX - 4.0f * dp;
                }

                // GPU-aware idle toggle (to the left, when idle is on)
                // When ON (default): GPU utilization >= 10% counts as "not idle"
                // When OFF: unrestricted mode, only keyboard/mouse input matters
                if (idleOn) {
                    bool gpuAware = app->settings()->getIdleGpuAware();
                    float gBtnX = idleRightEdge - btnSz;
                    float gBtnY = btnY;
                    material::IconButtonStyle st;
                    st.color = gpuAware ? Primary() : OnSurfaceDisabled();
                    st.restBg = gpuAware ? WithAlpha(Primary(), 40) : 0;
                    st.tooltip = gpuAware ? TR("mining_idle_gpu_on_tooltip")
                                          : TR("mining_idle_gpu_off_tooltip");
                    ImGui::SetCursorScreenPos(ImVec2(gBtnX, gBtnY));
                    if (material::IconButton("##IdleGpuAware", ICON_MD_MONITOR, icoFont, ImVec2(btnSz, btnSz), st)) {
                        app->settings()->setIdleGpuAware(!gpuAware);
                        app->settings()->save();
                    }
                    idleRightEdge = gBtnX - 4.0f * dp;
                }

                // Idle delay combo renderer (shared by the non-scaling and thread-scaling
                // branches below). Draws right-aligned at idleRightEdge and advances it.
                auto renderIdleDelayCombo = [&](const char* comboId) {
                    struct DelayOption { int seconds; const char* label; };
                    static const DelayOption delays[] = {
                        {30, "30s"}, {60, "1m"}, {120, "2m"}, {300, "5m"}, {600, "10m"}
                    };
                    int curDelay = app->settings()->getMineIdleDelay();
                    const char* previewLabel = "2m";
                    for (const auto& d : delays) {
                        if (d.seconds == curDelay) { previewLabel = d.label; break; }
                    }
                    float comboW = schema::UI().drawElement("components.settings-page", "idle-combo-width").sizeOr(64.0f) * dp;
                    float comboX = idleRightEdge - comboW;
                    float comboY = curY + (headerH - ImGui::GetFrameHeight()) * 0.5f;
                    ImGui::SetCursorScreenPos(ImVec2(comboX, comboY));
                    ImGui::SetNextItemWidth(comboW);
                    if (ImGui::BeginCombo(comboId, previewLabel, ImGuiComboFlags_NoArrowButton)) {
                        for (const auto& d : delays) {
                            bool selected = (d.seconds == curDelay);
                            if (ImGui::Selectable(d.label, selected)) {
                                app->settings()->setMineIdleDelay(d.seconds);
                                app->settings()->save();
                            }
                            if (selected) ImGui::SetItemDefaultFocus();
                        }
                        ImGui::EndCombo();
                    }
                    if (ImGui::IsItemHovered())
                        material::Tooltip("%s", TR("tt_idle_delay"));
                    idleRightEdge = comboX - 4.0f * dp;
                };

                // Idle delay combo (to the left, when idle is enabled and NOT in thread scaling mode)
                if (idleOn && !threadScaling) {
                    renderIdleDelayCombo("##IdleDelay");
                }

                // Thread scaling controls: idle delay + active threads / idle threads combos
                if (idleOn && threadScaling) {
                    int hwThreads = std::max(1, (int)std::thread::hardware_concurrency());

                    // Idle delay combo
                    renderIdleDelayCombo("##IdleDelayScale");

                    // Idle threads combo (threads when system is idle)
                    {
                        int curVal = app->settings()->getIdleThreadsIdle();
                        if (curVal <= 0) curVal = hwThreads;
                        char previewBuf[16];
                        snprintf(previewBuf, sizeof(previewBuf), "%d", curVal);
                        float comboW = schema::UI().drawElement("components.settings-page", "idle-combo-width").sizeOr(64.0f) * dp;
                        float comboX = idleRightEdge - comboW;
                        float comboY = curY + (headerH - ImGui::GetFrameHeight()) * 0.5f;
                        ImGui::SetCursorScreenPos(ImVec2(comboX, comboY));
                        ImGui::SetNextItemWidth(comboW);
                        if (ImGui::BeginCombo("##IdleThreadsIdle", previewBuf, ImGuiComboFlags_NoArrowButton)) {
                            for (int t = 1; t <= hwThreads; t++) {
                                char lbl[16];
                                snprintf(lbl, sizeof(lbl), "%d", t);
                                bool selected = (t == curVal);
                                if (ImGui::Selectable(lbl, selected)) {
                                    app->settings()->setIdleThreadsIdle(t);
                                    app->settings()->save();
                                }
                                if (selected) ImGui::SetItemDefaultFocus();
                            }
                            ImGui::EndCombo();
                        }
                        if (ImGui::IsItemHovered())
                            material::Tooltip("%s", TR("mining_idle_threads_idle_tooltip"));
                        idleRightEdge = comboX - 4.0f * dp;
                    }

                    // Separator arrow icon
                    {
                        const char* arrowIcon = ICON_MD_ARROW_BACK;
                        ImVec2 arrSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, arrowIcon);
                        float arrX = idleRightEdge - arrSz.x;
                        float arrY = curY + (headerH - arrSz.y) * 0.5f;
                        dl->AddText(icoFont, icoFont->LegacySize, ImVec2(arrX, arrY), OnSurfaceDisabled(), arrowIcon);
                        idleRightEdge = arrX - 4.0f * dp;
                    }

                    // Active threads combo (threads when user is active)
                    {
                        int curVal = app->settings()->getIdleThreadsActive();
                        if (curVal <= 0) curVal = std::max(1, hwThreads / 2);
                        char previewBuf[16];
                        snprintf(previewBuf, sizeof(previewBuf), "%d", curVal);
                        float comboW = schema::UI().drawElement("components.settings-page", "idle-combo-width").sizeOr(64.0f) * dp;
                        float comboX = idleRightEdge - comboW;
                        float comboY = curY + (headerH - ImGui::GetFrameHeight()) * 0.5f;
                        ImGui::SetCursorScreenPos(ImVec2(comboX, comboY));
                        ImGui::SetNextItemWidth(comboW);
                        if (ImGui::BeginCombo("##IdleThreadsActive", previewBuf, ImGuiComboFlags_NoArrowButton)) {
                            for (int t = 1; t <= hwThreads; t++) {
                                char lbl[16];
                                snprintf(lbl, sizeof(lbl), "%d", t);
                                bool selected = (t == curVal);
                                if (ImGui::Selectable(lbl, selected)) {
                                    app->settings()->setIdleThreadsActive(t);
                                    app->settings()->save();
                                }
                                if (selected) ImGui::SetItemDefaultFocus();
                            }
                            ImGui::EndCombo();
                        }
                        if (ImGui::IsItemHovered())
                            material::Tooltip("%s", TR("mining_idle_threads_active_tooltip"));
                        idleRightEdge = comboX - 4.0f * dp;
                    }
                }

                ImGui::SetCursorScreenPos(savedCur);
            }

            // --- Thread Benchmark button / progress (left of idle toggle) ---
            {
                ImVec2 benchSavedCur = ImGui::GetCursorScreenPos();
                bool benchRunning = s_benchmark.phase != ThreadBenchmark::Phase::Idle &&
                                    s_benchmark.phase != ThreadBenchmark::Phase::Done;
                bool benchDone    = s_benchmark.phase == ThreadBenchmark::Phase::Done;
                ImFont* icoFont = Type().iconSmall();

                if (benchRunning) {
                    // Show progress bar + current test info
                    float barW = std::min(180.0f * hs, idleRightEdge - (cardMin.x + pad) - 10.0f * dp);
                    float barH = 4.0f * dp;
                    float barX = idleRightEdge - barW;
                    float barY = curY + headerH - barH - 2.0f * dp;

                    // Progress bar track
                    dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW, barY + barH),
                                      WithAlpha(OnSurface(), 30), barH * 0.5f);
                    // Progress bar fill
                    float pct = s_benchmark.progress();
                    dl->AddRectFilled(ImVec2(barX, barY), ImVec2(barX + barW * pct, barY + barH),
                                      Primary(), barH * 0.5f);

                    // Status text above bar
                    int ct = s_benchmark.current_index < (int)s_benchmark.candidates.size()
                           ? s_benchmark.candidates[s_benchmark.current_index] : 0;
                    // Estimated remaining time (uses observed warmup for better accuracy)
                    int remaining_tests = (int)s_benchmark.candidates.size() - s_benchmark.current_index;
                    float elapsed_in_phase = s_benchmark.phase_timer;
                    float phase_total;
                    if (s_benchmark.phase == ThreadBenchmark::Phase::WarmingUp)
                        phase_total = s_benchmark.avgWarmupSecs();  // adaptive estimate
                    else if (s_benchmark.phase == ThreadBenchmark::Phase::CoolingDown)
                        phase_total = ThreadBenchmark::COOLDOWN_SECS;
                    else
                        phase_total = ThreadBenchmark::MEASURE_SECS;
                    float remaining_in_current = std::max(0.0f, phase_total - elapsed_in_phase);
                    // Remaining tests after current each need warmup + measure + cooldown
                    float est_secs = remaining_in_current
                                   + (remaining_tests - 1) * (s_benchmark.avgWarmupSecs() + ThreadBenchmark::MEASURE_SECS + ThreadBenchmark::COOLDOWN_SECS);
                    int est_min = (int)(est_secs / 60.0f);
                    int est_sec = (int)est_secs % 60;
                    const char* phase_label;
                    if (s_benchmark.phase == ThreadBenchmark::Phase::CoolingDown)
                        phase_label = TR("mining_benchmark_cooling");
                    else if (s_benchmark.phase == ThreadBenchmark::Phase::WarmingUp
                             && s_benchmark.phase_timer >= ThreadBenchmark::MIN_WARMUP_SECS)
                        phase_label = TR("mining_benchmark_stabilizing");
                    else
                        phase_label = TR("mining_benchmark_testing");
                    if (est_min > 0)
                        snprintf(buf, sizeof(buf), "%s %d/%d (%dt) ~%dm%ds",
                                 phase_label,
                                 s_benchmark.current_index + 1,
                                 (int)s_benchmark.candidates.size(), ct, est_min, est_sec);
                    else
                        snprintf(buf, sizeof(buf), "%s %d/%d (%dt) ~%ds",
                                 phase_label,
                                 s_benchmark.current_index + 1,
                                 (int)s_benchmark.candidates.size(), ct, est_sec);
                    ImVec2 txtSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, buf);
                    dl->AddText(capFont, capFont->LegacySize,
                                ImVec2(barX + (barW - txtSz.x) * 0.5f, barY - txtSz.y - 2.0f * dp),
                                OnSurfaceMedium(), buf);

                    // Cancel button (small X)
                    float cancelSz = icoFont->LegacySize + 4.0f * dp;
                    float cancelX  = barX - cancelSz - 4.0f * dp;
                    float cancelY  = curY + (headerH - cancelSz) * 0.5f;
                    ImGui::SetCursorScreenPos(ImVec2(cancelX, cancelY));
                    ImGui::InvisibleButton("##BenchCancel", ImVec2(cancelSz, cancelSz));
                    if (ImGui::IsItemClicked()) {
                        app->stopPoolMining();
                        if (s_benchmark.was_pool_running)
                            app->startPoolMining(s_benchmark.prev_threads);
                        s_benchmark.reset();
                    }
                    if (ImGui::IsItemHovered()) {
                        ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                        material::Tooltip("%s", TR("mining_benchmark_cancel"));
                    }
                    const char* cancelIcon = ICON_MD_CLOSE;
                    ImVec2 cIcoSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, cancelIcon);
                    dl->AddText(icoFont, icoFont->LegacySize,
                                ImVec2(cancelX + (cancelSz - cIcoSz.x) * 0.5f,
                                       cancelY + (cancelSz - cIcoSz.y) * 0.5f),
                                OnSurfaceMedium(), cancelIcon);

                    idleRightEdge = cancelX - 4.0f * dp;

                } else if (benchDone && s_benchmark.optimal_threads > 0) {
                    // Show result briefly, then reset on next click
                    snprintf(buf, sizeof(buf), "%s: %dt (%.1f H/s)",
                             TR("mining_benchmark_result"),
                             s_benchmark.optimal_threads, s_benchmark.optimal_hashrate);
                    ImVec2 txtSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, buf);
                    float txtX = idleRightEdge - txtSz.x;
                    dl->AddText(capFont, capFont->LegacySize,
                                ImVec2(txtX, curY + (headerH - txtSz.y) * 0.5f),
                                WithAlpha(Success(), 220), buf);

                    // Dismiss button
                    float dismissSz = icoFont->LegacySize + 4.0f * dp;
                    float dismissX  = txtX - dismissSz - 4.0f * dp;
                    float dismissY  = curY + (headerH - dismissSz) * 0.5f;
                    ImGui::SetCursorScreenPos(ImVec2(dismissX, dismissY));
                    ImGui::InvisibleButton("##BenchDismiss", ImVec2(dismissSz, dismissSz));
                    if (ImGui::IsItemClicked())
                        s_benchmark.reset();
                    if (ImGui::IsItemHovered()) {
                        ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                        material::Tooltip("%s", TR("mining_benchmark_dismiss"));
                    }
                    const char* okIcon = ICON_MD_CHECK;
                    ImVec2 oIcoSz = icoFont->CalcTextSizeA(icoFont->LegacySize, FLT_MAX, 0, okIcon);
                    dl->AddText(icoFont, icoFont->LegacySize,
                                ImVec2(dismissX + (dismissSz - oIcoSz.x) * 0.5f,
                                       dismissY + (dismissSz - oIcoSz.y) * 0.5f),
                                WithAlpha(Success(), 200), okIcon);

                    idleRightEdge = dismissX - 4.0f * dp;

                } else if (s_pool_mode) {
                    // Show benchmark button (only in pool mode)
                    float btnSz = icoFont->LegacySize + 8.0f * dp;
                    float btnX  = idleRightEdge - btnSz;
                    float btnY  = curY + (headerH - btnSz) * 0.5f;

                    material::IconButtonStyle st;
                    st.color = OnSurfaceMedium();
                    st.hoverBg = StateHover();
                    st.tooltip = TR("mining_benchmark_tooltip");
                    ImGui::SetCursorScreenPos(ImVec2(btnX, btnY));
                    if (material::IconButton("##BenchStart", ICON_MD_SPEED, icoFont, ImVec2(btnSz, btnSz), st)) {
                        // Require a wallet address for pool mining
                        std::string worker(s_pool_worker);
                        if (!worker.empty()) {
                            static bool s_benchConfirm = false;
                            if (!s_benchConfirm) {
                                // First click: build candidates so we can estimate the total duration, then
                                // require a confirming click (the benchmark interrupts mining and runs a while).
                                s_benchmark.reset();
                                s_benchmark.buildCandidates(max_threads);
                                s_benchConfirm = true;
                                char msg[128];
                                snprintf(msg, sizeof(msg),
                                    TR("grpc_benchmark_takes_secs"),
                                    (int)(s_benchmark.totalEstimatedSecs() + 0.5f));
                                Notifications::instance().warning(msg);
                            } else {
                                // Second click: start (candidates already built above).
                                s_benchConfirm = false;
                                s_benchmark.was_pool_running = state.pool_mining.xmrig_running;
                                s_benchmark.prev_threads = s_selected_threads;
                                s_benchmark.phase = ThreadBenchmark::Phase::Starting;
                                if (mining.generate)
                                    app->stopMining();
                            }
                        }
                    }

                    idleRightEdge = btnX - 4.0f * dp;
                }

                // --- Miner update: "Update <latest>" button + current version (left of benchmark) ---
                if (s_pool_mode && !benchRunning) {
                    // "Current" prefers the live version reported by the running miner (so a
                    // bundled miner with no persisted release tag still shows a version).
                    // Remembered for the session so it persists after the miner stops; falls
                    // back to the persisted release tag (set by the updater), else "none".
                    static std::string s_live_miner_ver;
                    if (!state.pool_mining.version.empty())
                        s_live_miner_ver = state.pool_mining.version;
                    // Live running version → detected installed version (before mining) →
                    // persisted release tag (updater) → "none".
                    std::string curVer = s_live_miner_ver;
                    if (curVer.empty()) curVer = app->poolMiningInstalledVersion();
                    if (curVer.empty()) curVer = app->settings()->getXmrigVersion();
                    // Version state: subtle GREEN + "xmrig releases" when up to date, subtle ORANGE +
                    // "Update <latest>" when an update is available, neutral when either version is unknown.
                    auto verNum = [](std::string s) -> std::vector<int> {
                        if (!s.empty() && (s[0] == 'v' || s[0] == 'V')) s.erase(0, 1);
                        s = s.substr(0, s.find('-'));   // drop any -build suffix
                        std::vector<int> v; size_t p = 0;
                        while (p <= s.size()) {
                            size_t q = s.find('.', p);
                            v.push_back(std::atoi(s.substr(p, q == std::string::npos ? std::string::npos : q - p).c_str()));
                            if (q == std::string::npos) break;
                            p = q + 1;
                        }
                        return v;
                    };
                    const bool verKnown = !curVer.empty() && !s_xmrig_latest_tag.empty();
                    bool upToDate = false;
                    if (verKnown) {
                        auto a = verNum(curVer), b = verNum(s_xmrig_latest_tag);
                        int cmp = 0;
                        for (size_t i = 0; i < std::max(a.size(), b.size()) && cmp == 0; ++i) {
                            int x = i < a.size() ? a[i] : 0, y = i < b.size() ? b[i] : 0;
                            if (x != y) cmp = x < y ? -1 : 1;
                        }
                        upToDate = (cmp >= 0);   // installed >= latest
                    }
                    const bool outdated = verKnown && !upToDate;
                    const ImU32 subtleGreen  = IM_COL32(120, 190, 130, 255);
                    const ImU32 subtleOrange = IM_COL32(214, 158, 74, 255);

                    char xbtn[64];
                    if (upToDate)
                        snprintf(xbtn, sizeof(xbtn), "%s", TR("xmrig_releases"));
                    else if (!s_xmrig_latest_tag.empty())
                        snprintf(xbtn, sizeof(xbtn), "%s %s", TR("xmrig_update_short"), s_xmrig_latest_tag.c_str());
                    else
                        snprintf(xbtn, sizeof(xbtn), "%s", TR("xmrig_update_button"));

                    const bool minerBusy = state.pool_mining.xmrig_running;
                    ImVec2 xlblSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, xbtn);
                    float xpadX = 8.0f * dp;
                    float xbtnW = xlblSz.x + xpadX * 2.0f;
                    float xbtnH = capFont->LegacySize + 6.0f * dp;
                    float xbtnX = idleRightEdge - xbtnW;
                    float xbtnY = curY + (headerH - xbtnH) * 0.5f;

                    ImGui::SetCursorScreenPos(ImVec2(xbtnX, xbtnY));
                    ImGui::InvisibleButton("##XmrigUpdateBtn", ImVec2(xbtnW, xbtnH));
                    bool xhov = ImGui::IsItemHovered();
                    bool xclk = ImGui::IsItemClicked();
                    ImU32 xpill = minerBusy ? WithAlpha(OnSurface(), 12)
                                            : (xhov ? StateHover() : WithAlpha(OnSurface(), 28));
                    dl->AddRectFilled(ImVec2(xbtnX, xbtnY), ImVec2(xbtnX + xbtnW, xbtnY + xbtnH),
                                      xpill, xbtnH * 0.3f);
                    dl->AddText(capFont, capFont->LegacySize,
                                ImVec2(xbtnX + xpadX, xbtnY + (xbtnH - xlblSz.y) * 0.5f),
                                minerBusy ? OnSurfaceDisabled()
                                          : (upToDate ? subtleGreen : outdated ? subtleOrange : OnSurfaceMedium()),
                                xbtn);
                    if (xhov) {
                        ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                        material::Tooltip("%s", minerBusy ? TR("xmrig_stop_mining_first")
                                                          : TR("xmrig_update_title"));
                    }
                    if (xclk && !minerBusy) XmrigDownloadDialog::show(app);

                    // Current installed version, as text to the LEFT of the button.
                    char xcur[64];
                    snprintf(xcur, sizeof(xcur), "%s %s", TR("xmrig_current"),
                             curVer.empty() ? TR("xmrig_none") : curVer.c_str());
                    ImVec2 xcurSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, xcur);
                    float xcurX = xbtnX - 6.0f * dp - xcurSz.x;
                    dl->AddText(capFont, capFont->LegacySize,
                                ImVec2(xcurX, curY + (headerH - xcurSz.y) * 0.5f),
                                upToDate ? subtleGreen : outdated ? subtleOrange : OnSurfaceDisabled(), xcur);
                    idleRightEdge = xcurX - 8.0f * dp;
                }

                ImGui::SetCursorScreenPos(benchSavedCur);
            }

            // Active mining indicator (left of idle toggle)
            if (mining.generate) {
                float pulse = effects::isLowSpecMode()
                    ? schema::UI().drawElement("animations", "pulse-base-normal").size
                    : schema::UI().drawElement("animations", "pulse-base-normal").size + schema::UI().drawElement("animations", "pulse-amp-normal").size * (float)std::sin((double)ImGui::GetTime() * schema::UI().drawElement("animations", "pulse-speed-normal").size);
                ImU32 pulseCol = WithAlpha(Success(), (int)(255 * pulse));
                float dotR = schema::UI().drawElement("tabs.mining", "active-dot-radius").size + 2.0f * hs;
                dl->AddCircleFilled(ImVec2(idleRightEdge - dotR, curY + dotR + 1 * dp), dotR, pulseCol);
                dl->AddText(capFont, capFont->LegacySize,
                            ImVec2(idleRightEdge - dotR - dotR - 60 * hs, curY),
                            WithAlpha(Success(), 200), TR("mining_active"));
            }
            curY += headerH + secGap;
        }

        // --- Thread Grid (drag-to-select) ---
        {
            // Center the grid horizontally within the card
            float gridX = cardMin.x + pad + (innerW - gridW) * 0.5f;
            float gridY = curY;

            bool threads_changed = false;

            // Track which thread the mouse is currently over (-1 = none)
            int hovered_thread = -1;

            // First pass: hit-test all cells to find the hovered thread-count option (its value, not index)
            for (int i = 0; i < nOpts; i++) {
                int row = i / cols;
                int col = i % cols;
                float cx = gridX + col * (cellSz + cellGap);
                float cy = gridY + row * (cellSz + cellGap);
                if (material::IsRectHovered(ImVec2(cx, cy), ImVec2(cx + cellSz, cy + cellSz))) {
                    hovered_thread = threadOptions[i];
                    break;
                }
            }

            // Show pointer cursor when hovering the thread grid
            bool benchActive = s_benchmark.phase != ThreadBenchmark::Phase::Idle &&
                               s_benchmark.phase != ThreadBenchmark::Phase::Done;
            if (hovered_thread > 0 && !benchActive)
                ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);

            // Drag-to-select logic (disabled during benchmark; ignored while a dropdown popup is open)
            if (!benchActive && !anyPopupOpen && ImGui::IsMouseClicked(0) && hovered_thread > 0) {
                // Begin drag
                s_drag_active = true;
                s_drag_anchor_thread = hovered_thread;
                s_selected_threads = hovered_thread;
                threads_changed = true;
            }
            if (s_drag_active) {
                if (ImGui::IsMouseDown(0)) {
                    if (hovered_thread > 0 && hovered_thread != s_selected_threads) {
                        // Drag selects up to the hovered thread in either direction
                        s_selected_threads = hovered_thread;
                        threads_changed = true;
                    }
                } else {
                    // Mouse released — end drag
                    s_drag_active = false;
                }
            }

            // Render cells (one per discrete thread-count option)
            for (int i = 0; i < nOpts; i++) {
                int row = i / cols;
                int col = i % cols;
                float cx = gridX + col * (cellSz + cellGap);
                float cy = gridY + row * (cellSz + cellGap);
                ImVec2 cMin(cx, cy);
                ImVec2 cMax(cx + cellSz, cy + cellSz);

                int threadNum = threadOptions[i];
                bool active   = threadNum <= s_selected_threads;   // fill-up-to-selected heat metaphor
                bool hovered  = (threadNum == hovered_thread);

                // Determine visual state
                float rounding = schema::UI().drawElement("tabs.mining", "cell-rounding").size;
                if (active && mining.generate) {
                    // Mining + active: animated heat glow using theme colors
                    float t       = (float)ImGui::GetTime();
                    float phase   = t * schema::UI().drawElement("animations", "heat-glow-speed").size + i * schema::UI().drawElement("animations", "heat-glow-phase-offset").size;
                    float glow    = effects::isLowSpecMode() ? 0.5f : 0.5f + 0.5f * (float)std::sin(phase);

                    // Base color from theme (--mining-heat-glow) falling back to Primary
                    ImU32 heatBase = schema::UI().resolveColor("var(--mining-heat-glow)", Primary());
                    int baseR = (heatBase >> 0)  & 0xFF;
                    int baseG = (heatBase >> 8)  & 0xFF;
                    int baseB = (heatBase >> 16) & 0xFF;
                    // Brighten toward white as glow increases
                    int   r   = std::min(255, (int)(baseR + (255 - baseR) * 0.3f * glow));
                    int   g   = std::min(255, (int)(baseG + (255 - baseG) * 0.3f * glow));
                    int   b   = std::min(255, (int)(baseB + (255 - baseB) * 0.3f * glow));
                    int   a   = (int)(180 + 60 * glow);
                    ImU32 fillCol = IM_COL32(r, g, b, a);
                    dl->AddRectFilled(cMin, cMax, fillCol, rounding);
                    // Bright border
                    dl->AddRect(cMin, cMax, WithAlpha(Primary(), (int)(160 + 60 * glow)), rounding, 0, schema::UI().drawElement("tabs.mining", "active-cell-border-thickness").size);
                } else if (active) {
                    // Active but not mining: solid primary fill
                    ImU32 fillCol = material::WithAlpha(Primary(), hovered ? 220 : 180);
                    dl->AddRectFilled(cMin, cMax, fillCol, rounding);
                    dl->AddRect(cMin, cMax, material::WithAlpha(Primary(), 255), rounding, 0, schema::UI().drawElement("tabs.mining", "cell-border-thickness").size);
                } else {
                    // Inactive: dim outline
                    ImU32 fillCol = hovered
                        ? WithAlpha(OnSurface(), 25)
                        : WithAlpha(OnSurface(), 8);
                    dl->AddRectFilled(cMin, cMax, fillCol, rounding);
                    dl->AddRect(cMin, cMax, WithAlpha(OnSurface(), hovered ? 80 : 35), rounding);
                }

                // Thread number label (centered)
                snprintf(buf, sizeof(buf), "%d", threadNum);
                ImVec2 txtSz = capFont->CalcTextSizeA(capFont->LegacySize, FLT_MAX, 0, buf);
                ImVec2 txtPos(cx + (cellSz - txtSz.x) * 0.5f, cy + (cellSz - txtSz.y) * 0.5f);
                ImU32  txtCol = active ? schema::UI().resolveColor("var(--on-primary)", IM_COL32(255, 255, 255, 230)) : WithAlpha(OnSurface(), 80);
                dl->AddText(capFont, capFont->LegacySize, txtPos, txtCol, buf);
            }

            if (threads_changed) {
                app->settings()->setPoolThreads(s_selected_threads);
                app->settings()->save();
                if (mining.generate) {
                    app->startMining(s_selected_threads);
                }
                if (s_pool_mode && state.pool_mining.xmrig_running) {
                    app->stopPoolMining();
                    app->startPoolMining(s_selected_threads);
                }
            }

            curY += gridH + secGap;
        }

        // ============================================================
        // Large square mining button — right of the controls card
        // ============================================================
        {
            float btnX = cardMin.x + controlsW + miningBtnGap;
            float btnY = cardMin.y;
            ImVec2 bMin(btnX, btnY);
            ImVec2 bMax(btnX + miningBtnSz, btnY + cardH);

            bool btnHovered = !anyPopupOpen && material::IsRectHovered(bMin, bMax);   // don't bleed through an open dropdown
            bool btnClicked = btnHovered && ImGui::IsMouseClicked(0);
            bool isSyncing  = state.sync.syncing;
            bool poolBlockedBySolo = s_pool_mode && mining.generate && !state.pool_mining.xmrig_running;
            bool isToggling = app->isMiningToggleInProgress();
            // Pool mining connects to an external pool via xmrig — it does not
            // need the local blockchain synced or even the daemon connected.
            // If pool mining is still shutting down after switching to solo,
            // keep the button enabled so user can stop it.
            bool poolStillRunning = !s_pool_mode && state.pool_mining.xmrig_running;
            // Can't start pool mining without a payout address (blank for a new wallet with no z-address);
            // only blocks starting — stopping a running miner stays enabled.
            // Block start when the payout address is empty OR not a valid DragonX address — mining to a
            // malformed / wrong-chain address silently loses the rewards. (M-01)
            const std::string poolPayoutStr(s_pool_worker);
            bool poolNeedsPayout = s_pool_mode && !state.pool_mining.xmrig_running &&
                                   (poolPayoutStr.empty() ||
                                    (poolPayoutStr != "x" && !util::isValidRecipientAddress(poolPayoutStr)));
            bool disabled = s_pool_mode
                ? (isToggling || poolBlockedBySolo || poolNeedsPayout)
                : (poolStillRunning ? false : (!app->isConnected() || isToggling || isSyncing));

            // Glass panel background with state-dependent tint
            GlassPanelSpec btnGlass;
            btnGlass.rounding = Layout::glassRounding();
            if (isToggling) {
                // Toggling: subtle pulsing glow to indicate activity
                float pulse = effects::isLowSpecMode()
                    ? 0.5f
                    : 0.5f + 0.5f * (float)std::sin((double)ImGui::GetTime() * 4.0);
                int glowA = (int)(15 + 25 * pulse);
                btnGlass.fillAlpha = glowA;
            } else if (isMiningActive) {
                // Active mining: warm glow
                float pulse = effects::isLowSpecMode()
                    ? schema::UI().drawElement("animations", "pulse-base-glow").size
                    : schema::UI().drawElement("animations", "pulse-base-glow").size + schema::UI().drawElement("animations", "pulse-amp-glow").size * (float)std::sin((double)ImGui::GetTime() * schema::UI().drawElement("animations", "pulse-speed-slow").size);
                int glowA = (int)(20 + 30 * pulse);
                btnGlass.fillAlpha = glowA;
            } else {
                btnGlass.fillAlpha = btnHovered ? 30 : 18;
            }
            DrawGlassPanel(dl, bMin, bMax, btnGlass);

            // Hover highlight
            if (btnHovered && !disabled) {
                dl->AddRectFilled(bMin, bMax,
                    isMiningActive ? WithAlpha(Error(), 25) : WithAlpha(Success(), 20),
                    btnGlass.rounding);
            }

            // Draw mining icon centered in button — Material Design ICON_MD_CONSTRUCTION
            {
                float btnW = bMax.x - bMin.x;
                float btnH = bMax.y - bMin.y;
                float cx = bMin.x + btnW * 0.5f;
                float cy = bMin.y + btnH * schema::UI().drawElement("tabs.mining", "button-icon-y-ratio").size;  // shift up to leave room for label

                if (isToggling) {
                    // Draw a spinning arc spinner to indicate progress
                    float spinnerR = std::min(btnW, btnH) * 0.18f;
                    float thickness = std::max(2.5f, spinnerR * 0.15f);
                    float time = (float)ImGui::GetTime();

                    // Track circle (faint)
                    ImU32 trackCol = WithAlpha(Primary(), 40);
                    dl->AddCircle(ImVec2(cx, cy), spinnerR, trackCol, 0, thickness);

                    // Animated arc
                    float rotation = fmodf(time * 2.0f * IM_PI / 1.4f, IM_PI * 2.0f);
                    float cycleTime = fmodf(time, 1.333f);
                    float arcLength = (cycleTime < 0.666f)
                        ? (cycleTime / 0.666f) * 0.75f + 0.1f
                        : ((1.333f - cycleTime) / 0.666f) * 0.75f + 0.1f;

                    float startAngle = rotation - IM_PI * 0.5f;
                    float endAngle = startAngle + IM_PI * 2.0f * arcLength;
                    int segments = (int)(32 * arcLength) + 1;
                    float angleStep = (endAngle - startAngle) / segments;
                    ImU32 arcCol = Primary();

                    for (int i = 0; i < segments; i++) {
                        float a1 = startAngle + angleStep * i;
                        float a2 = startAngle + angleStep * (i + 1);
                        ImVec2 p1(cx + cosf(a1) * spinnerR, cy + sinf(a1) * spinnerR);
                        ImVec2 p2(cx + cosf(a2) * spinnerR, cy + sinf(a2) * spinnerR);
                        dl->AddLine(p1, p2, arcCol, thickness);
                    }
                } else {
                    ImU32 iconCol;
                    if (disabled) {
                        iconCol = OnSurfaceDisabled();
                    } else if (isMiningActive) {
                        float pulse = effects::isLowSpecMode()
                            ? schema::UI().drawElement("animations", "pulse-base-normal").size
                            : schema::UI().drawElement("animations", "pulse-base-normal").size + schema::UI().drawElement("animations", "pulse-amp-normal").size * (float)std::sin((double)ImGui::GetTime() * schema::UI().drawElement("animations", "pulse-speed-normal").size);
                        iconCol = WithAlpha(Error(), (int)(200 + 55 * pulse));
                    } else {
                        iconCol = btnHovered ? WithAlpha(Success(), 255) : OnSurfaceMedium();
                    }

                    // Use XL icon for the large mining button
                    ImFont* iconFont = Type().iconXL();
                    const char* mineIcon = isMiningActive ? ICON_MD_CLOSE : ICON_MD_CONSTRUCTION;
                    ImVec2 iconSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, mineIcon);
                    dl->AddText(iconFont, iconFont->LegacySize,
                                ImVec2(cx - iconSz.x * 0.5f, cy - iconSz.y * 0.5f), iconCol, mineIcon);
                }
            }

            // Label below icon
            {
                float btnW = bMax.x - bMin.x;
                float btnH = bMax.y - bMin.y;
                const char* label;
                ImU32 lblCol;
                if (isToggling) {
                    label = isMiningActive ? TR("mining_stopping") : TR("mining_starting");
                    // Animated dots effect via alpha pulse
                    float pulse = effects::isLowSpecMode()
                        ? 0.7f
                        : 0.5f + 0.5f * (float)std::sin((double)ImGui::GetTime() * 3.0);
                    lblCol = WithAlpha(Primary(), (int)(120 + 135 * pulse));
                } else if (isMiningActive) {
                    label = TR("mining_stop");
                    lblCol = WithAlpha(Error(), 220);
                } else if (disabled) {
                    label = TR("mining_mine");
                    lblCol = WithAlpha(OnSurface(), 50);
                } else {
                    label = TR("mining_mine");
                    lblCol = WithAlpha(OnSurface(), 160);
                }
                ImVec2 lblSz = ovFont->CalcTextSizeA(ovFont->LegacySize, FLT_MAX, 0, label);
                float lblX = bMin.x + (btnW - lblSz.x) * 0.5f;
                float lblY = bMin.y + btnH * schema::UI().drawElement("tabs.mining", "button-label-y-ratio").size;
                dl->AddText(ovFont, ovFont->LegacySize, ImVec2(lblX, lblY), lblCol, label);
            }

            // Tooltip + pointer cursor
            if (btnHovered) {
                if (!disabled)
                    ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                if (isToggling)
                    material::Tooltip("%s", isMiningActive ? TR("mining_stopping_tooltip") : TR("mining_starting_tooltip"));
                else if (isSyncing && !s_pool_mode)
                    material::Tooltip(TR("mining_syncing_tooltip"), state.sync.verification_progress * 100.0);
                else if (poolBlockedBySolo)
                    material::Tooltip("%s", TR("mining_stop_solo_for_pool"));
                else if (poolNeedsPayout)
                    material::Tooltip("%s", TR("mining_pool_needs_payout_tooltip"));
                else
                    material::Tooltip("%s", isMiningActive ? TR("stop_mining") : TR("start_mining"));
            }

            // Click action — pool or solo
            if (btnClicked && !disabled) {
                if (s_pool_mode) {
                    if (state.pool_mining.xmrig_running)
                        app->stopPoolMining();
                    else
                        app->startPoolMining(s_selected_threads);
                } else {
                    // If pool mining is still running (user just switched from pool to solo),
                    // stop pool mining first
                    if (state.pool_mining.xmrig_running)
                        app->stopPoolMining();
                    else if (mining.generate)
                        app->stopMining();
                    else
                        app->startMining(s_selected_threads);
                }
            }
        }

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

} // namespace ui
} // namespace dragonx
