#define DVX_WIDGET_IMPL
// widgetOps.c  -- Paint dispatcher and public widget operations
//
// This file contains two categories of functions:
//   1. The paint dispatcher (widgetPaintOne, widgetPaintOverlays, wgtPaint)
//      which walks the widget tree and calls per-type paint functions.
//   2. Public operations (wgtSetText, wgtSetEnabled, wgtSetVisible,
//      wgtFind, wgtDestroy, etc.) that form the widget system's public API.
//
// The paint dispatcher and the public operations are in the same file
// because they share the same invalidation infrastructure (wgtInvalidate
// and wgtInvalidatePaint).

#include "dvxWidgetPlugin.h"
#include "dvxPlatform.h"
#include "../widgets/widgetBox.h"


// ============================================================
// debugContainerBorder
// ============================================================
//
// Draws a 1px border in a neon color derived from the widget pointer.
// The Knuth multiplicative hash (2654435761) distributes pointer values
// across the palette evenly so adjacent containers get different colors.
// This is only active when sDebugLayout is true (toggled via
// wgtSetDebugLayout), used during development to visualize container
// boundaries and diagnose layout issues.

static void debugContainerBorder(WidgetT *w, DisplayT *d, const BlitOpsT *ops) {
    static const uint8_t palette[][3] = {
        {255,   0, 255},  // magenta
        {  0, 255,   0},  // lime
        {255, 255,   0},  // yellow
        {  0, 255, 255},  // cyan
        {255, 128,   0},  // orange
        {128,   0, 255},  // purple
        {255,   0, 128},  // hot pink
        {  0, 128, 255},  // sky blue
        {128, 255,   0},  // chartreuse
        {255,  64,  64},  // salmon
        { 64, 255, 128},  // mint
        {255, 128, 255},  // orchid
    };

    uint32_t h   = (uint32_t)(uintptr_t)w * 2654435761u;
    int32_t  idx = (int32_t)((h >> 16) % 12);
    uint32_t color = packColor(d, palette[idx][0], palette[idx][1], palette[idx][2]);

    drawHLine(d, ops, w->x, w->y, w->w, color);
    drawHLine(d, ops, w->x, w->y + w->h - 1, w->w, color);
    drawVLine(d, ops, w->x, w->y, w->h, color);
    drawVLine(d, ops, w->x + w->w - 1, w->y, w->h, color);
}


// ============================================================
// widgetPaintOne
// ============================================================
//
// Recursive paint walker. For each visible widget:
//   1. Call the widget's paint function (if any) via vtable.
//   2. If the widget has WCLASS_PAINTS_CHILDREN, stop recursion  --
//      the widget's paint function already handled its children
//      (e.g. TabControl only paints the active tab's children).
//   3. Otherwise, recurse into children (default child painting).
//   4. Draw debug borders on top if debug layout is enabled.
//
// The paint order is parent-before-children, which means parent
// backgrounds are drawn first and children paint on top. This is
// the standard painter's algorithm for nested UI elements.

void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
    if (!w->visible) {
        return;
    }

    // Paint this widget via vtable
    wclsPaint(w, d, ops, font, colors);

    // Widgets that paint their own children return early
    if (w->wclass && (w->wclass->flags & WCLASS_PAINTS_CHILDREN)) {
        if (sDebugLayout) {
            debugContainerBorder(w, d, ops);
        }

        return;
    }

    // Paint children
    for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
        widgetPaintOne(c, d, ops, font, colors);
    }

    // Debug: draw container borders on top of children
    if (sDebugLayout && w->firstChild) {
        debugContainerBorder(w, d, ops);
    }
}


// ============================================================
// widgetPaintOverlays
// ============================================================
//
// Paints popup overlays (open dropdowns/comboboxes) on top of
// the widget tree. Called AFTER the main paint pass so popups
// always render above all other widgets regardless of tree position.
//
// Only one popup can be open at a time (tracked by sOpenPopup).
// The tree-root ownership check prevents a popup from one window
// being painted into a different window's content buffer.

void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
    if (!sOpenPopup) {
        return;
    }

    // Verify the popup belongs to this widget tree
    WidgetT *check = sOpenPopup;

    while (check->parent) {
        check = check->parent;
    }

    if (check != root) {
        return;
    }

    wclsPaintOverlay(sOpenPopup, d, ops, font, colors);
}


// ============================================================
// wgtDestroy
// ============================================================
//
// Destroys a widget and its entire subtree. The order is:
//   1. Unlink from parent (so the parent doesn't reference freed memory)
//   2. Recursively destroy all children (depth-first)
//   3. Call the widget's own destroy callback (free buffers, etc.)
//   4. Clear any global state that references this widget
//   5. Clear the window's root pointer if this was the root
//   6. Free the widget memory
//
// This ordering ensures that per-widget destroy callbacks can still
// access the widget's data (step 3 comes after child cleanup but
// before the widget itself is freed).

void wgtDestroy(WidgetT *w) {
    if (!w) {
        return;
    }

    // Notify parent chain of child destruction via onChildChanged vtable
    if (w->parent) {
        for (WidgetT *p = w->parent; p; p = p->parent) {
            if (wclsHas(p, WGT_METHOD_ON_CHILD_CHANGED)) {
                wclsOnChildChanged(p, w);
                break;
            }
        }
    }

    if (w->parent) {
        widgetRemoveChild(w->parent, w);
    }

    widgetDestroyChildren(w);

    wclsDestroy(w);

    // Clear static references
    if (sFocusedWidget == w) {
        sFocusedWidget = NULL;
    }

    if (sOpenPopup == w) {
        sOpenPopup = NULL;
    }

    if (sDragWidget == w) {
        sDragWidget = NULL;
    }

    // If this is the root, clear the window's reference
    if (w->window && w->window->widgetRoot == w) {
        w->window->widgetRoot = NULL;
    }

    free(w);
}


// ============================================================
// wgtFind
// ============================================================

static WidgetT *wgtFindImpl(WidgetT *w, const char *name) {
    if (w->name[0] && strcmp(w->name, name) == 0) {
        return w;
    }

    for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
        WidgetT *found = wgtFindImpl(c, name);

        if (found) {
            return found;
        }
    }

    return NULL;
}

WidgetT *wgtFind(WidgetT *root, const char *name) {
    if (!root || !name) {
        return NULL;
    }

    return wgtFindImpl(root, name);
}


// ============================================================
// wgtSetName
// ============================================================

void wgtSetName(WidgetT *w, const char *name) {
    if (!w || !name) {
        return;
    }

    strncpy(w->name, name, MAX_WIDGET_NAME - 1);
    w->name[MAX_WIDGET_NAME - 1] = '\0';
}


// ============================================================
// wgtGetContext
// ============================================================
//
// Retrieves the AppContextT from any widget by walking up to the root.
// The root widget stores the context in its userData field (set during
// wgtInitWindow). This is the only way to get the AppContextT from
// deep inside the widget tree without passing it as a parameter
// through every function call. The walk is O(depth) but widget trees
// are shallow (typically 3-6 levels deep).

AppContextT *wgtGetContext(const WidgetT *w) {
    if (!w) {
        return NULL;
    }

    const WidgetT *root = w;

    while (root->parent) {
        root = root->parent;
    }

    return (AppContextT *)root->userData;
}


// ============================================================
// wgtGetText
// ============================================================
//
// Polymorphic text getter  -- dispatches through the vtable to the
// appropriate getText implementation for the widget's type. Returns
// an empty string (not NULL) if the widget has no text or no getText
// handler, so callers don't need NULL checks.

const char *wgtGetText(const WidgetT *w) {
    if (!w) {
        return "";
    }

    return wclsGetText(w);
}


// ============================================================
// wgtInitWindow
// ============================================================
//
// Sets up a window for widget-based content. Creates a root VBox
// container and installs the four window callbacks (onPaint, onMouse,
// onKey, onResize) that bridge WM events into the widget system.
//
// The root widget's userData points to the AppContextT, which is
// the bridge back to the display, font, colors, and blitOps needed
// for painting. This avoids threading the context through every
// widget function  -- any widget can retrieve it via wgtGetContext().

WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
    WidgetT *root = dvxBoxApi() ? dvxBoxApi()->vBox(NULL) : NULL;

    if (!root) {
        dvxLog("Widget: wgtInitWindow failed (%s)", dvxBoxApi() ? "vBox returned NULL" : "dvxBoxApi returned NULL");
        return NULL;
    }

    root->window   = win;
    root->userData = ctx;

    win->widgetRoot = root;
    win->onPaint    = widgetOnPaint;
    win->onMouse    = widgetOnMouse;
    win->onKey      = widgetOnKey;
    win->onKeyUp    = widgetOnKeyUp;
    win->onResize   = widgetOnResize;

    return root;
}


// ============================================================
// wgtInvalidate
// ============================================================
//
// Full invalidation: re-measures the widget tree, manages scrollbars,
// re-lays out, repaints, and dirties the window on screen.
//
// This is the "something structural changed" path  -- use when widget
// sizes may have changed (text changed, children added/removed,
// visibility toggled). If only visual state changed (cursor blink,
// selection highlight), use wgtInvalidatePaint() instead to skip
// the expensive measure/layout passes.
//
// The widgetOnPaint check ensures that custom paint handlers (used
// by some dialog implementations) aren't bypassed by the scrollbar
// management code.

void wgtInvalidate(WidgetT *w) {
    if (!w || !w->window) {
        return;
    }

    // Find the root
    WidgetT *root = w;

    while (root->parent) {
        root = root->parent;
    }

    AppContextT *ctx = (AppContextT *)root->userData;

    if (!ctx) {
        return;
    }

    // Manage scrollbars (measures, adds/removes scrollbars, relayouts)
    // Skip if window has a custom paint handler (e.g. dialog) that manages its own layout
    if (w->window->onPaint == widgetOnPaint) {
        widgetManageScrollbars(w->window, ctx);
    }

    // Dirty the window  -- dvxInvalidateWindow calls onPaint automatically
    dvxInvalidateWindow(ctx, w->window);
}


// ============================================================
// wgtInvalidatePaint
// ============================================================
//
// Lightweight repaint  -- skips measure/layout/scrollbar management.
// Use when only visual state changed (slider value, cursor blink,
// selection highlight, checkbox toggle) but widget sizes are stable.

void wgtInvalidatePaint(WidgetT *w) {
    if (!w || !w->window) {
        return;
    }

    WidgetT *root = w;

    while (root->parent) {
        root = root->parent;
    }

    AppContextT *ctx = (AppContextT *)root->userData;

    if (!ctx) {
        return;
    }

    // Dirty the window  -- dvxInvalidateWindow calls onPaint automatically
    dvxInvalidateWindow(ctx, w->window);
}


// ============================================================
// wgtPaint
// ============================================================

void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
    if (!root) {
        return;
    }

    widgetPaintOne(root, d, ops, font, colors);
}


// ============================================================
// wgtSetDebugLayout
// ============================================================

void wgtSetDebugLayout(AppContextT *ctx, bool enabled) {
    sDebugLayout = enabled;

    for (int32_t i = 0; i < ctx->stack.count; i++) {
        WindowT *win = ctx->stack.windows[i];

        if (win->widgetRoot) {
            wgtInvalidate(win->widgetRoot);
        }
    }
}


// ============================================================
// wgtGetFocused
// ============================================================

WidgetT *wgtGetFocused(void) {
    return sFocusedWidget;
}


// ============================================================
// wgtSetEnabled
// ============================================================

void wgtSetEnabled(WidgetT *w, bool enabled) {
    if (w) {
        w->enabled = enabled;
        wgtInvalidatePaint(w);
    }
}


// ============================================================
// wgtSetFocused
// ============================================================

void wgtSetFocused(WidgetT *w) {
    if (!w || !w->enabled) {
        return;
    }

    WidgetT *prev = sFocusedWidget;

    if (prev && prev != w) {
        wgtInvalidatePaint(prev);
    }

    sFocusedWidget = w;
    wgtInvalidatePaint(w);

    if (prev && prev != w && prev->onBlur) {
        prev->onBlur(prev);
    }

    if (w->onFocus) {
        w->onFocus(w);
    }
}


// ============================================================
// wgtSetReadOnly
// ============================================================

void wgtSetReadOnly(WidgetT *w, bool readOnly) {
    if (w) {
        w->readOnly = readOnly;
    }
}


// ============================================================
// wgtSetText
// ============================================================
//
// Polymorphic text setter. Dispatches to the type-specific setText
// via vtable, then does a full invalidation because changing text
// can change the widget's minimum size (triggering relayout).

void wgtSetText(WidgetT *w, const char *text) {
    if (!w) {
        return;
    }

    wclsSetText(w, text);

    wgtInvalidate(w);
}


// ============================================================
// wgtSetTooltip
// ============================================================

void wgtSetTooltip(WidgetT *w, const char *text) {
    if (w) {
        w->tooltip = text;
    }
}


// ============================================================
// wgtSetVisible
// ============================================================

void wgtSetVisible(WidgetT *w, bool visible) {
    if (w) {
        w->visible = visible;

        // Notify parent chain of child visibility change via onChildChanged vtable
        if (w->parent) {
            for (WidgetT *p = w->parent; p; p = p->parent) {
                if (wclsHas(p, WGT_METHOD_ON_CHILD_CHANGED)) {
                    wclsOnChildChanged(p, w);
                    break;
                }
            }
        }

        wgtInvalidate(w);
    }
}
