#include "ui.h"
#include "../core/logger.h"
#include "../memory/memory.h"
#include "../memory/stack_alloc.h"
#include "../util/object_pool.h"
#include "../util/string.h"
#include "../util/array.h"

#include <raymath.h>
#include <stb_ds.h>

typedef struct BzUINode {
    BzUINode *parent;
    // Children
    BzUINode *first;
    BzUINode *last;
    // Siblings
    BzUINode *prev;
    BzUINode *next;

    // Key+generation info
    BzUIKey key;
    u64     lastFrame;

    // Per-frame info provided by builders
    BzUIFlags   flags;
    BzUILayout  layout;

    i32 backgroundStyleIdx;
    i32 boxShadowStyleIdx;
    i32 textStyleIdx;
    i32 textShadowStyleIdx;
    i32 borderStyleIdx;
    i32 spriteStyleIdx;

    BzUISize semanticSize[BZ_UI_AXIS_COUNT];
    f32      padding[BZ_UI_AXIS_COUNT * 2];
    f32      margin[BZ_UI_AXIS_COUNT * 2];

    // recomputed every frame
    f32 computedPosition[BZ_UI_AXIS_COUNT];
    f32 computedSize[BZ_UI_AXIS_COUNT];

    BzUIInteraction interaction;
    bool            canInteract;
} BzUINode;

typedef struct BzUI {
    struct {
        BzUIKey key;
        BzUINode *value;
    } *nodeMap;
    BzObjectPool *nodePool;
    BzUINode    **nodeStack;
    BzUINode     *root;

    BzUIBackgroundStyle *backgroundStyles;
    BzUIBoxShadowStyle  *boxShadowStyles;
    BzUITextStyle       *textStyles;
    BzUITextShadowStyle *textShadowStyles;
    BzUIBorderStyle     *borderStyles;
    BzUISpriteStyle     *spriteStyles;

    // TODO: Use arena (when implemented) instead of stack allocator.
    BzStackAlloc strArena;

    u64 currFrame;
    u64 keyIdCount; // Per-frame

    bool captureMouse;
    bool captureKeyboard;
    bool capturedMouse;
    bool capturedKeyboard;
} BzUI;

BzUIKey bzUIKeyNull() {
    return 0;
}
BzUIKey bzUIKeyFromString(const char *str) {
    BZ_ASSERT(str);
    return bzStringDefaultHash(str);
}

static void bzUINodeClearLinks(BzUINode *node) {
    BZ_ASSERT(node);
    node->parent = NULL;
    node->first = NULL;
    node->last = NULL;
    node->prev = NULL;
    node->next = NULL;
}
static void bzUINodeClearStyles(BzUINode *node) {
    BZ_ASSERT(node);
    node->backgroundStyleIdx = -1;
    node->boxShadowStyleIdx  = -1;
    node->textStyleIdx       = -1;
    node->textShadowStyleIdx = -1;
    node->borderStyleIdx     = -1;
    node->spriteStyleIdx     = -1;
}

BzUI *bzUICreate() {
    BzUI *ui = bzAlloc(sizeof(*ui));

    ui->nodeMap = NULL;
    hmdefault(ui->nodeMap, NULL);
    ui->nodePool = bzObjectPoolCreate(&(BzObjectPoolDesc) {
        .objectsPerPage=128,
        .objectSize=sizeof(BzUINode),
    });
    ui->nodeStack = bzArrayCreate(BzUINode *, 10);

    ui->root = bzObjectPool(ui->nodePool);
    bzMemSet(ui->root, 0, sizeof(*ui->root));
    ui->root->key = bzUIKeyFromString("##root");

    ui->backgroundStyles = bzArrayCreate(BzUIBackgroundStyle, 10);
    ui->boxShadowStyles  = bzArrayCreate(BzUIBoxShadowStyle, 10);
    ui->textStyles       = bzArrayCreate(BzUITextStyle, 10);
    ui->textShadowStyles = bzArrayCreate(BzUITextShadowStyle, 10);
    ui->borderStyles     = bzArrayCreate(BzUIBorderStyle, 10);
    ui->spriteStyles     = bzArrayCreate(BzUISpriteStyle, 10);

    ui->currFrame = 0;

    // 10kb should be enough
    ui->strArena = bzStackAllocCreate(10240);

    hmput(ui->nodeMap, ui->root->key, ui->root);

    ui->captureMouse = true;
    ui->captureKeyboard = true;
    return ui;
}
void bzUIDestroy(BzUI *ui) {
    hmfree(ui->nodeMap);
    bzObjectPoolDestroy(ui->nodePool);
    ui->nodePool = NULL;
    bzArrayDestroy(ui->nodeStack);

    bzArrayDestroy(ui->backgroundStyles);
    bzArrayDestroy(ui->boxShadowStyles);
    bzArrayDestroy(ui->textStyles);
    bzArrayDestroy(ui->textShadowStyles);
    bzArrayDestroy(ui->borderStyles);
    bzArrayDestroy(ui->spriteStyles);

    bzStackAllocDestroy(&ui->strArena);

    bzFree(ui);
}

bool bzUICapturedMouse(BzUI *ui) {
    return ui->capturedMouse;
}
bool bzUICapturedKeyboard(BzUI *ui) {
    return ui->capturedKeyboard;
}

bool bzUIGetCaptureMouse(BzUI *ui) {
    return ui->captureMouse;
}
bool bzUIGetCaptureKeyboard(BzUI *ui) {
    return ui->captureKeyboard;
}

void bzUISetCaptureMouse(BzUI *ui, bool state) {
    ui->captureMouse = state;
}
void bzUISetCaptureKeyboard(BzUI *ui, bool state) {
    ui->captureKeyboard = state;
}

void bzUIBegin(BzUI *ui, i32 width, i32 height) {
    bzArrayClear(ui->nodeStack);
    bzArrayPush(ui->nodeStack, ui->root);

    bzUINodeClearLinks(ui->root);
    bzUINodeClearStyles(ui->root);

    ui->root->semanticSize[BZ_UI_AXIS_X] = (BzUISize){
        .kind = BZ_UI_SIZE_PIXELS,
        .value = width
    };
    ui->root->semanticSize[BZ_UI_AXIS_Y] = (BzUISize) {
        .kind = BZ_UI_SIZE_PIXELS,
        .value = height
    };

    bzArrayClear(ui->backgroundStyles);
    bzArrayClear(ui->boxShadowStyles);
    bzArrayClear(ui->textStyles);
    bzArrayClear(ui->textShadowStyles);
    bzArrayClear(ui->borderStyles);
    bzArrayClear(ui->spriteStyles);

    bzStackAllocReset(&ui->strArena);

    ui->root->lastFrame = ui->currFrame;
    ui->keyIdCount = 1;
}

static Rectangle getNodeRect(const BzUINode *node) {
    return (Rectangle) {
        .x = node->computedPosition[BZ_UI_AXIS_X],
        .y = node->computedPosition[BZ_UI_AXIS_Y],
        .width = node->computedSize[BZ_UI_AXIS_X],
        .height = node->computedSize[BZ_UI_AXIS_Y],
    };
}
static void calculateAxisSizePreorder(const BzUI *ui, const BzUIAxis axis, BzUINode *node) {
    f32 compSize = 0;
    switch (node->semanticSize[axis].kind) {
        case BZ_UI_SIZE_PIXELS:
            compSize = node->semanticSize[axis].value;
            break;
        case BZ_UI_SIZE_FIT: {
            BzUITextStyle style = bzUIGetTextStyle(ui, node);
            BZ_ASSERT(style.text);
            Vector2 size = MeasureTextEx(style.font, style.text,
                                         style.fontSize, style.fontSpacing);
            compSize = (axis == BZ_UI_AXIS_X) ? size.x : size.y;
            break;
        }
        case BZ_UI_SIZE_REL_PARENT:
            BZ_ASSERT(node->parent);
            compSize = node->parent->computedSize[axis] * node->semanticSize[axis].value;
            break;
        case BZ_UI_SIZE_AS_PARENT:
            BZ_ASSERT(node->parent);
            compSize = node->parent->computedSize[axis];
            break;
        case BZ_UI_SIZE_NULL:
        default:
            return;
    }
    if (node->computedSize[axis] != compSize) {
        node->canInteract = false;
    }
    node->computedSize[axis] = compSize;
}
static void calculateAxisSizePostorder(const BzUI *ui, const BzUIAxis axis, BzUINode *node) {
    f32 compSize = 0;
    switch (node->semanticSize[axis].kind) {
        case BZ_UI_SIZE_CHILD_SUM:
            BZ_ASSERT(node->first);
            for (BzUINode *child = node->first; child; child = child->next) {
                compSize += child->computedSize[axis] + child->margin[axis] + child->margin[axis + 2];
            }
            break;
        case BZ_UI_SIZE_CHILD_MAX:
            BZ_ASSERT(node->first);
            for (BzUINode *child = node->first; child; child = child->next) {
                f32 childSize = child->computedSize[axis] + child->margin[axis] + child->margin[axis + 2];
                compSize = BZ_MAX(compSize, childSize);
            }
            break;
        default:
            return;
    }
    if (node->computedSize[axis] != compSize) {
        node->canInteract = false;
        node->computedSize[axis] = compSize;
    }
}
static void calculateSizes(const BzUI *ui, BzUINode *node) {
    // Border thickness counts as margin
    if (node->flags & BZ_UI_DRAW_BORDER) {
        BzUIBorderStyle style = bzUIGetBorderStyle(ui, node);
        node->margin[BZ_UI_AXIS_X] += style.thickness * 2;
        node->margin[BZ_UI_AXIS_Y] += style.thickness * 2;
    }

    calculateAxisSizePreorder(ui, BZ_UI_AXIS_X, node);
    calculateAxisSizePreorder(ui, BZ_UI_AXIS_Y, node);

    node->computedSize[BZ_UI_AXIS_X] += node->padding[BZ_UI_AXIS_X] + node->padding[BZ_UI_AXIS_X + 2];
    node->computedSize[BZ_UI_AXIS_Y] += node->padding[BZ_UI_AXIS_Y] + node->padding[BZ_UI_AXIS_Y + 2];

    for (BzUINode *child = node->first; child; child = child->next) {
        calculateSizes(ui, child);
    }

    calculateAxisSizePostorder(ui, BZ_UI_AXIS_X, node);
    calculateAxisSizePostorder(ui, BZ_UI_AXIS_Y, node);
}

static void calculatePositions(BzUI *ui, BzUINode *node, f32 x, f32 y);
static void calculatePositionsFlexBox(BzUI *ui, BzUINode *node) {
    BZ_ASSERT(node->layout.type == BZ_UI_LAYOUT_FLEX_BOX);
    BzUIFlags flags = node->layout.flags;

    f32 totalMainAxisSize = 0.0f;

    const i32 MAIN_AXIS  = (flags & BZ_UI_FLEX_DIR_COLUMN) ? BZ_UI_AXIS_Y : BZ_UI_AXIS_X;
    const i32 CROSS_AXIS = (flags & BZ_UI_FLEX_DIR_COLUMN) ? BZ_UI_AXIS_X : BZ_UI_AXIS_Y;

    i32 numChildren = 0;

    for (BzUINode *child = node->first; child; child = child->next) {
        totalMainAxisSize += child->computedSize[MAIN_AXIS] +
                             child->margin[MAIN_AXIS] +
                             child->margin[MAIN_AXIS + 2];
        numChildren++;
    }

    f32 mainAxisTotalSpacing = node->computedSize[MAIN_AXIS] - totalMainAxisSize;
    mainAxisTotalSpacing = BZ_MAX(0, mainAxisTotalSpacing);

    // Default: JUSTIFY_START
    f32 mainAxisOffset = 0.0f;
    f32 mainAxisStep   = 0.0f;
    if (flags & BZ_UI_FLEX_JUSTIFY_CENTER) {
        mainAxisOffset = mainAxisTotalSpacing * 0.5f;
    } else if (flags & BZ_UI_FLEX_JUSTIFY_END) {
        mainAxisOffset = mainAxisTotalSpacing;
    } else if (flags & BZ_UI_FLEX_JUSTIFY_SPACE_BETWEEN) {
        mainAxisStep = mainAxisTotalSpacing / BZ_MAX(1, numChildren - 1);
    } else if (flags & BZ_UI_FLEX_JUSTIFY_SPACE_AROUND) {
        mainAxisStep   = mainAxisTotalSpacing / BZ_MAX(1, numChildren);
        mainAxisOffset = mainAxisStep * 0.5f;
    } else if (flags & BZ_UI_FLEX_JUSTIFY_SPACE_EVENLY) {
        mainAxisStep   = mainAxisTotalSpacing / (numChildren + 1);
        mainAxisOffset = mainAxisStep;
    }

    f32 axisOffset[BZ_UI_AXIS_COUNT];
    axisOffset[MAIN_AXIS] = node->computedPosition[MAIN_AXIS] + mainAxisOffset;

    for (BzUINode *child = node->first; child; child = child->next) {
        // Default: ALIGN_START
        axisOffset[CROSS_AXIS] = node->computedPosition[CROSS_AXIS];
        if (flags & BZ_UI_FLEX_ALIGN_CENTER) {
            axisOffset[CROSS_AXIS] += (node->computedSize[CROSS_AXIS] - child->computedSize[CROSS_AXIS]) * 0.5f;
        } else if (flags & BZ_UI_FLEX_ALIGN_END) {
            axisOffset[CROSS_AXIS] += node->computedSize[CROSS_AXIS] - child->computedSize[CROSS_AXIS];
        }

        axisOffset[MAIN_AXIS] += child->margin[MAIN_AXIS];
        calculatePositions(ui, child, axisOffset[BZ_UI_AXIS_X], axisOffset[BZ_UI_AXIS_Y]);
        axisOffset[MAIN_AXIS] += child->margin[MAIN_AXIS + 2];
        axisOffset[MAIN_AXIS] += mainAxisStep;
        axisOffset[MAIN_AXIS] += child->computedSize[MAIN_AXIS];
    }

}
static void calculatePositions(BzUI *ui, BzUINode *node, f32 x, f32 y) {
    node->computedPosition[BZ_UI_AXIS_X] = x;
    node->computedPosition[BZ_UI_AXIS_Y] = y;

    switch (node->layout.type) {
        case BZ_UI_LAYOUT_FLEX_BOX:
            calculatePositionsFlexBox(ui, node);
            break;
        default:
            for (BzUINode *child = node->first; child; child = child->next) {
                calculatePositions(ui, child, x, y);
            }
            break;
    }
}

static void removeNode(BzUI *ui, BzUINode *node) {
    BZ_ASSERT(ui);
    BZ_ASSERT(node);

    BzUINode *child = node->first;
    while (child != NULL) {
        removeNode(ui, child);
        hmdel(ui->nodeMap, child->key);
        child = child->next;
    }
}
static void pruneStale(BzUI *ui, BzUINode *node) {
    BZ_ASSERT(node);
    BzUINode *child = node->first;

    BzUINode *first = NULL;
    BzUINode *last = NULL;

    while (child != NULL) {
        if (child->lastFrame != ui->currFrame) {
            BzUINode *next = child->next;
            if (child->prev)
                child->prev->next = next;
            removeNode(ui, child);
            if (next)
                next->prev = child;
            child = next;
        } else {
            if (!first)
                first = child;
            pruneStale(ui, child);
            last  = child;
            child = child->next;
        }
    }

    node->first = first;
    node->last = last;
}

static void updateNodeInteraction(BzUI *ui, BzUINode *node, Vector2 mouse) {
    BZ_ASSERT(node);

    bool hovered = CheckCollisionPointRec(mouse, getNodeRect(node));
    bool clicked = IsMouseButtonPressed(MOUSE_BUTTON_LEFT);
    if (!ui->captureMouse) {
        hovered = false;
        clicked = false;
    }
    BzUIFlags flags = node->flags;
    bool drawsAnything = (flags & BZ_UI_DRAW_BACKGROUND) ||
                         (flags & BZ_UI_DRAW_BOX_SHADOW)||
                         (flags & BZ_UI_DRAW_BORDER) ||
                         (flags & BZ_UI_DRAW_TEXT) ||
                         (flags & BZ_UI_DRAW_TEXT_SHADOW) ||
                         (flags & BZ_UI_DRAW_SPRITE);
    if (hovered && drawsAnything) {
        ui->capturedMouse = true;
    }

    node->interaction = (BzUIInteraction) {
        .pressed = hovered && clicked,
        .down = hovered && clicked,
        .released = hovered && clicked,
        .clicked = hovered && clicked,
        .hovering = hovered
    };

    BzUINode *child = node->first;
    while (child != NULL) {
        updateNodeInteraction(ui, child, mouse);
        child = child->next;
    }


}

static void renderNode(BzUI *ui, BzUINode *node) {
    BZ_ASSERT(ui);
    BZ_ASSERT(node);

    BzUIInteraction *inter = &node->interaction;

    Rectangle rect = getNodeRect(node);
    // Adjust for padding
    Rectangle drawRect = rect;
    drawRect.x += node->padding[BZ_UI_AXIS_X];
    drawRect.y += node->padding[BZ_UI_AXIS_Y];
    drawRect.width  -= (node->padding[BZ_UI_AXIS_X] + node->padding[BZ_UI_AXIS_X + 2]);
    drawRect.height -= (node->padding[BZ_UI_AXIS_Y] + node->padding[BZ_UI_AXIS_Y + 2]);

    if (node->flags & BZ_UI_DRAW_BOX_SHADOW) {

    }
    if (node->flags & BZ_UI_DRAW_BACKGROUND) {
        BzUIBackgroundStyle style = bzUIGetBackgroundStyle(ui, node);
        Color color = style.normal;
        if (inter->hovering) color = style.hover;
        if (inter->down)     color = style.active;
        Rectangle bgRect = rect;
        if (style.roundness > 0) {
            bgRect.x -= 1;
            bgRect.y -= 1;
            bgRect.width += 2;
            bgRect.height += 2;
        }
        DrawRectangleRounded(bgRect, style.roundness, 0, color);
    }
    if (node->flags & BZ_UI_DRAW_BORDER) {
        BzUIBorderStyle style = bzUIGetBorderStyle(ui, node);
        Color color = style.normal;
        if (inter->hovering) color = style.hover;
        if (inter->down)     color = style.active;
        DrawRectangleRoundedLines(rect, style.roundness, 0, style.thickness, color);
    }

    if (node->flags & BZ_UI_DRAW_SPRITE) {
        BzUISpriteStyle style = bzUIGetSpriteStyle(ui, node);
        Color tint = style.tintNormal;
        if (inter->hovering) tint = style.tintActive;
        if (inter->down)     tint = style.tintActive;
        Rectangle src = style.rec;
        Rectangle dst = rect;
        DrawTexturePro(style.texture, src, dst, Vector2Zero(), 0.0f, tint);
    }

    if (node->flags & BZ_UI_DRAW_TEXT) {
        BzUITextStyle style = bzUIGetTextStyle(ui, node);
        BZ_ASSERT(style.text);
        if (node->flags & BZ_UI_DRAW_TEXT_SHADOW) {
            BzUITextShadowStyle shadowStyle = bzUIGetTextShadowStyle(ui, node);
            Color color = shadowStyle.normal;
            if (inter->hovering) color = shadowStyle.hover;
            if (inter->down)     color = shadowStyle.active;
            DrawTextEx(style.font, style.text, (Vector2) {
                drawRect.x + shadowStyle.offset[BZ_UI_AXIS_X],
                drawRect.y + shadowStyle.offset[BZ_UI_AXIS_Y],
            }, style.fontSize, style.fontSpacing, color);
        }
        Color color = style.normal;
        if (inter->hovering) color = style.hover;
        if (inter->down)     color = style.active;
        DrawTextEx(style.font, style.text, (Vector2) {
            drawRect.x, drawRect.y
        }, style.fontSize, style.fontSpacing, color);
    }
    if (node->flags & BZ_UI_DRAW_DEBUG) {
        DrawRectangleLines(rect.x, rect.y, rect.width, rect.height, RED);
    }

    node->canInteract = true;

    BzUINode *child = node->first;
    while (child != NULL) {
        renderNode(ui, child);
        child = child->next;
    }
}

void bzUIEnd(BzUI *ui) {
    pruneStale(ui, ui->root);
    calculateSizes(ui, ui->root);
    calculatePositions(ui, ui->root, 0, 0);
    ui->capturedMouse = false;
    ui->capturedKeyboard = false;
    updateNodeInteraction(ui, ui->root, GetMousePosition());
    renderNode(ui, ui->root);

    ui->currFrame++;
}

BzUIKey bzUIGetUniqueKey(BzUI *ui) {
    return ui->keyIdCount++;
}

BzUINode *bzUINodeMake(BzUI *ui, BzUIKey key, const BzUINodeDesc *desc) {
    BzUINode *node = NULL;
    if (key != bzUIKeyNull())
        node = hmget(ui->nodeMap, key);
    if (!node) {
        node = bzObjectPool(ui->nodePool);
        bzMemSet(node, 0, sizeof(*node));
        hmput(ui->nodeMap, key, node);
        node->canInteract = true;
    }
    BZ_ASSERT(node);
    node->lastFrame = ui->currFrame;
    node->key = key;

    BzUINode *parent = bzArrayGet(ui->nodeStack, bzArraySize(ui->nodeStack) - 1);
    BZ_ASSERT(parent);

    bzUINodeClearLinks(node);
    bzUINodeClearStyles(node);

    if (parent->last) {
        parent->last->next = node;
        node->prev = parent->last;
        parent->last = node;
    } else {
        parent->first = node;
        parent->last = node;
    }

    node->parent = parent;

    node->layout = desc->layout;
    node->flags  = desc->flags;

    bzMemCpy(node->semanticSize, desc->semanticSize, sizeof(node->semanticSize));
    bzMemCpy(node->padding, desc->padding, sizeof(node->padding));
    bzMemCpy(node->margin, desc->margin, sizeof(node->margin));

    return node;
}

BzUINode *bzUIPushDiv(BzUI *ui, BzUISize x, BzUISize y) {
    return bzUIPushParent(ui, bzUINodeMake(ui, bzUIGetUniqueKey(ui),
                                           &(BzUINodeDesc) {
        .semanticSize[BZ_UI_AXIS_X] = x,
        .semanticSize[BZ_UI_AXIS_Y] = y,
    }));
}
BzUINode *bzUIPushParent(BzUI *ui, BzUINode *node) {
    BZ_ASSERT(node);
    bzArrayPush(ui->nodeStack, node);
    return node;
}
BzUINode *bzUIPopParent(BzUI *ui) {
    BZ_ASSERT(bzArraySize(ui->nodeStack) > 1);
    BzUINode *node = bzArrayPop(ui->nodeStack);
    return node;
}

BzUIBackgroundStyle bzUIGetBackgroundStyle(const BzUI *ui, BzUINode *node) {
    BZ_ASSERT(ui && node);
    BZ_ASSERT(node->backgroundStyleIdx != -1);
    return ui->backgroundStyles[node->backgroundStyleIdx];
}
BzUIBoxShadowStyle bzUIGetBoxShadowStyle(const BzUI *ui, BzUINode *node) {
    BZ_ASSERT(ui && node);
    BZ_ASSERT(node->boxShadowStyleIdx != -1);
    return ui->boxShadowStyles[node->boxShadowStyleIdx];
}
BzUITextStyle bzUIGetTextStyle(const BzUI *ui, BzUINode *node) {
    BZ_ASSERT(ui && node);
    BZ_ASSERT(node->textStyleIdx != -1);
    return ui->textStyles[node->textStyleIdx];
}
BzUITextShadowStyle bzUIGetTextShadowStyle(const BzUI *ui, BzUINode *node) {
    BZ_ASSERT(ui && node);
    BZ_ASSERT(node->textShadowStyleIdx != -1);
    return ui->textShadowStyles[node->textShadowStyleIdx];
}
BzUIBorderStyle bzUIGetBorderStyle(const BzUI *ui, BzUINode *node) {
    BZ_ASSERT(ui && node);
    BZ_ASSERT(node->borderStyleIdx != -1);
    return ui->borderStyles[node->borderStyleIdx];
}
BzUISpriteStyle bzUIGetSpriteStyle(const BzUI *ui, BzUINode *node) {
    BZ_ASSERT(ui && node);
    BZ_ASSERT(node->spriteStyleIdx != -1);
    return ui->spriteStyles[node->spriteStyleIdx];
}

#define BZ_UI_SET_STYLE(ui, node, mIdx, styles, style)  \
do {                                                    \
    i32 idx = node->mIdx;                               \
    if (idx == -1) {                                    \
        idx = bzArraySize(ui->styles);                  \
        bzArrayPush(ui->styles, style);                 \
        node->mIdx = idx;                               \
    } else {                                            \
        bzArraySet(ui->styles, idx, style);             \
    }                                                   \
} while (0)

void bzUISetBackgroundStyle(BzUI *ui, BzUINode *node, BzUIBackgroundStyle style) {
    BZ_ASSERT(ui && node);
    node->flags |= BZ_UI_DRAW_BACKGROUND;
    BZ_UI_SET_STYLE(ui, node, backgroundStyleIdx, backgroundStyles, style);
}
void bzUISetBoxShadowStyle(BzUI *ui, BzUINode *node, BzUIBoxShadowStyle style) {
    BZ_ASSERT(ui && node);
    node->flags |= BZ_UI_DRAW_BOX_SHADOW;
    BZ_UI_SET_STYLE(ui, node, boxShadowStyleIdx, boxShadowStyles, style);
}
void bzUISetTextStyle(BzUI *ui, BzUINode *node, BzUITextStyle style) {
    BZ_ASSERT(ui && node);
    node->flags |= BZ_UI_DRAW_TEXT;
    BZ_ASSERT(style.text);
    const char *str = style.text;
    char prev = '\0';
    while (*str) {
        if (*str == '#' && prev == '#') {
            str--;
            break;
        }
        prev = *str;
        str++;
    }
    size_t strLen = str - style.text;
    char *text = bzStackAlloc(&ui->strArena, strLen + 1);
    bzMemMove(text, style.text, strLen);
    text[strLen] = '\0';
    style.text = text;

    BZ_UI_SET_STYLE(ui, node, textStyleIdx, textStyles, style);
}
void bzUISetTextShadowStyle(BzUI *ui, BzUINode *node, BzUITextShadowStyle style) {
    BZ_ASSERT(ui && node);
    node->flags |= BZ_UI_DRAW_TEXT_SHADOW;
    BZ_UI_SET_STYLE(ui, node, textShadowStyleIdx, textShadowStyles, style);
}
void bzUISetBorderStyle(BzUI *ui, BzUINode *node, BzUIBorderStyle style) {
    BZ_ASSERT(ui && node);
    node->flags |= BZ_UI_DRAW_BORDER;
    BZ_UI_SET_STYLE(ui, node, borderStyleIdx, borderStyles, style);
}
void bzUISetSpriteStyle(BzUI *ui, BzUINode *node, BzUISpriteStyle style) {
    BZ_ASSERT(ui && node);
    node->flags = BZ_UI_DRAW_SPRITE;
    BZ_UI_SET_STYLE(ui, node, spriteStyleIdx, spriteStyles, style);
}
#undef BZ_UI_SET_STYLE

BzUILayout bzUIGetLayout(const BzUI *ui, BzUINode *node) {
    BZ_UNUSED(ui);
    BZ_ASSERT(node);
    return node->layout;
}
void bzUISetLayout(BzUI *ui, BzUINode *node, BzUILayout layout) {
    BZ_UNUSED(ui);
    BZ_ASSERT(node);
    node->layout = layout;
}

void bzUISetParentLayout(BzUI *ui, BzUILayout layout) {
    BZ_ASSERT(ui);
    i32 stackSize = bzArraySize(ui->nodeStack);
    BZ_ASSERT(stackSize > 0);
    BzUINode *last = bzArrayGet(ui->nodeStack, stackSize - 1);
    BZ_ASSERT(last);
    bzUISetLayout(ui, last, layout);
}

BzUIInteraction bzUIGetInteraction(BzUI *ui, BzUINode *node) {
    BZ_UNUSED(ui);
    BZ_ASSERT(node);
    return node->interaction;
}
bool bzUIButton(BzUI *ui, const char *string) {
    BzUINode *node = bzUINodeMake(ui, bzUIKeyFromString(string),
                                  &(BzUINodeDesc) {
        .flags = BZ_UI_CLICKABLE | BZ_UI_DRAW_TEXT |
                 BZ_UI_DRAW_BACKGROUND |
                 BZ_UI_DRAW_BORDER | BZ_UI_ALIGN_CENTER
    });
    node->semanticSize[BZ_UI_AXIS_X] = (BzUISize) {
        .kind = BZ_UI_SIZE_FIT,
    };
    node->semanticSize[BZ_UI_AXIS_Y] = node->semanticSize[BZ_UI_AXIS_X];

    for (i32 i = 0; i < 4; i++) {
        node->padding[i] = 2;
        node->margin[i]  = 4;
    }

    bzUISetBackgroundStyle(ui, node, (BzUIBackgroundStyle) {
        .normal = GRAY,
        .hover = GRAY,
        .active = GRAY,
    });
    bzUISetBorderStyle(ui, node, (BzUIBorderStyle) {
        .thickness = 4.0f,
        .normal = BLACK,
        .hover = BLACK,
        .active = GRAY
    });
    bzUISetTextStyle(ui, node, (BzUITextStyle) {
       .text = string,
       .font = GetFontDefault(),
       .fontSize = 24,
       .fontSpacing = 2,
       .normal = BLACK,
       .hover = RED,
       .active = ORANGE
    });
    return bzUIGetInteraction(ui, node).clicked;
}
