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

#pragma once

#include "imgui.h"
#include "material/type.h"
#include "schema/ui_schema.h"
#include <algorithm>
#include <cmath>
#include <cstdio>

namespace dragonx {
namespace ui {

// Import material helpers into ui namespace for convenience
using material::Type;
using material::OverlineLabel;
using material::OnSurface;
using material::OnSurfaceMedium;
using material::OnSurfaceDisabled;
using material::Primary;
using material::Secondary;
using material::Error;

/**
 * @brief Centralized layout configuration for consistent UI across all tabs
 * 
 * Values are now driven by UISchema (JSON-configurable with hot-reload).
 * The k* names are preserved as inline accessors for backward compatibility.
 */
namespace Layout {

// ============================================================================
// DPI Scaling  (must be first — other accessors multiply by dpiScale())
// ============================================================================

// ============================================================================
// User Font Scale (accessibility, 1.0–3.0, persisted in Settings)
// ============================================================================

namespace detail {
    inline float& userFontScaleRef() { static float s = 1.0f; return s; }
    inline bool&  fontReloadNeededRef() { static bool s = false; return s; }
    inline float& fontAtlasScaleRef() { static float s = 1.0f; return s; }
}

/**
 * @brief Get the user's font scale preference (1.0–1.5).
 * Multiplied into font loading so glyphs render at the chosen size.
 */
inline float userFontScale() { return detail::userFontScaleRef(); }

/**
 * @brief Get the user font scale at which the font atlas was last built.
 * Used to compute FontScaleMain compensation during smooth slider drag.
 */
inline float fontAtlasScale() { return detail::fontAtlasScaleRef(); }

/**
 * @brief Record the font atlas scale after a rebuild.
 * Called from Typography::load() after the atlas is built.
 */
inline void setFontAtlasScale(float v) { detail::fontAtlasScaleRef() = v; }

/**
 * @brief Set the user's font scale and flag a font reload.
 * Called on slider release for a crisp atlas rebuild.
 */
inline void setUserFontScale(float v) {
    v = std::max(1.0f, std::min(1.5f, v));
    detail::userFontScaleRef() = v;
    // Compare against the atlas scale, not the live ref — setUserFontScaleVisual
    // may have already updated the ref during slider drag.
    if (v != detail::fontAtlasScaleRef()) {
        detail::fontReloadNeededRef() = true;
    }
}

/**
 * @brief Set the user's font scale WITHOUT flagging a font reload.
 * Called every frame while the slider is being dragged for smooth
 * visual scaling via FontScaleMain compensation.
 */
inline void setUserFontScaleVisual(float v) {
    v = std::max(1.0f, std::min(1.5f, v));
    detail::userFontScaleRef() = v;
}

/**
 * @brief Consume the pending font-reload flag (returns true once).
 */
inline bool consumeUserFontReload() {
    bool v = detail::fontReloadNeededRef();
    detail::fontReloadNeededRef() = false;
    return v;
}

// ============================================================================
// DPI Scaling  (must be after userFontScale — dpiScale includes it)
// ============================================================================

/**
 * @brief Get the raw hardware DPI scale factor (no user font scale).
 *
 * Returns the DPI scale set during typography initialization (e.g. 2.0 for
 * 200 % Windows scaling).  Use this when you need the pure hardware DPI
 * without the user's accessibility font scale applied.
 */
inline float rawDpiScale() {
    return dragonx::ui::material::Typography::instance().getDpiScale();
}

/**
 * @brief Get the effective DPI scale factor including user font scale.
 *
 * Returns rawDpiScale() * userFontScale().  At userFontScale() == 1.0
 * this is identical to the hardware DPI.  All pixel constants from TOML
 * are in *logical* pixels and should be multiplied by this factor so that
 * containers grow proportionally when the user increases font scale.
 */
inline float dpiScale() {
    return rawDpiScale() * userFontScale();
}

/**
 * @brief Scale a logical pixel value by the current DPI factor.
 *
 * Convenience wrapper: Scale(16.0f) returns 16 * dpiScale.
 */
inline float Scale(float px) {
    return px * dpiScale();
}

// ============================================================================
// Font Sizes (in pixels, before DPI scaling)
// ============================================================================
// These read from UISchema which loads layout from ui.toml.
// Editing res/themes/ui.toml will hot-reload these at runtime.

inline float kFontH1()        { return schema::UI().drawElement("fonts", "h1").sizeOr(24.0f); }
inline float kFontH2()        { return schema::UI().drawElement("fonts", "h2").sizeOr(20.0f); }
inline float kFontH3()        { return schema::UI().drawElement("fonts", "h3").sizeOr(18.0f); }
inline float kFontH4()        { return schema::UI().drawElement("fonts", "h4").sizeOr(16.0f); }
inline float kFontH5()        { return schema::UI().drawElement("fonts", "h5").sizeOr(14.0f); }
inline float kFontH6()        { return schema::UI().drawElement("fonts", "h6").sizeOr(14.0f); }
inline float kFontSubtitle1() { return schema::UI().drawElement("fonts", "subtitle1").sizeOr(16.0f); }
inline float kFontSubtitle2() { return schema::UI().drawElement("fonts", "subtitle2").sizeOr(14.0f); }
inline float kFontBody1()     { return schema::UI().drawElement("fonts", "body1").sizeOr(14.0f); }
inline float kFontBody2()     { return schema::UI().drawElement("fonts", "body2").sizeOr(12.0f); }
inline float kFontButton()    { return schema::UI().drawElement("fonts", "button").sizeOr(13.0f); }
inline float kFontButtonSm()  { return schema::UI().drawElement("fonts", "button-sm").sizeOr(10.0f); }
inline float kFontButtonLg()  { return schema::UI().drawElement("fonts", "button-lg").sizeOr(14.0f); }
inline float kFontCaption()   { return schema::UI().drawElement("fonts", "caption").sizeOr(11.0f); }
inline float kFontOverline()  { return schema::UI().drawElement("fonts", "overline").sizeOr(11.0f); }

// Global font scale
inline float kFontScale()     { return schema::UI().drawElement("fonts", "scale").sizeOr(1.0f); }

// ============================================================================
// Panel Sizing (responsive)
// ============================================================================

inline float kSummaryPanelMinWidth()    { return schema::UI().drawElement("panels", "summary").getFloat("min-width", 280.0f) * dpiScale(); }
inline float kSummaryPanelMaxWidth()    { return schema::UI().drawElement("panels", "summary").getFloat("max-width", 400.0f) * dpiScale(); }
inline float kSummaryPanelWidthRatio()  { return schema::UI().drawElement("panels", "summary").getFloat("width-ratio", 0.32f); }
inline float kSummaryPanelMinHeight()   { return schema::UI().drawElement("panels", "summary").getFloat("min-height", 200.0f) * dpiScale(); }
inline float kSummaryPanelMaxHeight()   { return schema::UI().drawElement("panels", "summary").getFloat("max-height", 350.0f) * dpiScale(); }
inline float kSummaryPanelHeightRatio() { return schema::UI().drawElement("panels", "summary").getFloat("height-ratio", 0.8f); }

inline float kSidePanelMinWidth()   { return schema::UI().drawElement("panels", "side-panel").getFloat("min-width", 280.0f) * dpiScale(); }
inline float kSidePanelMaxWidth()   { return schema::UI().drawElement("panels", "side-panel").getFloat("max-width", 450.0f) * dpiScale(); }
inline float kSidePanelWidthRatio() { return schema::UI().drawElement("panels", "side-panel").getFloat("width-ratio", 0.4f); }

// Overall content-column cap: the max width a tab's content occupies before it is centered in wider
// windows. <= 0 disables the cap so tab content fills ALL available horizontal width (the default —
// requested so large windows don't leave a big empty gutter on the right). Set a positive
// ui.toml [layout] content-max-width to re-enable a centered readable column.
inline float kContentMaxWidth() { return schema::UI().drawElement("layout", "content-max-width").sizeOr(0.0f) * dpiScale(); }

// Shared compose-card envelope for the Send + Receive tabs (and any tab wanting the same box): fill the
// available column up to the content-max-width cap, then center the leftover as margin. Both tabs MUST
// derive their card width/offset from this so the two envelopes stay byte-for-byte identical — they
// previously drifted (Send capped at 760dp, Receive at 860dp), so the Send card rendered narrower than
// Receive on any window wider than ~860dp. Returns {width, offsetX} in the same units as availW.
struct CardBox { float width; float offsetX; };
inline CardBox mainComposeCardBox(float availW) {
    float cap = kContentMaxWidth();
    float w = (cap > 0.0f) ? std::min(availW, cap) : availW;  // cap <= 0 -> fill full width
    return CardBox{ w, std::max(0.0f, (availW - w) * 0.5f) };
}

inline float kTableMinHeight()   { return schema::UI().drawElement("panels", "table").getFloat("min-height", 150.0f) * dpiScale(); }
inline float kTableHeightRatio() { return schema::UI().drawElement("panels", "table").getFloat("height-ratio", 0.45f); }

// ============================================================================
// Spacing
// ============================================================================

inline float kSectionSpacing()  { return schema::UI().drawElement("spacing", "section").sizeOr(16.0f) * dpiScale(); }
inline float kItemSpacing()     { return schema::UI().drawElement("spacing", "item").sizeOr(8.0f) * dpiScale(); }
inline float kLabelValueGap()   { return schema::UI().drawElement("spacing", "label-value").sizeOr(4.0f) * dpiScale(); }
inline float kSeparatorGap()    { return schema::UI().drawElement("spacing", "separator").sizeOr(20.0f) * dpiScale(); }

// ============================================================================
// Per-frame cache — populated once via beginFrame(), avoids repeated
// schema hash-map lookups for the hottest accessors.
// ============================================================================

namespace detail {

struct FrameCache {
    uint32_t gen = 0;            // schema generation when last populated
    float    rawDp  = 1.0f;     // rawDpiScale() snapshot
    float    dp     = 1.0f;     // dpiScale() snapshot

    // Spacing tokens (raw, unscaled)
    float spXs  = 2.0f;
    float spSm  = 4.0f;
    float spMd  = 8.0f;
    float spLg  = 12.0f;
    float spXl  = 16.0f;
    float spXxl = 24.0f;

    // Responsive scale config
    float refW   = 1200.0f;
    float refH   = 700.0f;
    float minHS  = 0.5f;
    float maxHS  = 1.5f;
    float minVS  = 0.5f;
    float maxVS  = 1.4f;
    float minDen = 0.6f;
    float maxDen = 1.2f;

    // Breakpoint thresholds (raw, unscaled)
    float compactW  = 500.0f;
    float compactH  = 450.0f;
    float expandedW = 900.0f;
    float expandedH = 750.0f;

    // Glass / card helpers (raw, unscaled)
    float glassRnd = 8.0f;
    float cardPad  = 12.0f;
    float cardGap  = 8.0f;
};

inline FrameCache& frameCache() { static FrameCache c; return c; }

} // namespace detail

/**
 * @brief Refresh the per-frame layout cache.
 *
 * Call once per frame (e.g., from App::preFrame) BEFORE any rendering.
 * Reads all high-frequency TOML values into fast statics.  Invalidated
 * automatically when the schema generation changes (hot-reload).
 */
inline void beginFrame() {
    auto& c = detail::frameCache();
    uint32_t g = schema::UI().generation();
    // Also capture DPI each frame (can change on display-scale events)
    float curRawDp = rawDpiScale();
    float curDp    = dpiScale();
    if (g == c.gen && curRawDp == c.rawDp && curDp == c.dp) return;
    c.gen   = g;
    c.rawDp = curRawDp;
    c.dp    = curDp;

    const auto& S = schema::UI();

    // Spacing tokens
    c.spXs  = S.drawElement("spacing-tokens", "xs").sizeOr(2.0f);
    c.spSm  = S.drawElement("spacing-tokens", "sm").sizeOr(4.0f);
    c.spMd  = S.drawElement("spacing-tokens", "md").sizeOr(8.0f);
    c.spLg  = S.drawElement("spacing-tokens", "lg").sizeOr(12.0f);
    c.spXl  = S.drawElement("spacing-tokens", "xl").sizeOr(16.0f);
    c.spXxl = S.drawElement("spacing-tokens", "xxl").sizeOr(24.0f);

    // Responsive config
    c.refW   = S.drawElement("responsive", "ref-width").sizeOr(1200.0f);
    c.refH   = S.drawElement("responsive", "ref-height").sizeOr(700.0f);
    c.minHS  = S.drawElement("responsive", "min-h-scale").sizeOr(0.5f);
    c.maxHS  = S.drawElement("responsive", "max-h-scale").sizeOr(1.5f);
    c.minVS  = S.drawElement("responsive", "min-v-scale").sizeOr(0.5f);
    c.maxVS  = S.drawElement("responsive", "max-v-scale").sizeOr(1.4f);
    c.minDen = S.drawElement("responsive", "min-density").sizeOr(0.6f);
    c.maxDen = S.drawElement("responsive", "max-density").sizeOr(1.2f);

    // Breakpoints
    c.compactW  = S.drawElement("responsive", "compact-width").sizeOr(500.0f);
    c.compactH  = S.drawElement("responsive", "compact-height").sizeOr(450.0f);
    c.expandedW = S.drawElement("responsive", "expanded-width").sizeOr(900.0f);
    c.expandedH = S.drawElement("responsive", "expanded-height").sizeOr(750.0f);

    // Glass / card
    c.glassRnd = S.drawElement("responsive", "glass-rounding").sizeOr(8.0f);
    c.cardPad  = S.drawElement("responsive", "card-inner-padding").sizeOr(12.0f);
    c.cardGap  = S.drawElement("responsive", "card-gap").sizeOr(8.0f);
}

// ============================================================================
// Layout Tier (responsive breakpoints)
// ============================================================================

/**
 * @brief Three-tier layout system based on content area dimensions.
 *
 * Compact  — narrow/short window: single-column, collapsed elements
 * Normal   — default layout
 * Expanded — large window: extra spacing, optional extra columns
 */
enum class LayoutTier { Compact, Normal, Expanded };

/**
 * @brief Determine the current layout tier from content area dimensions.
 * Call after ImGui::BeginChild for the content area, or pass explicit avail.
 */
inline LayoutTier currentTier() {
    const auto& c = detail::frameCache();
    float dp = c.dp;
    float cw = c.compactW * dp;
    float ch = c.compactH * dp;
    float ew = c.expandedW * dp;
    float eh = c.expandedH * dp;
    ImVec2 avail = ImGui::GetContentRegionAvail();
    if (avail.x < cw  || avail.y < ch)  return LayoutTier::Compact;
    if (avail.x > ew && avail.y > eh) return LayoutTier::Expanded;
    return LayoutTier::Normal;
}

inline LayoutTier currentTier(float availW, float availH) {
    const auto& c = detail::frameCache();
    float dp = c.dp;
    float cw = c.compactW * dp;
    float ch = c.compactH * dp;
    float ew = c.expandedW * dp;
    float eh = c.expandedH * dp;
    if (availW < cw  || availH < ch)  return LayoutTier::Compact;
    if (availW > ew && availH > eh) return LayoutTier::Expanded;
    return LayoutTier::Normal;
}

// ============================================================================
// Responsive Scale Factors
// ============================================================================

/**
 * @brief Horizontal scale factor relative to reference width (default 1200px).
 *
 * The scale is decomposed into a *logical* portion (responsive to window
 * size, clamped to the configured range) multiplied by the display DPI
 * factor.  This ensures a DPI transition produces the same logical scale
 * while emitting physical-pixel results.
 */
inline float hScale(float availWidth) {
    const auto& c = detail::frameCache();
    float rw   = c.refW * c.rawDp;
    float logical = std::clamp(availWidth / rw, c.minHS, c.maxHS);
    return logical * c.dp;
}

inline float hScale() {
    return hScale(ImGui::GetContentRegionAvail().x);
}

/**
 * @brief Vertical scale factor relative to reference height (default 700px).
 *
 * Same decomposition as hScale — logical clamp × DPI.
 */
inline float vScale(float availHeight) {
    const auto& c = detail::frameCache();
    float rh   = c.refH * c.rawDp;
    float logical = std::clamp(availHeight / rh, c.minVS, c.maxVS);
    return logical * c.dp;
}

inline float vScale() {
    return vScale(ImGui::GetContentRegionAvail().y);
}

/**
 * @brief Density scale factor for spacing tokens.
 *
 * Logical portion is clamped, then multiplied by DPI so pixel spacing
 * values scale proportionally with fonts and style.
 */
inline float densityScale(float availHeight) {
    const auto& c = detail::frameCache();
    float rh     = c.refH * c.rawDp;
    float logical = std::clamp(availHeight / rh, c.minDen, c.maxDen);
    return logical * c.dp;
}

inline float densityScale() {
    return densityScale(ImGui::GetContentRegionAvail().y);
}

// ============================================================================
// Spacing Tokens (density-scaled)
// ============================================================================

/** @brief Get spacing token scaled by current density. */
inline float spacingXs()  { return detail::frameCache().spXs  * densityScale(); }
inline float spacingSm()  { return detail::frameCache().spSm  * densityScale(); }
inline float spacingMd()  { return detail::frameCache().spMd  * densityScale(); }
inline float spacingLg()  { return detail::frameCache().spLg  * densityScale(); }
inline float spacingXl()  { return detail::frameCache().spXl  * densityScale(); }
inline float spacingXxl() { return detail::frameCache().spXxl * densityScale(); }

/** @brief Get raw (unscaled) spacing token. */
inline float spacingXsRaw()  { return detail::frameCache().spXs; }
inline float spacingSmRaw()  { return detail::frameCache().spSm; }
inline float spacingMdRaw()  { return detail::frameCache().spMd; }
inline float spacingLgRaw()  { return detail::frameCache().spLg; }
inline float spacingXlRaw()  { return detail::frameCache().spXl; }
inline float spacingXxlRaw() { return detail::frameCache().spXxl; }

// ============================================================================
// Responsive Globals Helpers
// ============================================================================

/** @brief Default glass panel rounding (8.0 default). */
inline float glassRounding() { return detail::frameCache().glassRnd * dpiScale(); }

/** @brief Default card inner padding (12.0 default). */
inline float cardInnerPadding() { return detail::frameCache().cardPad * dpiScale(); }

/** @brief Default card gap (8.0 default). */
inline float cardGap() { return detail::frameCache().cardGap * dpiScale(); }

/**
 * @brief Compute a responsive card height from a base value.
 * @param base Design-time card height (e.g. 110.0f) in logical pixels
 * @param vs   Vertical scale factor (from vScale(), already DPI-scaled)
 * @return Scaled height with a DPI-aware floor of base * 0.4 * dpiScale
 */
inline float cardHeight(float base, float vs) {
    return std::max(base * 0.4f * dpiScale(), base * vs);
}

/**
 * @brief Compute a column offset as a ratio of available width.
 * Replaces hardcoded "cx + 100" patterns.
 * @param ratio Fraction of available width (e.g. 0.12)
 * @param availW Available width
 */
inline float columnOffset(float ratio, float availW) {
    return availW * ratio;
}

// ============================================================================
// Dialogs
// ============================================================================

inline float kDialogDefaultWidth()     { return schema::UI().drawElement("dialog", "width-default").sizeOr(480.0f) * dpiScale(); }
inline float kDialogLargeWidth()       { return schema::UI().drawElement("dialog", "width-lg").sizeOr(600.0f) * dpiScale(); }
inline float kDialogExtraLargeWidth()  { return schema::UI().drawElement("dialog", "width-xl").sizeOr(660.0f) * dpiScale(); }
inline float kDialogMinWidth()         { return schema::UI().drawElement("dialog", "min-width").sizeOr(280.0f) * dpiScale(); }
inline float kDialogFormWidth()        { return schema::UI().drawElement("dialog", "form-width").sizeOr(400.0f) * dpiScale(); }
inline float kDialogActionWidth()      { return schema::UI().drawElement("dialog", "action-width").sizeOr(100.0f) * dpiScale(); }
inline float kDialogActionGap()        { return schema::UI().drawElement("dialog", "action-gap").sizeOr(8.0f) * dpiScale(); }
inline float kDialogMaxHeightRatio()   { return schema::UI().drawElement("dialog", "max-height-ratio").sizeOr(0.94f); }
inline float kDialogCompactBottomRatio() { return schema::UI().drawElement("dialog", "compact-bottom-ratio").sizeOr(0.64f); }

// ============================================================================
// Buttons
// ============================================================================

inline float kButtonMinWidth()      { return schema::UI().drawElement("button", "min-width").sizeOr(180.0f) * dpiScale(); }
inline float kButtonStandardWidth() { return schema::UI().drawElement("button", "width").sizeOr(140.0f) * dpiScale(); }
inline float kButtonLargeWidth()    { return schema::UI().drawElement("button", "width-lg").sizeOr(160.0f) * dpiScale(); }
inline float kButtonHeight()        { float h = schema::UI().drawElement("button", "height").sizeOr(0.0f); return h > 0.0f ? h * dpiScale() : 0.0f; }

// ============================================================================
// Input Fields
// ============================================================================

inline float kInputMinWidth()         { return schema::UI().drawElement("input", "min-width").sizeOr(150.0f) * dpiScale(); }
inline float kInputMediumWidth()      { return schema::UI().drawElement("input", "width-md").sizeOr(200.0f) * dpiScale(); }
inline float kInputLargeWidth()       { return schema::UI().drawElement("input", "width-lg").sizeOr(300.0f) * dpiScale(); }
inline float kSearchFieldWidthRatio() { return schema::UI().drawElement("input", "search-width-ratio").sizeOr(0.30f); }

// ============================================================================
// Status Bar
// ============================================================================

inline float kStatusBarHeight()  { float dp = dpiScale(); auto h = schema::UI().window("components.status-bar", "window").height; return (h > 0 ? h : 60.0f) * dp; }
inline float kStatusBarPadding() { float dp = dpiScale(); auto w = schema::UI().window("components.status-bar", "window"); return (w.padding[0] > 0 ? w.padding[0] : 8.0f) * dp; }

// ============================================================================
// Helper Functions
// ============================================================================

/**
 * @brief Calculate responsive width with min/max bounds
 * @param availWidth Available width from GetContentRegionAvail().x
 * @param ratio Ratio of available width (0.0-1.0)
 * @param minWidth Minimum width in pixels
 * @param maxWidth Maximum width in pixels
 */
inline float responsiveWidth(float availWidth, float ratio, float minWidth, float maxWidth) {
    return std::max(minWidth, std::min(maxWidth, availWidth * ratio));
}

/**
 * @brief Calculate responsive height with min/max bounds
 */
inline float responsiveHeight(float availHeight, float ratio, float minHeight, float maxHeight) {
    return std::max(minHeight, std::min(maxHeight, availHeight * ratio));
}

/**
 * @brief Get summary panel dimensions
 */
inline ImVec2 getSummaryPanelSize() {
    ImVec2 avail = ImGui::GetContentRegionAvail();
    return ImVec2(
        responsiveWidth(avail.x, kSummaryPanelWidthRatio(), kSummaryPanelMinWidth(), kSummaryPanelMaxWidth()),
        responsiveHeight(avail.y, kSummaryPanelHeightRatio(), kSummaryPanelMinHeight(), kSummaryPanelMaxHeight())
    );
}

/**
 * @brief Get side panel width (height fills available)
 */
inline float getSidePanelWidth() {
    float avail = ImGui::GetContentRegionAvail().x;
    return responsiveWidth(avail, kSidePanelWidthRatio(), kSidePanelMinWidth(), kSidePanelMaxWidth());
}

/**
 * @brief Get table height for split view (two tables)
 */
inline float getTableHeight() {
    float avail = ImGui::GetContentRegionAvail().y;
    return responsiveHeight(avail, kTableHeightRatio(), kTableMinHeight(), avail * 0.6f);
}

/**
 * @brief Get remaining height (for second table/section)
 */
inline float getRemainingHeight(float reserveSpace) {
    float avail = ImGui::GetContentRegionAvail().y;
    return std::max(schema::UI().drawElement("panels", "table").getFloat("min-remaining", 100.0f) * dpiScale(), avail - reserveSpace);
}
inline float getRemainingHeight() {
    return getRemainingHeight(schema::UI().drawElement("panels", "table").getFloat("default-reserve", 30.0f) * dpiScale());
}

/**
 * @brief Get search/filter input width
 */
inline float getSearchWidth() {
    float avail = ImGui::GetContentRegionAvail().x;
    return std::min(kInputLargeWidth(), avail * kSearchFieldWidthRatio());
}

/**
 * @brief Calculate position for right-aligned buttons
 * @param buttonCount Number of buttons
 * @param buttonWidth Width of each button
 * @param spacing Spacing between buttons
 */
inline float getRightButtonsPos(int buttonCount, float buttonWidth, float spacing) {
    float dp = dpiScale();
    float margin = schema::UI().drawElement("button", "right-align-margin").sizeOr(16.0f) * dp;
    float minPos = schema::UI().drawElement("button", "right-align-min-pos").sizeOr(200.0f) * dp;
    float totalWidth = buttonCount * buttonWidth + (buttonCount - 1) * spacing + margin;
    return std::max(minPos, ImGui::GetWindowWidth() - totalWidth);
}
inline float getRightButtonsPos(int buttonCount, float buttonWidth) {
    return getRightButtonsPos(buttonCount, buttonWidth, schema::UI().drawElement("button", "right-align-gap").sizeOr(8.0f) * dpiScale());
}

// ============================================================================
// Shared Card Height (send/receive tab parity)
// ============================================================================

/**
 * @brief Compute the target glass card height for send/receive tabs.
 *
 * Both tabs call this with the same formW + vs so their cards match.
 * The height models the receive tab's QR-code-driven layout:
 *   topPad + totalQrSize + innerGap + actionBtnH + bottomPad
 *
 * @param formW  Total card / form width in pixels
 * @param vs     Vertical scale factor (from vScale())
 */
inline float mainCardTargetH(float formW, float vs) {
    float dp       = dpiScale();
    float pad      = spacingLg();
    float innerW   = formW - pad * 2;
    float qrColW   = innerW * 0.35f;
    float qrPad    = spacingMd();
    float maxQrSz  = std::min(std::max(0.0f, qrColW - qrPad * 2), 280.0f * dp);
    float qrSize   = std::max(100.0f * dp, maxQrSz);
    float totalQr  = qrSize + qrPad * 2;
    float innerGap = spacingLg();
    float btnH     = std::max(26.0f * dp, 30.0f * vs);
    return pad + totalQr + innerGap + btnH + pad;
}

// ============================================================================
// Section Budget Allocator
// ============================================================================

/**
 * @brief Proportional height-budget allocator for tab content.
 *
 * Each tab creates a SectionBudget from the available content height and
 * allocates fractions to its sections.  Sections receive a proportional
 * share of the total height clamped to [minPx, maxPx], guaranteeing
 * that all content fits without scrolling at any window size.
 *
 * Usage:
 *   SectionBudget b(ImGui::GetContentRegionAvail().y);
 *   float heroH  = b.allocate(0.13f, 55.0f);
 *   float listH  = b.rest(60.0f);  // whatever is left
 */
struct SectionBudget {
    float total;      ///< Total available height passed at construction
    float remaining;  ///< Decrements as sections are allocated

    explicit SectionBudget(float avail)
        : total(avail), remaining(avail) {}

    /**
     * @brief Allocate a fraction of the total budget.
     * @param fraction  Fraction of *total* (e.g. 0.12 = 12%)
     * @param minPx     Minimum pixel height in logical pixels (auto DPI-scaled)
     * @param maxPx     Maximum pixel height in logical pixels (auto DPI-scaled, default unlimited)
     * @return The allocated height in physical pixels.
     */
    float allocate(float fraction, float minPx, float maxPx = FLT_MAX) {
        float dp = dpiScale();
        float scaledMin = minPx * dp;
        float scaledMax = (maxPx >= FLT_MAX * 0.5f) ? FLT_MAX : maxPx * dp;
        float h = std::clamp(total * fraction, scaledMin, scaledMax);
        remaining -= h;
        if (remaining < 0.0f) remaining = 0.0f;
        return h;
    }

    /**
     * @brief Allocate whatever height remains (for the final section).
     * @param minPx  Minimum logical pixels guaranteed even if budget is exhausted.
     * @return Remaining height (at least minPx * dpiScale).
     */
    float rest(float minPx = 0.0f) {
        return std::max(minPx * dpiScale(), remaining);
    }
};

} // namespace Layout

// ============================================================================
// Convenience Macros/Functions for Common Patterns
// ============================================================================

/**
 * @brief Begin a summary panel with responsive sizing
 */
inline bool BeginSummaryPanel(const char* id) {
    ImVec2 size = Layout::getSummaryPanelSize();
    return ImGui::BeginChild(id, size, true);
}

/**
 * @brief Begin a side panel with responsive width
 */
inline bool BeginSidePanel(const char* id) {
    float width = Layout::getSidePanelWidth();
    return ImGui::BeginChild(id, ImVec2(width, 0), true);
}

/**
 * @brief Begin a content panel that fills remaining space
 */
inline bool BeginContentPanel(const char* id) {
    return ImGui::BeginChild(id, ImVec2(0, 0), true);
}

/**
 * @brief Add standard section header with separator
 */
inline void SectionHeader(const char* label) {
    OverlineLabel(label);
    ImGui::Spacing();
    ImGui::Separator();
    ImGui::Spacing();
}

/**
 * @brief Add spacing between sections
 */
inline void SectionSpacing() {
    ImGui::Spacing();
    ImGui::Separator();
    ImGui::Spacing();
}

/**
 * @brief Render a label-value pair
 */
inline void LabelValue(const char* label, const char* value) {
    Type().textColored(material::TypeStyle::Caption, OnSurfaceMedium(), label);
    ImGui::Text("%s", value);
}

/**
 * @brief Render a label-value pair with colored value
 */
inline void LabelValueColored(const char* label, const char* value, ImU32 color) {
    Type().textColored(material::TypeStyle::Caption, OnSurfaceMedium(), label);
    ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(color), "%s", value);
}

/**
 * @brief Render a balance display (amount + ticker)
 */
inline void BalanceDisplay(double amount, const char* ticker, ImU32 color = 0) {
    char buf[64];
    snprintf(buf, sizeof(buf), "%.8f", amount);
    
    if (color != 0) {
        ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(color), "%s", buf);
    } else {
        ImGui::Text("%s", buf);
    }
    ImGui::SameLine();
    Type().textColored(material::TypeStyle::Body2, OnSurfaceMedium(), ticker);
}

} // namespace ui
} // namespace dragonx
