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

#pragma once

#include "imgui.h"
#include "material/type.h"
#include "material/colors.h"
#include "material/draw_helpers.h"
#include "layout.h"
#include "schema/ui_schema.h"
#include "../embedded/IconsMaterialDesign.h"
#include "../util/i18n.h"
#include "../wallet/wallet_capabilities.h"
#include <cstdio>
#include <cmath>

namespace dragonx {
namespace ui {

// Navigation pages — order matches sidebar display order
enum class NavPage {
    Overview = 0,
    Send,
    Receive,
    History,
    Contacts,
    Chat,
    // --- separator ---
    Mining,
    Market,
    // --- separator ---
    Console,
    LiteConsole,   // lite-only diagnostics console (full-node uses the RPC Console); one per variant
    Peers,
    LiteNetwork,   // lite-only "Network" tab (full-node uses Peers); exactly one shows per variant
    Explorer,
    Settings,
    Count_
};

struct NavItem {
    const char* label;           // fallback label (English)
    NavPage     page;
    const char* section_label;   // if non-null, render section label above this item
    const char* tr_key;          // i18n key for label
    const char* section_tr_key;  // i18n key for section_label
};

inline const NavItem kNavItems[] = {
    { "Overview",  NavPage::Overview, nullptr,    "overview", nullptr     },
    { "Send",      NavPage::Send,     nullptr,    "send",     nullptr     },
    { "Receive",   NavPage::Receive,  nullptr,    "receive",  nullptr     },
    { "History",   NavPage::History,  nullptr,    "history",  nullptr     },
    { "Contacts",  NavPage::Contacts, nullptr,    "contacts", nullptr     },
    { "Chat",      NavPage::Chat,     nullptr,    "chat",     nullptr     },
    { "Mining",    NavPage::Mining,   "TOOLS",   "mining",   "tools"     },
    { "Market",    NavPage::Market,   nullptr,    "market",   nullptr     },
    { "Console",   NavPage::Console,     "ADVANCED","console",  "advanced"  },
    { "Console",   NavPage::LiteConsole, "ADVANCED","console",  "advanced"  },
    { "Network",   NavPage::Peers,       nullptr,   "network",  nullptr     },
    { "Network",   NavPage::LiteNetwork, nullptr,   "network",  nullptr     },
    { "Explorer",  NavPage::Explorer, nullptr,    "explorer", nullptr     },
    { "Settings",  NavPage::Settings, nullptr,    "settings", nullptr     },
};
static_assert(sizeof(kNavItems) / sizeof(kNavItems[0]) == (int)NavPage::Count_,
              "kNavItems must match NavPage::Count_");

// Get translated nav label at runtime
inline const char* NavLabel(const NavItem& item) {
    return item.tr_key ? TR(item.tr_key) : item.label;
}
inline const char* NavSectionLabel(const NavItem& item) {
    return item.section_tr_key ? TR(item.section_tr_key) : item.section_label;
}

inline wallet::WalletUiSurface NavPageSurface(NavPage page)
{
    switch (page) {
    case NavPage::Overview: return wallet::WalletUiSurface::Overview;
    case NavPage::Send:     return wallet::WalletUiSurface::Send;
    case NavPage::Receive:  return wallet::WalletUiSurface::Receive;
    case NavPage::History:  return wallet::WalletUiSurface::History;
    case NavPage::Contacts: return wallet::WalletUiSurface::Contacts;
    case NavPage::Chat:     return wallet::WalletUiSurface::Chat;
    case NavPage::Mining:   return wallet::WalletUiSurface::Mining;
    case NavPage::Market:   return wallet::WalletUiSurface::Market;
    case NavPage::Console:  return wallet::WalletUiSurface::Console;
    case NavPage::LiteConsole: return wallet::WalletUiSurface::LiteConsole;
    case NavPage::Peers:    return wallet::WalletUiSurface::Peers;
    case NavPage::LiteNetwork: return wallet::WalletUiSurface::LiteNetwork;
    case NavPage::Explorer: return wallet::WalletUiSurface::Explorer;
    case NavPage::Settings: return wallet::WalletUiSurface::Settings;
    default:                return wallet::WalletUiSurface::Overview;
    }
}

inline bool IsNavPageVisible(NavPage page)
{
    return wallet::isUiSurfaceAvailable(wallet::currentWalletCapabilities(), NavPageSurface(page));
}

// Get the Material Design icon string for a navigation page.
inline const char* GetNavIconMD(NavPage page)
{
    switch (page) {
    case NavPage::Overview:  return ICON_MD_HOME;
    case NavPage::Send:      return ICON_MD_CALL_MADE;
    case NavPage::Receive:   return ICON_MD_CALL_RECEIVED;
    case NavPage::History:   return ICON_MD_HISTORY;
    case NavPage::Contacts:  return ICON_MD_CONTACTS;
    case NavPage::Chat:      return ICON_MD_CHAT;
    case NavPage::Mining:    return ICON_MD_CONSTRUCTION;
    case NavPage::Market:    return ICON_MD_TRENDING_UP;
    case NavPage::Console:   return ICON_MD_TERMINAL;
    case NavPage::LiteConsole: return ICON_MD_TERMINAL;
    case NavPage::Peers:     return ICON_MD_HUB;
    case NavPage::LiteNetwork: return ICON_MD_HUB;
    case NavPage::Explorer:  return ICON_MD_EXPLORE;
    case NavPage::Settings:  return ICON_MD_SETTINGS;
    default:                 return ICON_MD_HOME;
    }
}

// Compute the effective draw-list font size for a given font.
// During smooth font-scale drag, FontScaleMain compensates for the atlas
// not yet being rebuilt.  drawList->AddText bypasses that, so we apply
// the factor manually to keep sidebar text in sync with the rest of the UI.
inline float ScaledFontSize(ImFont* f) {
    return f->LegacySize * ImGui::GetStyle().FontScaleMain;
}

// Draw a Material Design icon centered at (cx, cy) with the given color.
// Uses the medium (18px) icon font from Typography.
inline void DrawNavIcon(ImDrawList* dl, NavPage page, float cx, float cy, float /*s*/, ImU32 col)
{
    ImFont* iconFont = material::Type().iconMed();
    const char* icon = GetNavIconMD(page);
    float fsz = ScaledFontSize(iconFont);
    ImVec2 sz = iconFont->CalcTextSizeA(fsz, 1000.0f, 0.0f, icon);
    dl->AddText(iconFont, fsz,
                ImVec2(cx - sz.x * 0.5f, cy - sz.y * 0.5f), col, icon);
}

// Lightweight badge / status data the caller provides each frame.
// Counts <= 0 mean "no badge".  -1 means "show dot only" (no number).
struct SidebarStatus {
    int  unconfirmedTxCount = 0;   // badge on History
    bool miningActive       = false; // green dot on Mining
    int  peerCount          = 0;   // badge on Peers
    int  chatUnreadCount    = 0;   // badge on Chat (unread incoming messages)
    // Exit
    bool exitClicked        = false;
    // Branding logo (optional — loaded at startup)
    ImTextureID logoTexID   = 0;
    int  logoW              = 0;
    int  logoH              = 0;
    // Gradient overlay texture (optional)
    ImTextureID gradientTexID = 0;
};

// Draw an inset cutout bevel around a button rect — the "channel" carved
// into the sidebar surface that the raised button sits inside.
// Highlights and shadows are flipped relative to the button bevel so the
// cutout looks like it is recessed into the material.  Light source: top-left.
inline void DrawGlassCutout(ImDrawList* dl, ImVec2 mn, ImVec2 mx,
                            float rnd, float gap = 2.0f)
{
    float w = mx.x - mn.x + gap * 2.0f;
    float h = mx.y - mn.y + gap * 2.0f;
    if (w < 1.0f || h < 1.0f) return;

    // Cached cutout style — refreshed once per theme reload
    struct CutoutStyleCache {
        uint32_t gen = 0;
        float shadowAlpha, highlightAlpha, lineW;
        float glowExpand, glowAlpha, glowLineW;
    };
    static CutoutStyleCache s_cc;
    {
        uint32_t g = schema::UI().generation();
        if (g != s_cc.gen) {
            s_cc.gen = g;
            auto rd = [](const char* key, float fb) {
                auto e = schema::UI().drawElement("components.sidebar", key);
                return e.size >= 0 ? e.size : fb;
            };
            s_cc.shadowAlpha    = rd("cutout-shadow-alpha", 55.0f);
            s_cc.highlightAlpha = rd("cutout-highlight-alpha", 16.0f);
            s_cc.lineW          = rd("cutout-line-width", 0.75f);
            s_cc.glowExpand     = rd("cutout-glow-expand", 1.5f);
            s_cc.glowAlpha      = rd("cutout-glow-alpha", 35.0f);
            s_cc.glowLineW      = rd("cutout-glow-line-width", 1.5f);
        }
    }

    ImVec2 cMn(mn.x - gap, mn.y - gap);
    ImVec2 cMx(mx.x + gap, mx.y + gap);
    float cRnd = rnd + gap;

    float cx = (cMn.x + cMx.x) * 0.5f;
    float cy = (cMn.y + cMx.y) * 0.5f;

    // Fast directional light factor — algebraic approximation of angular
    // proximity to fixed light at -135°.  Replaces atan2f per vertex with
    // a dot-product + clamp (no trig, no sqrt).
    float invHalfW = 2.0f / w;
    float invHalfH = 2.0f / h;
    auto lightFactor = [cx, cy, invHalfW, invHalfH](float px, float py) -> float {
        float nx = (px - cx) * invHalfW;
        float ny = (py - cy) * invHalfH;
        return ImClamp(0.5f + 0.3536f * (-nx - ny), 0.0f, 1.0f);
    };

    float fadeStart = 0.30f, fadeEnd = 0.55f;
    int shA = (int)s_cc.shadowAlpha;
    int hiA = (int)s_cc.highlightAlpha;
    float lineW = s_cc.lineW;

    // --- Outer glow pass: wider, softer dark edge on top-left ---
    float glowExpand = s_cc.glowExpand * Layout::dpiScale();
    int   glowA      = (int)s_cc.glowAlpha;
    float glowLineW  = s_cc.glowLineW;
    {
        ImVec2 gMn(cMn.x - glowExpand, cMn.y - glowExpand);
        ImVec2 gMx(cMx.x + glowExpand, cMx.y + glowExpand);
        float gRnd = cRnd + glowExpand;
        int v0 = dl->VtxBuffer.Size;
        dl->AddRect(gMn, gMx, IM_COL32(0, 0, 0, 1), gRnd, 0, glowLineW);
        int v1 = dl->VtxBuffer.Size;
        for (int i = v0; i < v1; i++) {
            ImDrawVert& v = dl->VtxBuffer[i];
            float lf = lightFactor(v.pos.x, v.pos.y);
            float fade = ImClamp((lf - 0.25f) / 0.30f, 0.0f, 1.0f);
            int a = (int)(glowA * fade);
            v.col = IM_COL32(0, 0, 0, (unsigned char)ImClamp(a, 0, 255));
        }
    }

    // --- Crisp shadow pass (on light-facing edges — top-left dark edge) ---
    {
        int v0 = dl->VtxBuffer.Size;
        dl->AddRect(cMn, cMx, IM_COL32(0, 0, 0, 1), cRnd, 0, lineW);
        int v1 = dl->VtxBuffer.Size;
        for (int i = v0; i < v1; i++) {
            ImDrawVert& v = dl->VtxBuffer[i];
            float lf = lightFactor(v.pos.x, v.pos.y);
            float fade = ImClamp((lf - fadeStart) / (fadeEnd - fadeStart), 0.0f, 1.0f);
            int a = (int)(shA * fade);
            v.col = IM_COL32(0, 0, 0, (unsigned char)ImClamp(a, 0, 255));
        }
    }

    // --- Highlight pass (on shadow-facing edges — bottom-right highlight) ---
    {
        int v0 = dl->VtxBuffer.Size;
        dl->AddRect(cMn, cMx, IM_COL32(255, 255, 255, 1), cRnd, 0, lineW);
        int v1 = dl->VtxBuffer.Size;
        for (int i = v0; i < v1; i++) {
            ImDrawVert& v = dl->VtxBuffer[i];
            float lf = lightFactor(v.pos.x, v.pos.y);
            float sf = 1.0f - lf;
            float fade = ImClamp((sf - fadeStart) / (fadeEnd - fadeStart), 0.0f, 1.0f);
            int a = (int)(hiA * fade);
            v.col = IM_COL32(255, 255, 255, (unsigned char)ImClamp(a, 0, 255));
        }
    }
}

// Draw a neumorphic button molded from the sidebar surface itself.
// No contrasting fill — uses only shadows and highlights so the button
// appears to be the exact same material as the sidebar, just shaped.
// Light source: top-left.  depth controls the press amount:
//   0.0 = fully raised / convex  (normal idle state)
//   0.5 = half-pressed           (hover state)
//   1.0 = fully pressed in       (selected / clicked)
inline void DrawGlassBevelButton(ImDrawList* dl, ImVec2 mn, ImVec2 mx,
                                 float rnd, float depth = 0.0f, int /*fillAlpha*/ = 18)
{
    float w = mx.x - mn.x;
    float h = mx.y - mn.y;
    if (w < 1.0f || h < 1.0f) return;
    float inv = 1.0f - depth; // 1 raised, 0 pressed

    // ---- Directional bevel + neumorphic glow ----
    // Multiple concentric AddRect outline passes with directional vertex
    // coloring. Wider outer passes (expanded rect) create soft shadow glow;
    // inner pass gives crisp bevel edge. All use AddRect with rounding so
    // every layer follows the rounded corners perfectly — no clip rects needed.
    {
        const float dp = Layout::dpiScale();
        float cx = (mn.x + mx.x) * 0.5f;
        float cy = (mn.y + mx.y) * 0.5f;

        // Fast directional light factor — algebraic dot-product approximation
        // of angular proximity to fixed light at -135°.  No trig, no sqrt.
        float invHalfW = 2.0f / w;
        float invHalfH = 2.0f / h;
        auto lightFactor = [cx, cy, invHalfW, invHalfH](float px, float py) -> float {
            float nx = (px - cx) * invHalfW;
            float ny = (py - cy) * invHalfH;
            return ImClamp(0.5f + 0.3536f * (-nx - ny), 0.0f, 1.0f);
        };

        struct BevelPass { float expand; float lineW; float fadeStart; float fadeEnd; };
        BevelPass passes[] = {
            { 0.5f * dp, 0.75f, 0.30f, 0.55f }, // Outer glow (thin)
            { 0.0f, 0.75f, 0.38f, 0.58f }, // Inner crisp bevel
        };

        for (auto& bp : passes) {
            // Compute directional alpha.
            // Outer passes fade out when pressed; inner bevel swaps direction.
            int hiA, shA;
            if (bp.expand > 0.0f) {
                float baseHi = (bp.expand > 1.0f) ? 12.0f : 18.0f;
                float baseSh = (bp.expand > 1.0f) ? 20.0f : 30.0f;
                hiA = (int)(baseHi * inv);
                shA = (int)(baseSh * inv);
            } else {
                hiA = (int)(40.0f * inv + 15.0f * depth);
                shA = (int)(45.0f * inv + 60.0f * depth);
            }

            ImVec2 pMn(mn.x - bp.expand, mn.y - bp.expand);
            ImVec2 pMx(mx.x + bp.expand, mx.y + bp.expand);
            float pRnd = rnd + bp.expand;

            // Highlight pass (light-facing edges)
            {
                int v0 = dl->VtxBuffer.Size;
                dl->AddRect(pMn, pMx, IM_COL32(255, 255, 255, 1), pRnd, 0, bp.lineW);
                int v1 = dl->VtxBuffer.Size;
                for (int i = v0; i < v1; i++) {
                    ImDrawVert& v = dl->VtxBuffer[i];
                    float lf = lightFactor(v.pos.x, v.pos.y);
                    float fade = ImClamp((lf - bp.fadeStart) / (bp.fadeEnd - bp.fadeStart), 0.0f, 1.0f);
                    int targetA = (depth < 0.5f) ? hiA : shA;
                    int a = (int)(targetA * fade);
                    v.col = (depth < 0.5f)
                        ? IM_COL32(255, 255, 255, (unsigned char)ImClamp(a, 0, 255))
                        : IM_COL32(0, 0, 0, (unsigned char)ImClamp(a, 0, 255));
                }
            }

            // Shadow pass (shadow-facing edges)
            {
                int v0 = dl->VtxBuffer.Size;
                dl->AddRect(pMn, pMx, IM_COL32(0, 0, 0, 1), pRnd, 0, bp.lineW);
                int v1 = dl->VtxBuffer.Size;
                for (int i = v0; i < v1; i++) {
                    ImDrawVert& v = dl->VtxBuffer[i];
                    float lf = lightFactor(v.pos.x, v.pos.y);
                    float sf = 1.0f - lf;
                    float fade = ImClamp((sf - bp.fadeStart) / (bp.fadeEnd - bp.fadeStart), 0.0f, 1.0f);
                    int targetA = (depth < 0.5f) ? shA : hiA;
                    int a = (int)(targetA * fade);
                    v.col = (depth < 0.5f)
                        ? IM_COL32(0, 0, 0, (unsigned char)ImClamp(a, 0, 255))
                        : IM_COL32(255, 255, 255, (unsigned char)ImClamp(a, 0, 255));
                }
            }
        }
    }

    // ---- 3. Inset shadow (scales with depth, emanates from all edges inward) ----
    // Uses concentric rounded rects with decreasing alpha to avoid triangle
    // interpolation artifacts that occur with single-rect vertex color hacking.
    // Cached inset shadow style — refreshed once per theme reload
    struct InsetCache {
        uint32_t gen = 0;
        float threshold, inset, maxAlpha, fadeRatio;
    };
    static InsetCache s_ic;
    {
        uint32_t g = schema::UI().generation();
        if (g != s_ic.gen) {
            s_ic.gen = g;
            auto rd = [](const char* key, float fb) {
                auto e = schema::UI().drawElement("components.sidebar", key);
                return e.size >= 0 ? e.size : fb;
            };
            s_ic.threshold = rd("inset-shadow-threshold", 0.1f);
            s_ic.inset     = rd("inset-shadow-inset", 1.0f);
            s_ic.maxAlpha  = rd("inset-shadow-max-alpha", 140.0f);
            s_ic.fadeRatio = rd("inset-shadow-fade-ratio", 0.35f);
        }
    }
    if (depth > s_ic.threshold) {
        float baseInset = s_ic.inset * Layout::dpiScale();
        int shadowMax = (int)(s_ic.maxAlpha * depth);
        float fadeRatio = s_ic.fadeRatio;
        float bW = mx.x - mn.x - baseInset * 2.0f;
        float bH = mx.y - mn.y - baseInset * 2.0f;
        if (bW > 0.0f && bH > 0.0f) {
            float fadeDepth = ImMin(bW, bH) * fadeRatio;
            const int steps = 8;
            // Draw inside-out: innermost (lightest) first, then progressively
            // larger/darker rects on top. Each outer rect extends further toward
            // the edges, adding darkness only at the perimeter.
            for (int s = steps - 1; s >= 0; s--) {
                float t = (float)s / (float)(steps - 1); // 0 = edge, 1 = deepest
                float shrink = baseInset + t * fadeDepth;
                ImVec2 sMn(mn.x + shrink, mn.y + shrink);
                ImVec2 sMx(mx.x - shrink, mx.y - shrink);
                if (sMx.x <= sMn.x || sMx.y <= sMn.y) continue;
                float sRnd = ImMax(rnd - shrink, 0.0f);
                // Quadratic falloff: darkest at edge (t=0), zero at center (t=1)
                float alpha01 = (1.0f - t) * (1.0f - t);
                int a = (int)((float)shadowMax * alpha01);
                if (a < 1) continue;
                dl->AddRectFilled(sMn, sMx, IM_COL32(0, 0, 0, (unsigned char)ImClamp(a, 0, 255)), sRnd);
            }
        }
    }
    // (No specular highlight — same matte material as sidebar)
}

// Render the sidebar navigation. Returns true if the page changed.
// collapsed: when true, sidebar is in icon-only mode (narrow width).
// The caller can toggle collapsed via a reference if a toggle button is desired.
inline bool RenderSidebar(NavPage& current, float sidebarWidth, float contentHeight,
                          SidebarStatus& status, bool& collapsed, bool locked = false)
{
    using namespace material;
    bool changed = false;

    // Collect button rects so we can punch holes in the glass panel fill
    // to avoid opacity stacking (buttons should be same opacity as sidebar).
    struct Rect { ImVec2 mn, mx; float rnd; };
    ImVector<Rect> buttonRects;

    const auto& S  = schema::UISchema::instance();

    // Read sidebar layout from schema (with fallbacks)
    // All TOML values are in logical pixels; multiply by DPI scale
    // for correct physical pixel sizing on high-DPI displays.
    const float dp = Layout::dpiScale();
    auto sde = [&](const char* key, float fallback) {
        auto e = S.drawElement("components.sidebar", key);
        return (e.size >= 0 ? e.size : fallback) * dp;
    };
    const float sbWidth            = sde("width",                160.0f);
    const float sbCollapsedWidth   = sde("collapsed-width",     64.0f);
    const float glassMarginY       = sde("glass-margin-y",       6.0f);
    const float glassMarginL       = sde("glass-margin-left",    6.0f);
    const float glassMarginR       = sde("glass-margin-right",   2.0f);
    const float stripH             = sde("strip-height",         20.0f);
    const float btnPadCollapsed    = sde("button-pad-collapsed", 8.0f);
    const float btnPadExpanded     = sde("button-pad-expanded",  14.0f);
    const float iconHalfSize       = sde("icon-half-size",       7.0f);
    const float iconLabelGap       = sde("icon-label-gap",       8.0f);
    const float badgeRadiusDot     = sde("badge-radius-dot",     4.0f);
    const float badgeRadiusNumber  = sde("badge-radius-number",  8.0f);
    const float bottomPadding      = sde("bottom-padding",       6.0f);
    const float exitIconGap        = sde("exit-icon-gap",        4.0f);
    const float sbSectionLabelPadLeft = sde("section-label-pad-left", 16.0f);

    // Base values for responsive scaling
    const float baseItemHeight     = sde("item-height",          46.0f);
    const float baseNavGap         = sde("nav-gap",              20.0f);
    const float baseSectionGap     = sde("section-gap",          8.0f);
    const float baseButtonSpacing  = sde("button-spacing",       6.0f);
    const float sectionLabelPadBot = sde("section-label-pad-bottom", 4.0f);

    // How "expanded" are we? 0.0 = fully collapsed, 1.0 = fully expanded
    float expandFrac = (sbWidth > sbCollapsedWidth)
        ? (sidebarWidth - sbCollapsedWidth) / (sbWidth - sbCollapsedWidth)
        : 1.0f;
    if (expandFrac < 0.0f) expandFrac = 0.0f;
    if (expandFrac > 1.0f) expandFrac = 1.0f;
    bool showLabels = expandFrac > 0.3f;

    // Font size for section labels (fixed, doesn't scale)
    float olFsz = ScaledFontSize(Type().overline());

    // Glass panel rounding from responsive schema
    float glassRounding = [&]() {
        float v = S.drawElement("responsive", "glass-rounding").size;
        return (v >= 0 ? v : 8.0f) * dp;
    }();

    // ===================================================================
    // PASS 1: Compute layout — all Y positions relative to panel top
    // ===================================================================
    // Separate fixed parts (don't scale) from flex parts (scale when tight).
    float fixedH = stripH;  // collapse strip
    for (int i = 0; i < (int)NavPage::Count_; ++i)
        if (IsNavPageVisible(kNavItems[i].page) && kNavItems[i].section_label && showLabels)
            fixedH += olFsz + 2.0f * dp + sectionLabelPadBot;  // section label + pad below
    fixedH += bottomPadding + stripH;  // exit area

    float baseFlexH = baseNavGap;
    for (int i = 0; i < (int)NavPage::Count_; ++i) {
        if (!IsNavPageVisible(kNavItems[i].page)) continue;
        if (kNavItems[i].section_label) {
            if (showLabels) baseFlexH += baseSectionGap;
            else            baseFlexH += baseSectionGap * 0.8f;
        }
        baseFlexH += baseItemHeight + baseButtonSpacing;
    }

    // Responsive shrink (only scales flex parts)
    float sidebarMinHeight = sde("min-height", 360.0f);
    float scaleFloor = (sidebarMinHeight > fixedH && baseFlexH > 0.0f)
        ? std::max(0.55f, (sidebarMinHeight - fixedH) / baseFlexH) : 0.55f;
    float sidebarScale = 1.0f;
    if (fixedH + baseFlexH > contentHeight && baseFlexH > 0.0f)
        sidebarScale = std::max(scaleFloor, (contentHeight - fixedH) / baseFlexH);

    const float itemH      = baseItemHeight * sidebarScale;
    const float navGap     = baseNavGap * sidebarScale;
    const float sectionGap = baseSectionGap * sidebarScale;
    const float btnSpacing = baseButtonSpacing * sidebarScale;

    // Compute Y position for every element (relative to panel top)
    float itemY[(int)NavPage::Count_];
    float sectionLabelY[4] = {};   int nSectionLabels = 0;
    float separatorY[4] = {};      int nSeparators = 0;

    float curY = stripH + navGap;  // after collapse strip + gap
    for (int i = 0; i < (int)NavPage::Count_; ++i) {
        if (!IsNavPageVisible(kNavItems[i].page)) continue;
        if (kNavItems[i].section_label) {
            if (showLabels) {
                curY += sectionGap;
                if (nSectionLabels < 4) sectionLabelY[nSectionLabels++] = curY;
                curY += olFsz + 2.0f * dp + sectionLabelPadBot;
            } else {
                curY += sectionGap * 0.4f;
                if (nSeparators < 4) separatorY[nSeparators++] = curY;
                curY += sectionGap * 0.4f;
            }
        }
        itemY[i] = curY;
        curY += itemH + btnSpacing;
    }
    float exitRelY = curY + bottomPadding;
    float panelH   = exitRelY + stripH;

    // ===================================================================
    // PASS 2: Render using computed positions
    // ===================================================================
    ImGui::BeginChild("##Sidebar", ImVec2(sidebarWidth, contentHeight), false,
        ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoBackground);

    ImDrawList* dl = ImGui::GetWindowDrawList();
    ImVec2 wp = ImGui::GetWindowPos();

    // Vertical centering — center the panel within the child. app.cpp sizes the child (contentHeight)
    // to the visible area (child top -> status-bar top) using window-local geometry, so this yields
    // equal top/bottom gaps at any height on every platform, no viewport dependency.
    float centerOffset = std::max(glassMarginY, (contentHeight - panelH) * 0.5f);
    if (centerOffset + panelH > contentHeight)
        centerOffset = std::max(0.0f, contentHeight - panelH);

    float panelLeft   = wp.x + glassMarginL;
    float panelRight  = wp.x + sidebarWidth - glassMarginR;
    float panelTopY   = wp.y + centerOffset;
    float panelBotY   = std::min(panelTopY + panelH, wp.y + contentHeight - glassMarginY);

    // Draw list splitter: channel 0 = glass background, channel 1 = content
    ImDrawListSplitter splitter;
    splitter.Split(dl, 2);
    splitter.SetCurrentChannel(dl, 1);

    // ---- Collapse toggle strip (panel top) ----
    {
        float stripX = panelLeft;
        float stripW = panelRight - panelLeft;
        ImVec2 stripMin(stripX, panelTopY);
        ImVec2 stripMax(stripX + stripW, panelTopY + stripH);

        ImGui::SetCursorScreenPos(stripMin);
        if (ImGui::InvisibleButton("##SidebarCollapse", ImVec2(stripW, stripH)))
            collapsed = !collapsed;
        bool btnHover = ImGui::IsItemHovered();

        if (btnHover) {
            dl->AddRectFilled(stripMin, stripMax,
                schema::UI().resolveColor("var(--sidebar-hover)", IM_COL32(255, 255, 255, 25)),
                glassRounding, ImDrawFlags_RoundCornersTop);
            ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
        }
        dl->AddLine(ImVec2(stripMin.x + glassRounding * 0.5f, stripMax.y),
                    ImVec2(stripMax.x - glassRounding * 0.5f, stripMax.y),
                    schema::UI().resolveColor("var(--sidebar-divider)", IM_COL32(255, 255, 255, 15)), 1.0f);

        ImU32 iconCol = btnHover ? OnSurface() : schema::UI().resolveColor("var(--sidebar-icon)", IM_COL32(255, 255, 255, 60));
        float cx = stripX + stripW * 0.5f;
        float cy = panelTopY + stripH * 0.5f;
        ImFont* iconFont = Type().iconSmall();
        const char* chevIcon = collapsed ? ICON_MD_CHEVRON_RIGHT : ICON_MD_CHEVRON_LEFT;
        float chevFsz = ScaledFontSize(iconFont);
        ImVec2 chevSz = iconFont->CalcTextSizeA(chevFsz, 1000.0f, 0.0f, chevIcon);
        dl->AddText(iconFont, chevFsz,
                    ImVec2(cx - chevSz.x * 0.5f, cy - chevSz.y * 0.5f), iconCol, chevIcon);
    }

    // ---- Section labels / separators ----
    if (showLabels) {
        ImFont* olFont = Type().overline();
        float olFontSz = ScaledFontSize(olFont);
        float maxSectionW = panelRight - wp.x - sbSectionLabelPadLeft;
        int si = 0;
        for (int i = 0; i < (int)NavPage::Count_; ++i) {
            if (!IsNavPageVisible(kNavItems[i].page)) continue;
            if (kNavItems[i].section_label && si < nSectionLabels) {
                float ly = panelTopY + sectionLabelY[si++];
                const char* sLabel = NavSectionLabel(kNavItems[i]);
                float drawFsz = olFontSz;
                ImVec2 sLabelSz = olFont->CalcTextSizeA(drawFsz, 1000.0f, 0.0f, sLabel);
                if (sLabelSz.x > maxSectionW && maxSectionW > 0)
                    drawFsz *= maxSectionW / sLabelSz.x;
                ImVec4 olCol = ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium());
                olCol.w *= expandFrac;
                dl->AddText(olFont, drawFsz,
                    ImVec2(wp.x + sbSectionLabelPadLeft, ly),
                    ImGui::ColorConvertFloat4ToU32(olCol), sLabel);
            }
        }
    } else {
        for (int si = 0; si < nSeparators; ++si) {
            float sy = panelTopY + separatorY[si];
            dl->AddLine(ImVec2(wp.x + btnPadCollapsed, sy),
                        ImVec2(wp.x + sidebarWidth - btnPadCollapsed, sy),
                        Divider(), 1.0f);
        }
    }

    // ---- Navigation items ----
    float btnPadX = collapsed ? btnPadCollapsed : btnPadExpanded;
    for (int i = 0; i < (int)NavPage::Count_; ++i) {
        const NavItem& item = kNavItems[i];
        if (!IsNavPageVisible(item.page)) continue;
        bool selected = (current == item.page);
        float btnY = panelTopY + itemY[i];
        float btnRnd = itemH * 0.22f;

        ImVec2 indMin(panelLeft + btnPadX, btnY);
        ImVec2 indMax(panelRight - btnPadX, btnY + itemH);

        bool hovered = material::IsRectHovered(indMin, indMax);

        // Glass bevel + theme effects
        {
            float btnDepth = selected ? 1.0f : (hovered ? 0.5f : 0.0f);
            if (selected) {
                auto& fx = effects::ThemeEffects::instance();
                fx.drawGlowPulse(dl, indMin, indMax, btnRnd);
                fx.drawEdgeTrace(dl, indMin, indMax, btnRnd);
                fx.drawEmberRise(dl, indMin, indMax);
                fx.drawShimmer(dl, indMin, indMax, btnRnd);
                fx.drawGradientBorderShift(dl, indMin, indMax, btnRnd);
            }
            DrawGlassCutout(dl, indMin, indMax, btnRnd, 1.5f * dp);
            DrawGlassBevelButton(dl, indMin, indMax, btnRnd, btnDepth, 18);
            buttonRects.push_back({indMin, indMax, btnRnd});
        }

        if (hovered) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);

        // Click detection
        bool pageNeedsUnlock = locked &&
            item.page != NavPage::Console &&
            item.page != NavPage::LiteConsole &&
            item.page != NavPage::Peers &&
            item.page != NavPage::Settings;
        if (hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !pageNeedsUnlock) {
            current = item.page;
            changed = true;
        }

        // Icon + label
        float iconS = iconHalfSize;
        float iconCY = btnY + itemH * 0.5f;
        float textY = btnY + (itemH - ImGui::GetTextLineHeight()) * 0.5f;
        ImU32 textCol = selected ? Primary() : (pageNeedsUnlock ? OnSurfaceDisabled() : OnSurfaceMedium());

        if (showLabels) {
            // The badge is a fixed top-right corner overlay, so it must NOT move
            // the icon+label — otherwise the text jumps sideways the moment a live
            // count toggles the badge on/off. Reserve clearance from whether the
            // page CAN show a badge (constant per item), never from the current
            // count, and keep the icon+label centered in the FULL button width so
            // the text position and size stay identical with or without a badge.
            bool itemBadgeCapable =
                item.page == NavPage::History ||
                item.page == NavPage::Mining ||
                item.page == NavPage::Chat;
            float badgeReserve = 0.0f;
            if (itemBadgeCapable) {
                bool dotOnlyReserve = (item.page == NavPage::Mining);
                float badgeRReserve = dotOnlyReserve ? badgeRadiusDot : badgeRadiusNumber;
                float badgeInsetXReserve = sde("badge-inset-x", 6.0f);
                badgeReserve = badgeRReserve * 2.0f + badgeInsetXReserve;
            }

            ImFont* font = selected ? Type().subtitle2() : Type().body2();
            float lblFsz = ScaledFontSize(font);
            float btnW = indMax.x - indMin.x;
            // Clearance is symmetric (2x) because the group stays centered in the
            // full width: reserving on both sides keeps the label's right edge clear
            // of the right-side corner badge without shifting the center off-axis.
            float maxLabelW = btnW - iconS * 2.0f - iconLabelGap - Layout::spacingXs() * 2 - badgeReserve * 2.0f;
            ImVec2 labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item));
            if (labelSz.x > maxLabelW && maxLabelW > 0) {
                lblFsz *= maxLabelW / labelSz.x;
                labelSz = font->CalcTextSizeA(lblFsz, 1000.0f, 0.0f, NavLabel(item));
            }
            float totalW = iconS * 2.0f + iconLabelGap + labelSz.x;
            float btnCX = (indMin.x + indMax.x) * 0.5f;
            float startX = btnCX - totalW * 0.5f;

            DrawNavIcon(dl, item.page, startX + iconS, iconCY, iconS, textCol);

            ImVec4 lc = ImGui::ColorConvertU32ToFloat4(textCol);
            lc.w *= expandFrac;
            dl->AddText(font, lblFsz,
                ImVec2(startX + iconS * 2.0f + iconLabelGap, textY),
                ImGui::ColorConvertFloat4ToU32(lc), NavLabel(item));
        } else {
            float iconCX = (indMin.x + indMax.x) * 0.5f;
            DrawNavIcon(dl, item.page, iconCX, iconCY, iconS, textCol);
            if (hovered) material::Tooltip("%s", NavLabel(item));
        }

        // Badge indicator
        {
            int badgeCount = 0;
            bool dotOnly = false;
            ImU32 badgeCol = Primary();
            ImU32 badgeTextCol = OnPrimary();

            if (item.page == NavPage::History && status.unconfirmedTxCount > 0) {
                badgeCount = status.unconfirmedTxCount;
                badgeCol = Warning(); badgeTextCol = OnWarning();
            } else if (item.page == NavPage::Mining && status.miningActive) {
                dotOnly = true; badgeCol = Success();
            } else if (item.page == NavPage::Chat && status.chatUnreadCount > 0) {
                badgeCount = status.chatUnreadCount;
            }

            if (badgeCount > 0 || dotOnly) {
                float badgeR = dotOnly ? badgeRadiusDot : badgeRadiusNumber;
                float badgeInsetX = sde("badge-inset-x", 6.0f);
                float badgeInsetY = sde("badge-inset-y", 5.0f);
                float bx = indMax.x - badgeR - badgeInsetX;
                float by = indMin.y + badgeR + badgeInsetY;
                dl->AddCircleFilled(ImVec2(bx, by), badgeR, badgeCol);
                if (!dotOnly && showLabels) {
                    char buf[16];
                    snprintf(buf, sizeof(buf), "%d", badgeCount > 99 ? 99 : badgeCount);
                    ImFont* capFont = Type().caption();
                    float capFsz = ScaledFontSize(capFont);
                    ImVec2 ts = capFont->CalcTextSizeA(capFsz, 1000.0f, 0.0f, buf);
                    dl->AddText(capFont, capFsz,
                        ImVec2(bx - ts.x * 0.5f, by - ts.y * 0.5f), badgeTextCol, buf);
                }
            }
        }
    }

    // ---- Keyboard navigation ----
    if (!ImGui::GetIO().WantTextInput && !ImGui::GetIO().KeyCtrl) {
        int idx = static_cast<int>(current);
        bool nav = false;
        if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) || ImGui::IsKeyPressed(ImGuiKey_K)) {
            do { idx--; } while (idx >= 0 && !IsNavPageVisible(static_cast<NavPage>(idx)));
            if (idx >= 0) nav = true;
        }
        if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) || ImGui::IsKeyPressed(ImGuiKey_J)) {
            do { idx++; } while (idx < (int)NavPage::Count_ && !IsNavPageVisible(static_cast<NavPage>(idx)));
            if (idx < (int)NavPage::Count_) nav = true;
        }
        if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket))
            collapsed = !collapsed;
        if (nav) { current = static_cast<NavPage>(idx); changed = true; }
    }

    // ---- Glass panel background (channel 0) ----
    splitter.SetCurrentChannel(dl, 0);
    {
        ImVec2 panelMin(panelLeft, panelTopY);
        ImVec2 panelMax(panelRight, panelBotY);
        GlassPanelSpec glass;
        glass.rounding    = glassRounding;
        glass.fillAlpha   = 14;
        glass.borderAlpha = 25;
        glass.borderWidth = 1.0f;
        int vtx0 = dl->VtxBuffer.Size;
        DrawGlassPanel(dl, panelMin, panelMax, glass);
        int vtx1 = dl->VtxBuffer.Size;

        // Punch holes in glass where buttons are to avoid double opacity
        for (int vi = vtx0; vi < vtx1; vi++) {
            ImDrawVert& v = dl->VtxBuffer[vi];
            for (int bi = 0; bi < buttonRects.Size; bi++) {
                const Rect& r = buttonRects[bi];
                if (v.pos.x > r.mn.x + 0.5f && v.pos.x < r.mx.x - 0.5f &&
                    v.pos.y > r.mn.y + 0.5f && v.pos.y < r.mx.y - 0.5f) {
                    v.col = (v.col & 0x00FFFFFFu);
                    break;
                }
            }
        }
    }

    // ---- Exit button strip (panel bottom) ----
    splitter.SetCurrentChannel(dl, 1);
    {
        float exitY = panelTopY + exitRelY;
        if (exitY + stripH > panelBotY) exitY = panelBotY - stripH;
        float exitX = panelLeft;
        float exitW = panelRight - panelLeft;
        ImVec2 exitMin(exitX, exitY);
        ImVec2 exitMax(exitX + exitW, exitY + stripH);

        ImGui::SetCursorScreenPos(exitMin);
        if (ImGui::InvisibleButton("##ExitBtn", ImVec2(exitW, stripH)))
            status.exitClicked = true;
        bool exitHover = ImGui::IsItemHovered();

        if (exitHover) {
            ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
            dl->AddRectFilled(exitMin, exitMax,
                schema::UI().resolveColor("var(--sidebar-hover)", IM_COL32(255, 255, 255, 25)),
                glassRounding, ImDrawFlags_RoundCornersBottom);
        }
        dl->AddLine(ImVec2(exitMin.x + glassRounding * 0.5f, exitMin.y),
                    ImVec2(exitMax.x - glassRounding * 0.5f, exitMin.y),
                    schema::UI().resolveColor("var(--sidebar-divider)", IM_COL32(255, 255, 255, 15)), 1.0f);

        ImU32 exitCol = exitHover ? Error() : schema::UI().resolveColor("var(--sidebar-icon)", IM_COL32(255, 255, 255, 60));
        float cx = exitX + exitW * 0.5f;
        float cy = exitY + stripH * 0.5f;

        if (showLabels) {
            ImFont* iconFont = Type().iconSmall();
            ImFont* font = Type().caption();
            const char* exitIcon = ICON_MD_EXIT_TO_APP;
            float eIconFsz = ScaledFontSize(iconFont);
            float eLblFsz  = ScaledFontSize(font);
            ImVec2 iconSz = iconFont->CalcTextSizeA(eIconFsz, 1000.0f, 0.0f, exitIcon);
            const char* exitLabel = TR("exit");
            ImVec2 labelSz = font->CalcTextSizeA(eLblFsz, 1000.0f, 0.0f, exitLabel);
            float totalW = iconSz.x + exitIconGap + labelSz.x;
            float startX = cx - totalW * 0.5f;
            dl->AddText(iconFont, eIconFsz,
                ImVec2(startX, cy - iconSz.y * 0.5f), exitCol, exitIcon);
            ImVec4 lc = ImGui::ColorConvertU32ToFloat4(exitCol);
            lc.w *= expandFrac;
            dl->AddText(font, eLblFsz,
                ImVec2(startX + iconSz.x + exitIconGap, cy - labelSz.y * 0.5f),
                ImGui::ColorConvertFloat4ToU32(lc), exitLabel);
        } else {
            ImFont* iconFont = Type().iconSmall();
            const char* exitIcon = ICON_MD_EXIT_TO_APP;
            float eIconFsz = ScaledFontSize(iconFont);
            ImVec2 iconSz = iconFont->CalcTextSizeA(eIconFsz, 1000.0f, 0.0f, exitIcon);
            dl->AddText(iconFont, eIconFsz,
                ImVec2(cx - iconSz.x * 0.5f, cy - iconSz.y * 0.5f), exitCol, exitIcon);
            if (exitHover) material::Tooltip("%s", TR("exit"));
        }
    }

    splitter.Merge(dl);

    ImGui::EndChild();
    return changed;
}

} // namespace ui
} // namespace dragonx
