#pragma once

#include <string>

// Persistent node-connectivity banner shown at the top of the content column when the wallet
// cannot reach its node. Distinct from the transient toast notifications: it stays visible for
// as long as the fault persists, so an offline wallet is never silently mistaken for a working
// one. The decision (whether to show, how severe, which action) is a pure function of a state
// snapshot so it can be unit-tested; App::renderNodeStatusBanner() feeds it the live state and
// draws the strip. See src/app.cpp.
namespace dragonx::ui {

// Visual weight. Warning (amber) = recoverable / a reconnect is offered; Error (red) = a hard
// fault the user must act on (the daemon gave up crashing, or a lite wallet failed to open).
enum class NodeBannerSeverity {
    Warning,
    Error,
};

// What the banner's action button does. App maps this to the concrete call.
enum class NodeBannerAction {
    None,         // no button — nothing the user can usefully do from here
    Reconnect,    // full node: re-run the RPC connect state machine (App::tryConnect)
    RestartNode,  // full node: the embedded daemon crashed & auto-restart gave up (App::restartDaemon)
};

// Why the banner is up. App maps this to a translated headline; `detail` carries the live,
// already-human-readable status text (connection_status_ / daemon lastError / lite open error).
enum class NodeBannerReason {
    None,
    FullNodeOffline,  // a reachable node was lost, or never came up; reconnect offered
    DaemonCrashed,    // the embedded daemon crashed repeatedly and auto-restart stopped
    LiteOpenFailed,   // lite build: the wallet failed to open
    NoPeers,          // connected to the daemon but no peer-derived tip (0 peers) — stalled, stale
};

struct NodeBannerState {
    bool               show     = false;
    NodeBannerSeverity severity = NodeBannerSeverity::Warning;
    NodeBannerReason   reason   = NodeBannerReason::None;
    NodeBannerAction   action   = NodeBannerAction::None;
    std::string        detail;  // passthrough status/error text (may be empty)
};

// Snapshot of the connection state the banner reads. Plain values so the decision is testable
// without an App instance.
struct NodeBannerInputs {
    bool lite                 = false;  // lite build (no embedded daemon / RPC)
    bool connected            = false;  // state_.connected — the master "online" flag
    bool warming_up           = false;  // daemon reachable, RPC warmup (code -28)
    bool daemon_initializing  = false;  // daemon launching / block index loading
    bool connection_in_progress = false;  // a connect attempt is actively running

    // Peer connectivity: a full node connected to the daemon but with NO peer-derived tip
    // (longestchain == 0 -> tip_known false) has no peers, so it silently stalls at a stale height
    // while balance/sync look "done". `blocks` gates the check so we don't flag the brief window
    // before the first chain-info refresh. (audit: no-peer-banner)
    bool tip_known            = true;   // sync.tip_known — a peer-derived network tip is known
    int  blocks               = 0;      // sync.blocks — >0 once chain info has been fetched

    // Full-node embedded-daemon crash signal.
    bool using_embedded_daemon = false;
    bool has_daemon_controller = false;
    bool daemon_running        = false;
    int  daemon_crash_count    = 0;

    std::string connection_status;  // human-readable status line (already translated)
    std::string daemon_last_error;  // DaemonController::lastError() (may be empty)
    std::string lite_open_error;    // lite: last wallet-open failure reason
};

// Auto-restart give-up threshold — mirrors the crash cap in app_network.cpp's connect loop.
inline constexpr int kNodeBannerCrashGiveUpCount = 3;

inline NodeBannerState evaluateNodeStatusBanner(const NodeBannerInputs& in) {
    NodeBannerState s;

    if (in.lite) {
        // Lite has no daemon/RPC; "online" == wallet open. Only a genuine open failure is a
        // fault worth a persistent banner (a not-yet-created wallet is handled by the normal
        // "No wallet open" prompt, and leaves lite_open_error empty).
        if (!in.connected && !in.lite_open_error.empty()) {
            s.show     = true;
            s.severity = NodeBannerSeverity::Error;
            s.reason   = NodeBannerReason::LiteOpenFailed;
            s.action   = NodeBannerAction::None;
            s.detail   = in.lite_open_error;
        }
        return s;
    }

    // Full node. In an expected startup phase → the loading/warmup overlay owns the screen, so no
    // banner. An active connect attempt likewise shows progress, not an error — don't flicker a
    // banner over it. (These return even if `connected` has flipped early, matching prior behavior.)
    if (in.warming_up || in.daemon_initializing) return s;
    if (in.connection_in_progress) return s;

    // Connected to the daemon (RPC works) but with no peer-derived tip → the node has no peers and
    // cannot learn about new blocks; it silently stalls at a stale height while everything looks
    // synced. Surface it as a recoverable warning (reconnect won't help, so no action button).
    if (in.connected) {
        if (!in.tip_known && in.blocks > 0) {
            s.show     = true;
            s.severity = NodeBannerSeverity::Warning;
            s.reason   = NodeBannerReason::NoPeers;
            s.action   = NodeBannerAction::None;
            s.detail   = in.connection_status;
        }
        return s;
    }

    // Genuinely offline. Distinguish "the embedded daemon crashed and we stopped retrying" (a
    // hard fault needing a manual restart) from an ordinary lost/failed connection (retryable).
    if (in.using_embedded_daemon && in.has_daemon_controller && !in.daemon_running &&
        in.daemon_crash_count >= kNodeBannerCrashGiveUpCount) {
        s.show     = true;
        s.severity = NodeBannerSeverity::Error;
        s.reason   = NodeBannerReason::DaemonCrashed;
        s.action   = NodeBannerAction::RestartNode;
        s.detail   = !in.daemon_last_error.empty() ? in.daemon_last_error : in.connection_status;
        return s;
    }

    s.show     = true;
    s.severity = NodeBannerSeverity::Warning;
    s.reason   = NodeBannerReason::FullNodeOffline;
    s.action   = NodeBannerAction::Reconnect;
    s.detail   = in.connection_status;
    return s;
}

}  // namespace dragonx::ui
