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

#include "mining_tab.h"
#include "mining_benchmark.h"
#include "mining_tab_helpers.h"
#include "mining_pool_panel.h"
#include "mining_earnings.h"
#include "mining_stats.h"
#include "mining_controls.h"
#include "mining_mode_toggle.h"
#include "xmrig_download_dialog.h"
#include "../../util/xmrig_updater.h"
#include "../../app.h"
#include "../../util/i18n.h"
#include "../../config/version.h"
#include "../../data/wallet_state.h"
#include "../../config/settings.h"
#include "../../util/platform.h"
#include "../schema/ui_schema.h"
#include "../material/type.h"
#include "../material/draw_helpers.h"
#include "../material/colors.h"
#include "../effects/low_spec.h"
#include "../layout.h"
#include "../notifications.h"
#include "../../embedded/IconsMaterialDesign.h"
#include "imgui.h"
#include "imgui_internal.h"

#include <thread>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <vector>

namespace dragonx {
namespace ui {

using namespace material;

// Local UI state for thread grid
static int s_selected_threads = 0;
static bool s_threads_initialized = false;

// Drag-to-select state
static bool s_drag_active = false;
static int  s_drag_anchor_thread = 0;   // thread# where drag started


static ThreadBenchmark s_benchmark;

bool IsMiningBenchmarkActive() {
    return s_benchmark.active();
}

void CancelMiningBenchmark(App* app) {
    if (!s_benchmark.active()) return;
    const int restoreThreads = s_benchmark.prev_threads;
    s_benchmark.reset();
    // Restore the miner to its pre-benchmark thread count. A benchmark runs in pool mode, so restore
    // regardless of the instantaneous running state — the sweep may be mid inter-candidate stop, where an
    // isPoolMinerRunning() check would be transiently false and silently drop the restart. (L-04, L-11)
    if (app && restoreThreads > 0) {
        app->stopPoolMining();
        app->startPoolMining(restoreThreads);
    }
}

// Miner-update version check (one shot per session): fetches the latest DRG-XMRig release tag in
// the background so the "Update" button can show it. Network call to the project Gitea, started
// the first time the pool section is shown.
static util::XmrigUpdater s_xmrig_version_check;
static bool        s_xmrig_check_started = false;
static std::string s_xmrig_latest_tag;

// Pool mode state
static bool  s_pool_mode = false;
static char  s_pool_url[256] = "pool.dragonx.is:3433";
static char  s_pool_worker[256] = "x";
static bool  s_pool_settings_dirty = false;
static bool  s_pool_state_loaded = false;

static void RenderMiningTabContent(App* app);

void RenderMiningTab(App* app)
{
    // Scrollable child to contain all content within available space
    ImVec2 miningAvail = ImGui::GetContentRegionAvail();
    ImGui::BeginChild("##MiningScroll", miningAvail, false, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar);

    ImGuiErrorRecoveryState erState;
    ImGui::ErrorRecoveryStoreState(&erState);
    try {
        RenderMiningTabContent(app);
    } catch (const std::exception& e) {
        DEBUG_LOGF("[MiningTab] Exception: %s\n", e.what());
        ImGui::ErrorRecoveryTryToRecoverState(&erState);
    } catch (...) {
        DEBUG_LOGF("[MiningTab] Unknown exception\n");
        ImGui::ErrorRecoveryTryToRecoverState(&erState);
    }

    ImGui::EndChild(); // ##MiningScroll
}

static void RenderMiningTabContent(App* app)
{
    auto& S = schema::UI();
    ImVec2 miningAvail = ImGui::GetContentRegionAvail();
    auto sliderInput = S.input("tabs.mining", "thread-slider");
    auto startBtn    = S.button("tabs.mining", "start-button");
    auto lbl         = S.label("tabs.mining", "label-column");
    const auto& state = app->getWalletState();
    const auto& mining = state.mining;

    // Responsive: scale factors per frame
    float availWidth = ImGui::GetContentRegionAvail().x;
    float hs = Layout::hScale(availWidth);
    float vs = Layout::vScale(miningAvail.y);
    float pad = Layout::cardInnerPadding();
    float gap = Layout::cardGap();
    const float dp = Layout::dpiScale();
    auto tier = Layout::currentTier(availWidth, miningAvail.y);
    (void)tier;

    int max_threads = GetMaxMiningThreads();
    
    if (!s_threads_initialized) {
        int saved = app->settings()->getPoolThreads();
        if (mining.generate)
            s_selected_threads = ClampMiningThreads(mining.genproclimit, max_threads);
        else if (saved > 0)
            s_selected_threads = ClampMiningThreads(saved, max_threads);
        else
            s_selected_threads = 1;
        s_threads_initialized = true;
    }

    // Sync thread grid with actual count when idle thread scaling adjusts threads
    // Skip during benchmark — the benchmark controls thread counts directly
    if (app->settings()->getMineWhenIdle() && app->settings()->getIdleThreadScaling() && !s_drag_active && !IsMiningBenchmarkActive()) {
        if (s_pool_mode && state.pool_mining.xmrig_running) {
            // Use the requested thread count (available immediately) rather
            // than threads_active from the xmrig API which lags during restarts.
            int reqThreads = app->getXmrigRequestedThreads();
            if (reqThreads > 0)
                s_selected_threads = ClampMiningThreads(reqThreads, max_threads);
            else if (state.pool_mining.threads_active > 0)
                s_selected_threads = ClampMiningThreads(state.pool_mining.threads_active, max_threads);
        } else if (mining.generate && mining.genproclimit > 0) {
            s_selected_threads = ClampMiningThreads(mining.genproclimit, max_threads);
        }
    }

    ImDrawList* dl = ImGui::GetWindowDrawList();
    GlassPanelSpec glassSpec;
    glassSpec.rounding = Layout::glassRounding();
    ImFont* ovFont  = Type().overline();
    ImFont* capFont = Type().caption();
    ImFont* sub1    = Type().subtitle1();
    if (!ovFont || !capFont || !sub1) {
        return;
    }
    char buf[128];

    // Load pool state from settings on first frame
    if (!s_pool_state_loaded) {
        s_pool_mode = app->settings()->getPoolMode();
        strncpy(s_pool_url, app->settings()->getPoolUrl().c_str(), sizeof(s_pool_url) - 1);
        strncpy(s_pool_worker, app->settings()->getPoolWorker().c_str(), sizeof(s_pool_worker) - 1);
        s_pool_state_loaded = true;
    }
    // Kick off a one-shot miner-version check the first time the pool section is shown, then cache
    // the latest tag once it arrives (so the "Update" button can display it). Network call.
    if (s_pool_mode && !s_xmrig_check_started) {
        s_xmrig_version_check.startCheck(app->settings()->getXmrigVersion());
        s_xmrig_check_started = true;
    }
    if (s_xmrig_check_started && s_xmrig_latest_tag.empty()) {
        const auto _vp = s_xmrig_version_check.getProgress();
        if (!_vp.latest_tag.empty()) s_xmrig_latest_tag = _vp.latest_tag;
    }

    if (!app->supportsSoloMining() && !s_pool_mode) {
        s_pool_mode = true;
        app->settings()->setPoolMode(true);
        app->settings()->save();
    }

    // Default pool worker to user's first shielded (z) address once available.
    // For new wallets without a z-address, leave the field blank so the user
    // is prompted to generate one before mining.
    {
        static bool s_pool_worker_defaulted = false;
        std::string workerStr(s_pool_worker);
        if (shouldDefaultPoolWorker(workerStr, s_pool_worker_defaulted) && !state.addresses.empty()) {
            std::string defaultAddr = defaultPoolWorkerAddress(state.addresses);
            if (!defaultAddr.empty()) {
                strncpy(s_pool_worker, defaultAddr.c_str(), sizeof(s_pool_worker) - 1);
                s_pool_worker[sizeof(s_pool_worker) - 1] = '\0';
                s_pool_settings_dirty = true;
            } else {
                // No z-address yet — clear the placeholder "x" so field shows empty
                s_pool_worker[0] = '\0';
                s_pool_settings_dirty = true;
            }
            s_pool_worker_defaulted = true;
        }
    }

    // Persist pool settings when dirty and no field is active
    if (s_pool_settings_dirty && !ImGui::IsAnyItemActive()) {
        // Trim whitespace/newlines (a pasted URL or payout address often carries a trailing newline)
        // before persisting and feeding it to xmrig; write the trimmed value back so the field agrees.
        auto trimmed = [](const char* b) {
            std::string s(b);
            while (!s.empty() && (s.front()==' '||s.front()=='\t'||s.front()=='\n'||s.front()=='\r')) s.erase(s.begin());
            while (!s.empty() && (s.back()==' '||s.back()=='\t'||s.back()=='\n'||s.back()=='\r')) s.pop_back();
            return s;
        };
        std::string poolUrl = trimmed(s_pool_url), poolWorker = trimmed(s_pool_worker);
        snprintf(s_pool_url, sizeof(s_pool_url), "%s", poolUrl.c_str());
        snprintf(s_pool_worker, sizeof(s_pool_worker), "%s", poolWorker.c_str());
        app->settings()->setPoolUrl(poolUrl);
        app->settings()->setPoolWorker(poolWorker);
        app->settings()->save();
        s_pool_settings_dirty = false;

        // Auto-restart pool miner if it is currently running so the new
        // URL / worker address takes effect immediately.
        if (state.pool_mining.xmrig_running) {
            app->stopPoolMining();
            app->startPoolMining(s_selected_threads);
        }
    }

    // Determine active mining state for UI
    // Include pool mining running state even when user just switched to solo,
    // so the button shows STOP/STOPPING while xmrig shuts down.
    bool isMiningActive = IsPoolMiningActive(s_pool_mode,
                                             state.pool_mining.xmrig_running,
                                             mining.generate);

    // ================================================================
    // Thread Benchmark state machine — runs pool mining at each candidate
    // thread count to find the optimal setting for this CPU.
    // ================================================================
    if (s_benchmark.active()) {
        auto benchmarkUpdate = AdvanceThreadBenchmark(
            s_benchmark, ImGui::GetIO().DeltaTime, state.pool_mining.hashrate_10s);
        if (benchmarkUpdate.stopPoolMining) {
            app->stopPoolMining();
        }
        if (benchmarkUpdate.saveOptimalThreads) {
            s_selected_threads = benchmarkUpdate.optimalThreads;
            app->settings()->setPoolThreads(s_selected_threads);
            app->settings()->save();
        }
        if (benchmarkUpdate.startPoolMining) {
            app->startPoolMining(benchmarkUpdate.startThreads);
        }
        if (benchmarkUpdate.inconclusive) {
            Notifications::instance().warning(
                TR("grpc_benchmark_inconclusive"));
        }
    }

    // ================================================================
    // Proportional section budget — ensures all content fits without
    // scrolling at the minimum window size (1024×775).
    // ================================================================
    float sHdr = ovFont->LegacySize + Layout::spacingXs()
               + ImGui::GetStyle().ItemSpacing.y * 2.0f;
    float gapOver = gap + ImGui::GetStyle().ItemSpacing.y;
    // 3 sections with headers (CHART+STATS, DETAILS+EARNINGS, BLOCKS)
    float totalOverhead = 3.0f * (sHdr + gapOver) + 1.0f * gapOver;
    float cardBudget = std::max(schema::UI().drawElement("tabs.mining", "card-budget-min").size, miningAvail.y - totalOverhead);

    Layout::SectionBudget cb(cardBudget);
    float controlsBudgetH = cb.allocate(0.26f, 80.0f * dp);
    float chartBudgetH    = cb.allocate(0.22f, 60.0f * dp);
    (void)cb; // remaining budget used by combined earnings+details card

    // ================================================================
    // MODE TOGGLE — SOLO | POOL segmented control
    // ================================================================
    RenderMiningModeToggle(app, state, mining, dl, capFont, ovFont, dp, hs, gap, availWidth,
                           s_pool_mode, s_pool_url, s_pool_worker, s_pool_settings_dirty);

    // Payout safety (multi-wallet): in pool mode, warn if the worker looks like a DragonX address
    // that isn't in the CURRENT wallet — e.g. left over from a wallet you switched away from, so
    // rewards would land in the wrong wallet. Non-destructive: we only warn, never overwrite (it
    // could be a deliberate external payout).
    if (s_pool_mode) {
        std::string w(s_pool_worker);
        while (!w.empty() && (w.front()==' '||w.front()=='\t'||w.front()=='\n'||w.front()=='\r')) w.erase(w.begin());
        while (!w.empty() && (w.back()==' '||w.back()=='\t'||w.back()=='\n'||w.back()=='\r')) w.pop_back();
        const bool looksLikeAddr = w.size() > 10 && (w.rfind("zs", 0) == 0 || w[0] == 'R');
        bool inWallet = false;
        for (const auto& a : state.addresses) if (a.address == w) { inWallet = true; break; }
        if (looksLikeAddr && !inWallet && !state.addresses.empty()) {
            ImGui::Dummy(ImVec2(0, gap));
            Type().textColored(TypeStyle::Caption, Warning(), TR("mining_payout_foreign"));
        }
    }

    // ================================================================
    // CONTROLS — Glass card with CPU core grid (no heading)
    // ================================================================
    RenderMiningControls(app, state, mining, dl, capFont, sub1, ovFont, dp, hs, vs, gap, pad,
                         availWidth, glassSpec, controlsBudgetH, max_threads, isMiningActive,
                         s_pool_mode, s_pool_worker, s_xmrig_latest_tag, s_benchmark,
                         s_selected_threads, s_drag_active, s_drag_anchor_thread);

    // (The miner download/update control lives in the mining-control header row, next to the
    //  benchmark button — see the "Miner update" block above.)

    // ================================================================
    // HASHRATE + STATS — Combined glass card: stat values on top, chart below
    // (Or full-card log view when toggled in pool mode)
    // ================================================================
    RenderMiningStats(state, mining, dl, capFont, sub1, ovFont, dp, vs, gap, pad,
                      availWidth, glassSpec, chartBudgetH, s_pool_mode,
                      app, s_pool_url, s_pool_settings_dirty);

    // ================================================================
    // EARNINGS — Horizontal row card (Today | Yesterday | All Time | Est. Daily)
    // ================================================================
    RenderMiningEarnings(app, state, mining, dl, capFont, sub1, ovFont, dp, vs, gap, pad,
                         isMiningActive, s_pool_mode, availWidth, glassSpec, sHdr, gapOver);

    // ================================================================
    // POOL CONNECTION STATUS — inline indicator (pool mode, no log)
    // ================================================================
    if (s_pool_mode && state.pool_mining.log_lines.empty() && state.pool_mining.xmrig_running) {
        ImFont* iconFont = Type().iconSmall();
        const char* dotIcon = ICON_MD_CIRCLE;
        ImU32 dotCol = state.pool_mining.connected ? WithAlpha(Success(), 200) : WithAlpha(Error(), 200);
        const char* statusText = state.pool_mining.connected
            ? (state.pool_mining.pool_url.empty() ? TR("mining_connected") : state.pool_mining.pool_url.c_str())
            : TR("mining_connecting");

        ImVec2 pos = ImGui::GetCursorScreenPos();
        ImVec2 dotSz = iconFont->CalcTextSizeA(iconFont->LegacySize, 1000.0f, 0.0f, dotIcon);
        dl->AddText(iconFont, iconFont->LegacySize, pos, dotCol, dotIcon);
        dl->AddText(capFont, capFont->LegacySize,
                    ImVec2(pos.x + dotSz.x + 4 * dp, pos.y + (dotSz.y - capFont->LegacySize) * 0.5f),
                    OnSurfaceMedium(), statusText);
        ImGui::Dummy(ImVec2(availWidth, capFont->LegacySize + 4 * dp));
    }
}

} // namespace ui
} // namespace dragonx
