// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// dvxdemo.c  -- DVX GUI demonstration app (DXE version)
//
// Callback-only DXE app (hasMainLoop = false) that opens several windows
// showcasing the DVX widget system, paint callbacks, menus, accelerators, etc.
//
// This serves as a comprehensive widget catalog and integration test:
//   - setupMainWindow:      raw onPaint callbacks (text, gradient, pattern)
//   - setupWidgetDemo:      form widgets (input, checkbox, radio, listbox)
//   - setupControlsWindow:  advanced widgets in a TabControl (dropdown,
//                           progress, slider, spinner, tree, listview,
//                           scrollpane, toolbar, canvas, splitter, image)
//   - setupTerminalWindow:  ANSI terminal emulator widget
//
// Each window is independent; closing one doesn't affect the others.
// The app has no persistent state  -- it's purely a showcase.

#include "dvxApp.h"
#include "dvxDlg.h"
#include "dvxWgt.h"
#include "ansiTerm/ansiTerm.h"
#include "box/box.h"
#include "button/button.h"
#include "canvas/canvas.h"
#include "checkbox/checkbox.h"
#include "comboBox/comboBox.h"
#include "dropdown/dropdown.h"
#include "image/image.h"
#include "imageButton/imgBtn.h"
#include "label/label.h"
#include "listBox/listBox.h"
#include "listView/listView.h"
#include "progressBar/progress.h"
#include "radio/radio.h"
#include "scrollPane/scrlPane.h"
#include "separator/separatr.h"
#include "slider/slider.h"
#include "spacer/spacer.h"
#include "spinner/spinner.h"
#include "splitter/splitter.h"
#include "statusBar/statBar.h"
#include "tabControl/tabCtrl.h"
#include "textInput/textInpt.h"
#include "toolbar/toolbar.h"
#include "treeView/treeView.h"
#include "dvxWm.h"
#include "shellApp.h"

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dvxMem.h"

// ============================================================
// Menu command IDs
// ============================================================

#define CMD_FILE_NEW    100
#define CMD_FILE_OPEN   101
#define CMD_FILE_SAVE   102
#define CMD_FILE_EXIT   103
#define CMD_EDIT_CUT    200
#define CMD_EDIT_COPY   201
#define CMD_EDIT_PASTE  202
#define CMD_VIEW_TERM       300
#define CMD_VIEW_CTRL       301
#define CMD_VIEW_ZOOM_IN    302
#define CMD_VIEW_ZOOM_OUT   303
#define CMD_VIEW_ZOOM_FIT   304
#define CMD_VIEW_TOOLBAR    305
#define CMD_VIEW_STATUSBAR  306
#define CMD_VIEW_SIZE_SMALL 307
#define CMD_VIEW_SIZE_MED   308
#define CMD_VIEW_SIZE_LARGE 309
#define CMD_VIEW_DEBUG_LYT  310
#define CMD_WIN_CASCADE  350
#define CMD_WIN_TILE_H   351
#define CMD_WIN_TILE_V   352
#define CMD_WIN_TILE     353
#define CMD_HELP_ABOUT   400
#define CMD_CTX_CUT     500
#define CMD_CTX_COPY    501
#define CMD_CTX_PASTE   502
#define CMD_CTX_DELETE  503
#define CMD_CTX_SELALL  504
#define CMD_CTX_PROPS   505

// ============================================================
// Module state
// ============================================================

static AppContextT    *sAc     = NULL;
static DxeAppContextT *sDxeCtx = NULL;

// wmDestroyWindow frees neither win->accelTable nor win->contextMenu, and
// widget teardown doesn't free widget->contextMenu, so track these here and
// release them in appShutdown to avoid leaking on every launch/close.
static AccelTableT    *sAccel      = NULL;
static MenuT          *sWinCtxMenu = NULL;
static MenuT          *sLbCtxMenu  = NULL;

static const FileFilterT sFileFilters[] = {
    {"All Files (*.*)"},
    {"Text Files (*.txt)"},
    {"Batch Files (*.bat)"},
    {"Executables (*.exe)"},
    {"Bitmap Files (*.bmp)"}
};

// Item arrays are static because dropdown/combobox widgets store pointers,
// not copies. They must outlive the widgets that reference them.
static const char *colorItems[] = {"Red", "Green", "Blue", "Yellow", "Cyan", "Magenta"};
static const char *sizeItems[]  = {"Small", "Medium", "Large", "Extra Large"};

// ============================================================
// Prototypes
// ============================================================

int32_t     appMain(DxeAppContextT *ctx);
static void onCanvasDraw(WidgetT *w, int32_t cx, int32_t cy, bool drag);
static void onCloseCb(WindowT *win);
static void onCloseMainCb(WindowT *win);
static void onMenuCb(WindowT *win, int32_t menuId);
static void onOkClick(WidgetT *w);
static void onPaintColor(WindowT *win, RectT *dirtyArea);
static void onPaintPattern(WindowT *win, RectT *dirtyArea);
static void onPaintText(WindowT *win, RectT *dirtyArea);
static void onToolbarClick(WidgetT *w);
static void setStatusByName(WidgetT *from, const char *name, const char *text);
static void setupControlsWindow(void);
static void setupMainWindow(void);
static void setupTerminalWindow(void);
static void setupWidgetDemo(void);

// ============================================================
// App descriptor
// ============================================================

// Callback-only: all windows use either widget trees (which handle their own
// input) or onPaint callbacks (which are invoked by the compositor). No need
// for a dedicated task since there's no ongoing computation.
AppDescriptorT appDescriptor = {
    .name        = "DVX Demo",
    .hasMainLoop = false,
    .stackSize   = SHELL_STACK_DEFAULT,
    .priority    = TS_PRIORITY_NORMAL
};


// Cleanup hook the shell calls before dlclose.  The accelerator table and the
// two context menus aren't freed by window/widget teardown, so release them
// here to avoid leaking into the shell heap on every launch/close.
void appShutdown(void) {
    if (sAccel) {
        dvxFreeAccelTable(sAccel);
        sAccel = NULL;
    }

    if (sWinCtxMenu) {
        wmFreeMenu(sWinCtxMenu);
        sWinCtxMenu = NULL;
    }

    if (sLbCtxMenu) {
        wmFreeMenu(sLbCtxMenu);
        sLbCtxMenu = NULL;
    }
}


static void onCanvasDraw(WidgetT *w, int32_t cx, int32_t cy, bool drag) {
    (void)drag;
    wgtCanvasDrawLine(w, cx, cy, cx, cy);
}


static void onCloseCb(WindowT *win) {
    dvxDestroyWindow(sAc, win);
}


static void onCloseMainCb(WindowT *win) {
    int32_t result = dvxMessageBox(sAc, "Close",
                                    "Close this window?",
                                    MB_YESNO | MB_ICONQUESTION);

    if (result == ID_YES) {
        dvxDestroyWindow(sAc, win);
    }
}


static void onMenuCb(WindowT *win, int32_t menuId) {
    switch (menuId) {
        case CMD_FILE_OPEN: {
            char path[DVX_MAX_PATH];

            if (dvxFileDialog(sAc, "Open File", FD_OPEN, NULL, sFileFilters, 5, path, sizeof(path))) {
                char msg[300];
                snprintf(msg, sizeof(msg), "Selected: %s", path);
                dvxInfoBox(sAc, "Open", msg);
            }

            break;
        }

        case CMD_FILE_SAVE: {
            char path[DVX_MAX_PATH];

            if (dvxFileDialog(sAc, "Save As", FD_SAVE, NULL, sFileFilters, 5, path, sizeof(path))) {
                char msg[300];
                snprintf(msg, sizeof(msg), "Save to: %s", path);
                dvxInfoBox(sAc, "Save", msg);
            }

            break;
        }

        case CMD_FILE_EXIT:
            onCloseMainCb(win);
            break;

        case CMD_VIEW_TERM:
            setupTerminalWindow();
            break;

        case CMD_VIEW_CTRL:
            setupControlsWindow();
            break;

        case CMD_VIEW_DEBUG_LYT: {
            static bool sDebugLyt = false;
            sDebugLyt = !sDebugLyt;
            wgtSetDebugLayout(sAc, sDebugLyt);
            break;
        }

        case CMD_WIN_CASCADE:
            dvxCascadeWindows(sAc);
            break;

        case CMD_WIN_TILE_H:
            dvxTileWindowsH(sAc);
            break;

        case CMD_WIN_TILE_V:
            dvxTileWindowsV(sAc);
            break;

        case CMD_WIN_TILE:
            dvxTileWindows(sAc);
            break;

        case CMD_HELP_ABOUT:
            dvxInfoBox(sAc, "About DVX Demo",
                       "DVX GUI Demonstration\n"
                       "A DOS Visual eXecutive windowing system for DOS.");
            break;

        case CMD_CTX_CUT:
            dvxInfoBox(sAc, "Context Menu", "Cut selected.");
            break;

        case CMD_CTX_COPY:
            dvxInfoBox(sAc, "Context Menu", "Copied to clipboard.");
            break;

        case CMD_CTX_PASTE:
            dvxInfoBox(sAc, "Context Menu", "Pasted from clipboard.");
            break;

        case CMD_CTX_DELETE:
            dvxInfoBox(sAc, "Context Menu", "Deleted.");
            break;

        case CMD_CTX_SELALL:
            dvxInfoBox(sAc, "Context Menu", "Selected all.");
            break;

        case CMD_CTX_PROPS:
            dvxInfoBox(sAc, "Properties", "Window properties dialog would appear here.");
            break;
    }
}


// Walk up the widget tree to find the root, then use wgtFind() to locate
// the named status label. This pattern avoids static coupling between the
// button and the status label  -- they're connected only by the name string.
static void onOkClick(WidgetT *w) {
    setStatusByName(w, "status", "Button clicked!");
}


// Renders a vertical gradient directly into the window's content buffer.
// This demonstrates the raw onPaint approach: the app has full pixel control
// and uses the BlitOps span primitives for performance. The gradient is
// computed per-scanline using the span fill operation (rep stosl on x86),
// which is much faster than per-pixel writes.
static void onPaintColor(WindowT *win, RectT *dirtyArea) {
    (void)dirtyArea;

    if (!win->contentBuf || !sAc) {
        return;
    }

    const DisplayT *d   = dvxGetDisplay(sAc);
    const BlitOpsT *ops = dvxGetBlitOps(sAc);

    for (int32_t y = 0; y < win->contentH; y++) {
        uint8_t r = (uint8_t)((y * 255) / (win->contentH > 1 ? win->contentH - 1 : 1));
        uint8_t g = (uint8_t)(100);
        uint8_t b = (uint8_t)(200 - (y * 150) / (win->contentH > 1 ? win->contentH - 1 : 1));

        uint32_t color = packColor(d, r, g, b);
        uint8_t *row   = win->contentBuf + y * win->contentPitch;

        ops->spanFill(row, color, win->contentW);
    }
}


// Renders a checkerboard pattern pixel-by-pixel. This is the slowest
// approach (per-pixel BPP branching) and exists to test that the content
// buffer renders correctly at all supported bit depths (8/16/32 bpp).
static void onPaintPattern(WindowT *win, RectT *dirtyArea) {
    (void)dirtyArea;

    if (!win->contentBuf || !sAc) {
        return;
    }

    const DisplayT *d   = dvxGetDisplay(sAc);
    int32_t         bpp = d->format.bytesPerPixel;
    int32_t         sq  = 16;

    uint32_t c1 = packColor(d, 255, 255, 255);
    uint32_t c2 = packColor(d, 0, 0, 180);

    for (int32_t y = 0; y < win->contentH; y++) {
        for (int32_t x = 0; x < win->contentW; x++) {
            uint32_t color = ((x / sq) + (y / sq)) & 1 ? c2 : c1;
            uint8_t *px    = win->contentBuf + y * win->contentPitch + x * bpp;

            if (bpp == 1) {
                *px = (uint8_t)color;
            } else if (bpp == 2) {
                *(uint16_t *)px = (uint16_t)color;
            } else {
                *(uint32_t *)px = color;
            }
        }
    }
}


// Renders text manually using the bitmap font glyph data, bypassing the
// normal drawText helper. This demonstrates direct glyph rendering and
// also serves as a regression test for the font subsystem  -- if the glyph
// data format ever changes, this paint callback would visibly break.
static void onPaintText(WindowT *win, RectT *dirtyArea) {
    (void)dirtyArea;

    if (!win->contentBuf || !sAc) {
        return;
    }

    const DisplayT     *d    = dvxGetDisplay(sAc);
    const BlitOpsT     *ops  = dvxGetBlitOps(sAc);
    const BitmapFontT  *font = dvxGetFont(sAc);
    int32_t             bpp  = d->format.bytesPerPixel;

    uint32_t bg = packColor(d, 255, 255, 255);
    uint32_t fg = packColor(d, 0, 0, 0);

    for (int32_t y = 0; y < win->contentH; y++) {
        ops->spanFill(win->contentBuf + y * win->contentPitch, bg, win->contentW);
    }

    static const char *lines[] = {
        "DVX GUI Compositor",
        "",
        "A DOS Visual eXecutive windowed",
        "GUI compositor for DOS.",
        "",
        "Features:",
        "  - VESA VBE 2.0+ LFB",
        "  - Dirty-rect compositing",
        "  - Beveled Motif-style chrome",
        "  - Draggable/resizable windows",
        "  - Menu bars with accelerators",
        "  - Scrollbars",
        "  - Widget system",
        "  - ANSI terminal emulator",
        NULL
    };

    int32_t textY = 4;

    for (int32_t i = 0; lines[i] != NULL; i++) {
        const char *line = lines[i];
        int32_t     textX = 4;

        for (int32_t j = 0; line[j] != '\0'; j++) {
            int32_t idx = (uint8_t)line[j] - font->firstChar;

            if (idx < 0 || idx >= font->numChars) {
                textX += font->charWidth;
                continue;
            }

            const uint8_t *glyph = font->glyphData + idx * font->charHeight;

            for (int32_t row = 0; row < font->charHeight; row++) {
                int32_t py = textY + row;

                if (py >= win->contentH) {
                    break;
                }

                uint8_t bits = glyph[row];

                for (int32_t col = 0; col < font->charWidth; col++) {
                    int32_t px = textX + col;

                    if (px >= win->contentW) {
                        break;
                    }

                    if (bits & (0x80 >> col)) {
                        uint8_t *dst = win->contentBuf + py * win->contentPitch + px * bpp;

                        if (bpp == 1) {
                            *dst = (uint8_t)fg;
                        } else if (bpp == 2) {
                            *(uint16_t *)dst = (uint16_t)fg;
                        } else {
                            *(uint32_t *)dst = fg;
                        }
                    }
                }
            }

            textX += font->charWidth;
        }

        textY += font->charHeight + 2;
    }
}


static void onToolbarClick(WidgetT *w) {
    setStatusByName(w, "advStatus", wgtGetText(w));
}


// Walks to the window root from any widget and sets the text of the named
// status widget, if present.  Shared by the demo's click handlers.
static void setStatusByName(WidgetT *from, const char *name, const char *text) {
    WidgetT *root = from;

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

    WidgetT *status = wgtFind(root, name);

    if (status) {
        wgtSetText(status, text);
    }
}


// Demonstrates every advanced widget type, organized into tabs. The TabControl
// widget manages tab pages; each page is a container that shows/hides based
// on the selected tab. This is the densest widget test in the project.
static void setupControlsWindow(void) {
    WindowT *win = dvxCreateWindow(sAc, "Advanced Widgets", 380, 50, 360, 440, true);

    if (!win) {
        return;
    }

    win->onClose = onCloseCb;

    WidgetT *root = wgtInitWindow(sAc, win);

    // TabControl at top
    WidgetT *tabs = wgtTabControl(root);

    // --- Tab 1: Controls ---
    WidgetT *page1 = wgtTabPage(tabs, "&Controls");

    WidgetT *ddRow = wgtHBox(page1);
    wgtLabel(ddRow, "Co&lor:");
    WidgetT *dd = wgtDropdown(ddRow);
    wgtDropdownSetItems(dd, colorItems, 6);
    wgtDropdownSetSelected(dd, 0);
    wgtSetTooltip(dd, "Choose a color");

    WidgetT *cbRow = wgtHBox(page1);
    wgtLabel(cbRow, "Si&ze:");
    WidgetT *cb = wgtComboBox(cbRow, 32);
    wgtComboBoxSetItems(cb, sizeItems, 4);
    wgtComboBoxSetSelected(cb, 1);
    wgtSetTooltip(cb, "Type or select a size");

    wgtHSeparator(page1);

    wgtLabel(page1, "&Progress:");
    WidgetT *pbRow = wgtHBox(page1);
    WidgetT *pb = wgtProgressBar(pbRow);
    pb->weight = WGT_WEIGHT_FILL;
    wgtProgressBarSetValue(pb, 65);
    wgtSetTooltip(pb, "Task progress: 65%");
    WidgetT *pbV = wgtProgressBarV(pbRow);
    wgtProgressBarSetValue(pbV, 75);
    wgtSetTooltip(pbV, "Vertical progress: 75%");

    wgtLabel(page1, "&Volume:");
    WidgetT *slider = wgtSlider(page1, 0, 100);
    wgtSetTooltip(slider, "Adjust volume level");

    WidgetT *spinRow = wgtHBox(page1);
    spinRow->maxH = wgtPixels(30);
    wgtLabel(spinRow, "&Quantity:");
    WidgetT *spin = wgtSpinner(spinRow, 0, 999, 1);
    wgtSpinnerSetValue(spin, 42);
    spin->weight = 50;
    wgtSetTooltip(spin, "Enter quantity (0-999)");

    // --- Tab 2: Tree ---
    WidgetT *page2 = wgtTabPage(tabs, "&Tree");

    WidgetT *tree = wgtTreeView(page2);
    wgtTreeViewSetReorderable(tree, true);
    WidgetT *docs = wgtTreeItem(tree, "Documents");
    wgtTreeItemSetExpanded(docs, true);
    wgtTreeItem(docs, "README.md");
    wgtTreeItem(docs, "DESIGN.md");
    WidgetT *src = wgtTreeItem(docs, "src");
    wgtTreeItemSetExpanded(src, true);
    wgtTreeItem(src, "main.c");
    wgtTreeItem(src, "utils.c");
    wgtTreeItem(src, "render.c");
    WidgetT *images = wgtTreeItem(tree, "Images");
    wgtTreeItem(images, "logo.png");
    wgtTreeItem(images, "icon.bmp");
    WidgetT *config = wgtTreeItem(tree, "Config");
    wgtTreeItem(config, "settings.ini");
    wgtTreeItem(config, "palette.dat");

    // --- Tab 3: ListView (multi-column list) ---
    WidgetT *page3 = wgtTabPage(tabs, "&List");

    static const ListViewColT lvCols[] = {
        {"Name",     (int32_t)(WGT_SIZE_CHARS | 16), ListViewAlignLeftE},
        {"Size",     (int32_t)(WGT_SIZE_CHARS | 8),  ListViewAlignRightE},
        {"Type",     (int32_t)(WGT_SIZE_CHARS | 12), ListViewAlignLeftE},
        {"Modified", (int32_t)(WGT_SIZE_CHARS | 12), ListViewAlignLeftE}
    };

    static const char *lvData[] = {
        "AUTOEXEC.BAT", "412",     "Batch File",   "03/15/1994",
        "CONFIG.SYS",   "256",     "System File",  "03/15/1994",
        "COMMAND.COM",  "54,645",  "Application",  "09/30/1993",
        "HIMEM.SYS",    "29,136",  "System Driver", "09/30/1993",
        "EMM386.EXE",   "120,926", "Application",  "09/30/1993",
        "MOUSE.COM",    "56,408",  "Application",  "06/01/1994",
        "DOSKEY.COM",   "5,883",   "Application",  "09/30/1993",
        "EDIT.COM",     "413",     "Application",  "09/30/1993",
        "README.TXT",   "8,192",   "Text File",    "01/10/1994",
        "DVXDEMO.EXE",  "98,304",  "Application",  "03/15/2026"
    };

    WidgetT *lv = wgtListView(page3);
    wgtListViewSetColumns(lv, lvCols, 4);
    wgtListViewSetData(lv, lvData, 10);
    wgtListViewSetSelected(lv, 0);
    wgtListViewSetMultiSelect(lv, true);
    wgtListViewSetReorderable(lv, true);
    lv->weight = WGT_WEIGHT_FILL;

    // --- Tab 4: ScrollPane ---
    WidgetT *page4sp = wgtTabPage(tabs, "&Scroll");

    WidgetT *sp = wgtScrollPane(page4sp);
    sp->weight  = WGT_WEIGHT_FILL;
    sp->padding = wgtPixels(4);
    sp->spacing = wgtPixels(4);

    wgtLabel(sp, "ScrollPane Demo:");
    wgtHSeparator(sp);

    static const char *spItems[] = {
        "Item &1", "Item &2", "Item &3", "Item &4",
        "Item &5", "Item &6", "Item &7", "Item &8"
    };

    for (int32_t i = 0; i < 8; i++) {
        wgtCheckbox(sp, spItems[i]);
    }

    wgtHSeparator(sp);
    wgtLabel(sp, "More widgets below:");

    WidgetT *spRg = wgtRadioGroup(sp);
    static const char *spRadios[] = {
        "Radio 1", "Radio 2", "Radio 3", "Radio 4", "Radio 5"
    };

    for (int32_t i = 0; i < 5; i++) {
        wgtRadio(spRg, spRadios[i]);
    }

    wgtHSeparator(sp);
    wgtLabel(sp, "Bottom of scroll area");
    wgtButton(sp, "Scrolled &Button");

    // --- Tab 5: Toolbar (text buttons + VSeparator) ---
    WidgetT *page5tb = wgtTabPage(tabs, "Tool&bar");

    WidgetT *tb = wgtToolbar(page5tb);

    char iconPath[DVX_MAX_PATH];
    snprintf(iconPath, sizeof(iconPath), "%s/new.bmp", sDxeCtx->appDir);
    WidgetT *btnNew = wgtImageButtonFromFile(tb, iconPath);

    if (!btnNew) {
        btnNew = wgtButton(tb, "&New");
    }

    btnNew->onClick = onToolbarClick;

    snprintf(iconPath, sizeof(iconPath), "%s/open.bmp", sDxeCtx->appDir);
    WidgetT *btnOpen = wgtImageButtonFromFile(tb, iconPath);

    if (!btnOpen) {
        btnOpen = wgtButton(tb, "&Open");
    }

    btnOpen->onClick = onToolbarClick;

    snprintf(iconPath, sizeof(iconPath), "%s/save.bmp", sDxeCtx->appDir);
    WidgetT *btnSave = wgtImageButtonFromFile(tb, iconPath);

    if (!btnSave) {
        btnSave = wgtButton(tb, "&Save");
    }

    btnSave->onClick = onToolbarClick;

    wgtVSeparator(tb);
    WidgetT *btnHelp = wgtButton(tb, "&Help");
    btnHelp->onClick = onToolbarClick;

    wgtLabel(page5tb, "Toolbar with image buttons, text fallback, and VSeparator.");

    // --- Tab 6: Media (Image from file) ---
    WidgetT *page6m = wgtTabPage(tabs, "&Media");

    char samplePath[DVX_MAX_PATH];
    snprintf(samplePath, sizeof(samplePath), "%s/sample.bmp", sDxeCtx->appDir);
    wgtLabel(page6m, "ImageFromFile (sample.bmp):");
    WidgetT *img = wgtImageFromFile(page6m, samplePath);

    if (!img) {
        wgtLabel(page6m, "(File not found)");
    }

    // --- Tab 7: Editor (TextArea, Canvas) ---
    WidgetT *page7e = wgtTabPage(tabs, "&Editor");

    wgtLabel(page7e, "TextArea:");
    WidgetT *ta = wgtTextArea(page7e, 512);
    ta->weight = WGT_WEIGHT_FILL;
    wgtSetText(ta, "Multi-line text editor.\n\nFeatures:\n- Word wrap\n- Selection\n- Copy/Paste\n- Undo (Ctrl+Z)");

    wgtHSeparator(page7e);

    wgtLabel(page7e, "Canvas (draw with mouse):");
    const DisplayT *d = dvxGetDisplay(sAc);
    WidgetT *cv = wgtCanvas(page7e, 280, 80);
    wgtCanvasSetPenColor(cv, packColor(d, 200, 0, 0));
    wgtCanvasDrawRect(cv, 5, 5, 50, 35);
    wgtCanvasSetPenColor(cv, packColor(d, 0, 0, 200));
    wgtCanvasFillCircle(cv, 150, 40, 25);
    wgtCanvasSetPenColor(cv, packColor(d, 0, 150, 0));
    wgtCanvasDrawLine(cv, 70, 5, 130, 70);
    wgtCanvasSetPenColor(cv, packColor(d, 0, 0, 0));
    wgtCanvasSetMouseCallback(cv, onCanvasDraw);

    // --- Tab 8: Splitter ---
    // Demonstrates nested splitters: a horizontal split (top/bottom) where
    // the top pane contains a vertical split (left/right). This creates a
    // three-pane file-explorer layout similar to Windows Explorer.
    WidgetT *page8s = wgtTabPage(tabs, "S&plit");

    WidgetT *hSplit = wgtSplitter(page8s, false);
    hSplit->weight = WGT_WEIGHT_FILL;
    wgtSplitterSetPos(hSplit, 120);

    // Top pane: vertical splitter (tree | list)
    WidgetT *vSplit = wgtSplitter(hSplit, true);
    wgtSplitterSetPos(vSplit, 120);

    // Left pane: multi-select tree view
    WidgetT *leftTree = wgtTreeView(vSplit);
    wgtTreeViewSetMultiSelect(leftTree, true);
    WidgetT *tiFolders = wgtTreeItem(leftTree, "Folders");
    wgtTreeItemSetExpanded(tiFolders, true);
    wgtTreeItem(tiFolders, "Documents");
    wgtTreeItem(tiFolders, "Pictures");
    wgtTreeItem(tiFolders, "Music");
    WidgetT *tiSystem = wgtTreeItem(leftTree, "System");
    wgtTreeItemSetExpanded(tiSystem, true);
    wgtTreeItem(tiSystem, "Config");
    wgtTreeItem(tiSystem, "Drivers");

    // Right pane: list box
    WidgetT *rightList = wgtListBox(vSplit);

    static const char *explorerItems[] = {
        "README.TXT", "SETUP.EXE", "CONFIG.SYS",
        "AUTOEXEC.BAT", "COMMAND.COM", "HIMEM.SYS",
        "EMM386.EXE", "MOUSE.COM"
    };

    wgtListBoxSetItems(rightList, explorerItems, 8);

    // Bottom pane: detail/preview area
    WidgetT *detailFrame = wgtFrame(hSplit, "Preview");
    wgtLabel(detailFrame, "Select a file above to preview.");

    // --- Tab 9: Disabled ---
    // Side-by-side comparison of every widget in enabled vs disabled state.
    // Tests that wgtSetEnabled(w, false) correctly greys out each widget type.
    WidgetT *page9d = wgtTabPage(tabs, "&Disabled");
    WidgetT *disRow = wgtHBox(page9d);
    disRow->weight = WGT_WEIGHT_FILL;

    // Enabled column
    WidgetT *enCol = wgtVBox(disRow);
    enCol->weight = WGT_WEIGHT_FILL;
    wgtLabel(enCol, "Enabled:");
    wgtHSeparator(enCol);
    wgtLabel(enCol, "A &label");
    wgtButton(enCol, "A &button");

    WidgetT *enChk = wgtCheckbox(enCol, "&Check me");
    wgtCheckboxSetChecked(enChk, true);

    WidgetT *enRg = wgtRadioGroup(enCol);
    wgtRadio(enRg, "&Radio 1");
    WidgetT *enR2 = wgtRadio(enRg, "R&adio 2");
    wgtRadioGroupSetSelected(enRg, wgtRadioGetIndex(enR2));

    static const char *disItems[] = {"Alpha", "Beta", "Gamma"};

    WidgetT *enDd = wgtDropdown(enCol);
    wgtDropdownSetItems(enDd, disItems, 3);
    wgtDropdownSetSelected(enDd, 0);

    WidgetT *enSlider = wgtSlider(enCol, 0, 100);
    wgtSliderSetValue(enSlider, 40);

    WidgetT *enProg = wgtProgressBar(enCol);
    wgtProgressBarSetValue(enProg, 60);

    WidgetT *enSpin = wgtSpinner(enCol, 0, 100, 1);
    wgtSpinnerSetValue(enSpin, 42);

    WidgetT *enText = wgtTextInput(enCol, 64);
    wgtSetText(enText, "Editable");

    // Disabled column
    WidgetT *disCol = wgtVBox(disRow);
    disCol->weight = WGT_WEIGHT_FILL;
    wgtLabel(disCol, "Disabled:");
    wgtHSeparator(disCol);

    WidgetT *dLbl = wgtLabel(disCol, "A &label");
    wgtSetEnabled(dLbl, false);

    WidgetT *dBtn = wgtButton(disCol, "A &button");
    wgtSetEnabled(dBtn, false);

    WidgetT *dChk = wgtCheckbox(disCol, "&Check me");
    wgtCheckboxSetChecked(dChk, true);
    wgtSetEnabled(dChk, false);

    WidgetT *dRg = wgtRadioGroup(disCol);
    WidgetT *dR1 = wgtRadio(dRg, "&Radio 1");
    WidgetT *dR2 = wgtRadio(dRg, "R&adio 2");
    wgtRadioGroupSetSelected(dRg, wgtRadioGetIndex(dR2));
    wgtSetEnabled(dR1, false);
    wgtSetEnabled(dR2, false);

    WidgetT *dDd = wgtDropdown(disCol);
    wgtDropdownSetItems(dDd, disItems, 3);
    wgtDropdownSetSelected(dDd, 0);
    wgtSetEnabled(dDd, false);

    WidgetT *dSlider = wgtSlider(disCol, 0, 100);
    wgtSliderSetValue(dSlider, 40);
    wgtSetEnabled(dSlider, false);

    WidgetT *dProg = wgtProgressBar(disCol);
    wgtProgressBarSetValue(dProg, 60);
    wgtSetEnabled(dProg, false);

    WidgetT *dSpin = wgtSpinner(disCol, 0, 100, 1);
    wgtSpinnerSetValue(dSpin, 42);
    wgtSetEnabled(dSpin, false);

    WidgetT *dText = wgtTextInput(disCol, 64);
    wgtSetText(dText, "Read-only");
    wgtSetEnabled(dText, false);

    // Status bar at bottom (outside tabs)
    WidgetT *sb = wgtStatusBar(root);
    WidgetT *sbLabel = wgtLabel(sb, "Ready");
    sbLabel->weight = WGT_WEIGHT_FILL;
    wgtSetName(sbLabel, "advStatus");
    wgtLabel(sb, "Line 1, Col 1");
}


// Creates three windows that demonstrate raw onPaint rendering (no widgets):
//   1. Text info window with full menu bar, accelerators, and context menu
//   2. Color gradient window
//   3. Checkerboard pattern window with scrollbars
static void setupMainWindow(void) {
    WindowT *win1 = dvxCreateWindow(sAc, "DVX Information", 50, 40, 340, 350, true);

    if (win1) {
        win1->onPaint  = onPaintText;
        win1->onClose  = onCloseMainCb;
        win1->onMenu   = onMenuCb;

        MenuBarT *bar = wmAddMenuBar(win1);

        if (bar) {
            MenuT *fileMenu = wmAddMenu(bar, "&File");

            if (fileMenu) {
                wmAddMenuItem(fileMenu, "&New", CMD_FILE_NEW);
                wmAddMenuItem(fileMenu, "&Open...", CMD_FILE_OPEN);
                wmAddMenuItem(fileMenu, "&Save", CMD_FILE_SAVE);
                wmAddMenuSeparator(fileMenu);
                wmAddMenuItem(fileMenu, "E&xit", CMD_FILE_EXIT);
            }

            MenuT *editMenu = wmAddMenu(bar, "&Edit");

            if (editMenu) {
                wmAddMenuItem(editMenu, "Cu&t", CMD_EDIT_CUT);
                wmAddMenuItem(editMenu, "&Copy", CMD_EDIT_COPY);
                wmAddMenuItem(editMenu, "&Paste", CMD_EDIT_PASTE);
            }

            MenuT *viewMenu = wmAddMenu(bar, "&View");

            if (viewMenu) {
                wmAddMenuItem(viewMenu, "&Terminal", CMD_VIEW_TERM);
                wmAddMenuItem(viewMenu, "&Controls", CMD_VIEW_CTRL);
                wmAddMenuSeparator(viewMenu);
                wmAddMenuCheckItem(viewMenu, "Tool&bar", CMD_VIEW_TOOLBAR, true);
                wmAddMenuCheckItem(viewMenu, "&Status Bar", CMD_VIEW_STATUSBAR, true);
                wmAddMenuSeparator(viewMenu);
                wmAddMenuRadioItem(viewMenu, "S&mall", CMD_VIEW_SIZE_SMALL, false);
                wmAddMenuRadioItem(viewMenu, "Me&dium", CMD_VIEW_SIZE_MED, true);
                wmAddMenuRadioItem(viewMenu, "&Large", CMD_VIEW_SIZE_LARGE, false);
                wmAddMenuSeparator(viewMenu);

                MenuT *zoomMenu = wmAddSubMenu(viewMenu, "&Zoom");

                if (zoomMenu) {
                    wmAddMenuItem(zoomMenu, "Zoom &In", CMD_VIEW_ZOOM_IN);
                    wmAddMenuItem(zoomMenu, "Zoom &Out", CMD_VIEW_ZOOM_OUT);
                    wmAddMenuSeparator(zoomMenu);
                    wmAddMenuItem(zoomMenu, "&Fit to Window", CMD_VIEW_ZOOM_FIT);
                }

                wmAddMenuSeparator(viewMenu);
                wmAddMenuCheckItem(viewMenu, "&Debug Layout", CMD_VIEW_DEBUG_LYT, false);
            }

            MenuT *winMenu = wmAddMenu(bar, "&Window");

            if (winMenu) {
                wmAddMenuItem(winMenu, "&Cascade", CMD_WIN_CASCADE);
                wmAddMenuItem(winMenu, "Tile &Grid", CMD_WIN_TILE);
                wmAddMenuItem(winMenu, "Tile &Horizontal", CMD_WIN_TILE_H);
                wmAddMenuItem(winMenu, "Tile &Vertical", CMD_WIN_TILE_V);
            }

            MenuT *helpMenu = wmAddMenu(bar, "&Help");

            if (helpMenu) {
                wmAddMenuItem(helpMenu, "&About...", CMD_HELP_ABOUT);
            }
        }

        // Accelerator table: keyboard shortcuts that work even without the
        // menu being open. The WM dispatches these via the window's onMenu
        // callback, making them indistinguishable from menu clicks.
        sAccel = dvxCreateAccelTable();
        dvxAddAccel(sAccel, 'N', ACCEL_CTRL, CMD_FILE_NEW);
        dvxAddAccel(sAccel, 'O', ACCEL_CTRL, CMD_FILE_OPEN);
        dvxAddAccel(sAccel, 'S', ACCEL_CTRL, CMD_FILE_SAVE);
        dvxAddAccel(sAccel, 'Q', ACCEL_CTRL, CMD_FILE_EXIT);
        dvxAddAccel(sAccel, KEY_F1, 0, CMD_HELP_ABOUT);
        win1->accelTable = sAccel;

        // Right-click context menu, attached at the window level. If a widget
        // has its own contextMenu it takes priority; otherwise this one fires.
        sWinCtxMenu = wmCreateMenu();
        wmAddMenuItem(sWinCtxMenu, "Cu&t", CMD_CTX_CUT);
        wmAddMenuItem(sWinCtxMenu, "&Copy", CMD_CTX_COPY);
        wmAddMenuItem(sWinCtxMenu, "&Paste", CMD_CTX_PASTE);
        wmAddMenuSeparator(sWinCtxMenu);
        wmAddMenuItem(sWinCtxMenu, "&Properties...", CMD_CTX_PROPS);
        win1->contextMenu = sWinCtxMenu;

        // For raw-paint windows (no widget tree), we must manually update
        // the content rect and allocate the content buffer after adding
        // menu bars or scrollbars, since those shrink the content area.
        // Then we do an initial paint to fill the buffer before it's composited.
        wmUpdateContentRect(win1);
        wmReallocContentBuf(win1, &sAc->display);

        RectT fullRect = {0, 0, win1->contentW, win1->contentH};
        win1->onPaint(win1, &fullRect);
    }

    // Window 2: Color gradient
    WindowT *win2 = dvxCreateWindow(sAc, "Color Gradient", 200, 100, 280, 250, true);

    if (win2) {
        win2->onPaint = onPaintColor;
        win2->onClose = onCloseCb;

        RectT fullRect = {0, 0, win2->contentW, win2->contentH};
        win2->onPaint(win2, &fullRect);
    }

    // Window 3: Checkerboard pattern with scrollbars
    WindowT *win3 = dvxCreateWindow(sAc, "Pattern", 400, 150, 250, 220, true);

    if (win3) {
        win3->onPaint = onPaintPattern;
        win3->onClose = onCloseCb;

        wmAddVScrollbar(win3, 0, 100, 25);
        wmAddHScrollbar(win3, 0, 100, 25);
        wmUpdateContentRect(win3);
        wmReallocContentBuf(win3, &sAc->display);

        RectT fullRect = {0, 0, win3->contentW, win3->contentH};
        win3->onPaint(win3, &fullRect);
    }
}


// Creates an ANSI terminal widget with sample output demonstrating all
// supported attributes (bold, reverse, blink), 8+8 colors, background
// colors, and CP437 box-drawing/graphic characters. The terminal is
// minimized on creation to avoid cluttering the initial demo view.
static void setupTerminalWindow(void) {
    WindowT *win = dvxCreateWindow(sAc, "ANSI Terminal", 60, 60, 660, 420, true);

    if (!win) {
        return;
    }

    win->onClose = onCloseCb;

    WidgetT *root = wgtInitWindow(sAc, win);
    WidgetT *term = wgtAnsiTerm(root, 80, 25);

    term->weight = WGT_WEIGHT_FILL;
    wgtAnsiTermSetScrollback(term, 500);

    // Feed some ANSI content to demonstrate the terminal
    static const uint8_t ansiDemo[] =
        "\x1B[2J"                                   // clear screen
        "\x1B[1;34m========================================\r\n"
        "  DVX ANSI Terminal Emulator\r\n"
        "========================================\x1B[0m\r\n"
        "\r\n"
        "\x1B[1mBold text\x1B[0m, "
        "\x1B[7mReverse\x1B[0m, "
        "\x1B[5mBlinking\x1B[0m\r\n"
        "\r\n"
        "Standard colors:\r\n"
        "  \x1B[30m\x1B[47m Black \x1B[0m"
        " \x1B[31m Red \x1B[0m"
        " \x1B[32m Green \x1B[0m"
        " \x1B[33m Yellow \x1B[0m"
        " \x1B[34m Blue \x1B[0m"
        " \x1B[35m Magenta \x1B[0m"
        " \x1B[36m Cyan \x1B[0m"
        " \x1B[37m White \x1B[0m\r\n"
        "\r\n"
        "Bright colors:\r\n"
        "  \x1B[1;30m\x1B[47m DkGray \x1B[0m"
        " \x1B[1;31m LtRed \x1B[0m"
        " \x1B[1;32m LtGreen \x1B[0m"
        " \x1B[1;33m LtYellow \x1B[0m"
        " \x1B[1;34m LtBlue \x1B[0m"
        " \x1B[1;35m LtMagenta \x1B[0m"
        " \x1B[1;36m LtCyan \x1B[0m"
        " \x1B[1;37m BrWhite \x1B[0m\r\n"
        "\r\n"
        "Background colors:\r\n"
        "  \x1B[40m\x1B[37m Black \x1B[0m"
        " \x1B[41m\x1B[37m Red \x1B[0m"
        " \x1B[42m\x1B[30m Green \x1B[0m"
        " \x1B[43m\x1B[30m Yellow \x1B[0m"
        " \x1B[44m\x1B[37m Blue \x1B[0m"
        " \x1B[45m\x1B[37m Magenta \x1B[0m"
        " \x1B[46m\x1B[30m Cyan \x1B[0m"
        " \x1B[47m\x1B[30m White \x1B[0m\r\n"
        "\r\n"
        "CP437 graphics: "
        "\x01\x02\x03\x04\x05\x06"     // smileys, hearts, diamonds, clubs, spades
        " \xB0\xB1\xB2\xDB"            // shade blocks
        " \xC4\xC5\xB3\xDA\xBF\xC0\xD9" // box drawing
        "\r\n"
        "\r\n"
        "\x1B[1;32mTerminal ready.\x1B[0m "
        "\x1B[36m(Not connected to any host)\x1B[0m\r\n";

    wgtAnsiTermWrite(term, ansiDemo, (int32_t)(sizeof(ansiDemo) - 1));

    WidgetT *sb = wgtStatusBar(root);
    wgtLabel(sb, "80x25 [Local]");

    dvxFitWindow(sAc, win);
    dvxMinimizeWindow(sAc, win);
}


// Demonstrates the standard form pattern: labeled inputs in frames, checkbox
// and radio groups, list boxes (single and multi-select with context menus),
// and a button row. The '&' in label text marks the Alt+key mnemonic that
// moves focus to the associated widget  -- this is the accelerator mechanism.
static void setupWidgetDemo(void) {
    WindowT *win = dvxCreateWindow(sAc, "Widget Demo", 80, 200, 280, 360, true);

    if (!win) {
        return;
    }

    win->onClose = onCloseCb;
    win->onMenu  = onMenuCb;

    WidgetT *root = wgtInitWindow(sAc, win);

    // Status label at top
    WidgetT *status = wgtLabel(root, "Ready.");
    wgtSetName(status, "status");

    wgtHSeparator(root);

    // Frame with text input
    WidgetT *frame = wgtFrame(root, "&User Input");
    WidgetT *row1 = wgtHBox(frame);
    wgtLabel(row1, "&Name:");
    wgtTextInput(row1, 64);

    WidgetT *row2 = wgtHBox(frame);
    wgtLabel(row2, "&Password:");
    wgtPasswordInput(row2, 32);

    WidgetT *row3 = wgtHBox(frame);
    wgtLabel(row3, "P&hone:");
    wgtMaskedInput(row3, "(###) ###-####");

    wgtHSeparator(root);

    // Checkboxes
    wgtCheckbox(root, "Enable feature &A");
    wgtCheckbox(root, "Enable feature &B");

    wgtHSeparator(root);

    // Radio buttons
    WidgetT *rg = wgtRadioGroup(root);
    wgtRadio(rg, "Option &1");
    wgtRadio(rg, "Option &2");
    wgtRadio(rg, "Option &3");

    wgtHSeparator(root);

    // List boxes
    static const char *listItems[] = {"Alpha", "Beta", "Gamma", "Delta", "Epsilon"};
    static const char *multiItems[] = {"Red", "Orange", "Yellow", "Green", "Blue", "Indigo", "Violet"};
    WidgetT *listRow = wgtHBox(root);
    wgtLabel(listRow, "&Items:");
    WidgetT *lb = wgtListBox(listRow);
    wgtListBoxSetItems(lb, listItems, 5);
    wgtListBoxSetReorderable(lb, true);
    lb->weight = WGT_WEIGHT_FILL;

    // Context menu on the list box
    sLbCtxMenu = wmCreateMenu();
    wmAddMenuItem(sLbCtxMenu, "Cu&t", CMD_CTX_CUT);
    wmAddMenuItem(sLbCtxMenu, "&Copy", CMD_CTX_COPY);
    wmAddMenuItem(sLbCtxMenu, "&Paste", CMD_CTX_PASTE);
    wmAddMenuSeparator(sLbCtxMenu);
    wmAddMenuItem(sLbCtxMenu, "&Delete", CMD_CTX_DELETE);
    wmAddMenuSeparator(sLbCtxMenu);
    wmAddMenuItem(sLbCtxMenu, "Select &All", CMD_CTX_SELALL);
    lb->contextMenu = sLbCtxMenu;
    wgtLabel(listRow, "&Multi:");
    WidgetT *mlb = wgtListBox(listRow);
    wgtListBoxSetMultiSelect(mlb, true);
    wgtListBoxSetItems(mlb, multiItems, 7);
    mlb->weight = WGT_WEIGHT_FILL;

    wgtHSeparator(root);

    // Button row at bottom
    WidgetT *btnRow = wgtHBox(root);
    btnRow->align = AlignEndE;
    WidgetT *okBtn = wgtButton(btnRow, "&OK");
    okBtn->onClick = onOkClick;
    wgtButton(btnRow, "&Cancel");
}


// Create all demo windows at once. Because this is a callback-only app,
// appMain returns immediately and the shell's event loop takes over.
// Each window is self-contained with its own callbacks.
int32_t appMain(DxeAppContextT *ctx) {
    sDxeCtx = ctx;
    sAc     = ctx->shellCtx;

    setupMainWindow();
    setupWidgetDemo();
    setupControlsWindow();
    setupTerminalWindow();

    return 0;
}
