// ctrlpanel.c  -- DVX Control Panel
//
// System configuration app with four tabs:
//   Mouse     -- scroll direction, double-click speed, acceleration
//   Colors    -- all 18 system colors, theme load/save
//   Desktop   -- wallpaper image (stretch mode)
//   Video     -- resolution and color depth
//
// Changes preview live.  OK saves to DVX.INI, Cancel reverts to the
// state captured when the control panel was opened.

#include "dvxApp.h"
#include "dvxDialog.h"
#include "dvxPrefs.h"
#include "dvxWidget.h"
#include "widgetBox.h"
#include "widgetButton.h"
#include "widgetCanvas.h"
#include "widgetDropdown.h"
#include "widgetLabel.h"
#include "widgetListBox.h"
#include "widgetSlider.h"
#include "widgetSpacer.h"
#include "widgetTabControl.h"
#include "dvxWm.h"
#include "dvxPlatform.h"
#include "shellApp.h"
#include "stb_ds_wrap.h"

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include "dvxMem.h"

// ============================================================
// Constants
// ============================================================

#define CP_WIN_W           460
#define CP_WIN_H           340
#define CP_SPACING         8
#define CP_SPACING_SMALL   4
#define CP_THEME_BTN_W     60
#define CP_SAVE_BTN_W      70
#define CP_SWATCH_W        64
#define CP_SWATCH_H        24
#define CP_DBLCLICK_LBL_W  52
#define CP_TEST_BTN_W      100
#define CP_APPLY_BTN_W     100
#define CP_OK_CANCEL_BTN_W 80
#define CP_CONFIRM_W       300
#define CP_CONFIRM_H       100
#define CP_CONFIRM_BTN_W   70
#define CP_WPAPER_BTN_W    90
#define WPAPER_DIR         "CONFIG/WPAPER"
#define THEME_DIR        "CONFIG/THEMES"
#define THEME_EXT        ".THM"

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

AppDescriptorT appDescriptor = {
    .name          = "Control Panel",
    .hasMainLoop   = false,
    .multiInstance = false,
    .stackSize     = SHELL_STACK_DEFAULT,
    .priority      = TS_PRIORITY_NORMAL
};

// ============================================================
// Entry types for dynamic lists
// ============================================================

typedef struct {
    char name[64];
    char path[DVX_MAX_PATH];
} FileEntryT;

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

static DxeAppContextT *sCtx   = NULL;
static AppContextT    *sAc    = NULL;
static WindowT        *sWin   = NULL;
static PrefsHandleT  *sPrefs  = NULL;

// Saved state for Cancel
static uint8_t  sSavedColorRgb[ColorCountE][3];
static int32_t  sSavedWheelDir;
static int32_t  sSavedDblClick;
static int32_t  sSavedAccel;
static int32_t  sSavedVideoW;
static int32_t  sSavedVideoH;
static int32_t       sSavedVideoBpp;
static WallpaperModeE sSavedWpMode;

// Mouse tab widgets
static WidgetT *sWheelDrop     = NULL;
static WidgetT *sDblClickSldr  = NULL;
static WidgetT *sDblClickLbl   = NULL;
static WidgetT *sAccelDrop     = NULL;
static WidgetT *sDblTestLbl    = NULL;

// Colors tab widgets
static WidgetT *sColorList     = NULL;
static WidgetT *sRedSldr       = NULL;
static WidgetT *sGreenSldr     = NULL;
static WidgetT *sBlueSldr      = NULL;
static WidgetT *sRedLbl        = NULL;
static WidgetT *sGreenLbl      = NULL;
static WidgetT *sBlueLbl       = NULL;
static WidgetT *sColorSwatch   = NULL;

// Desktop tab widgets
static WidgetT         *sWallpaperLbl  = NULL;
static WidgetT         *sWpaperList    = NULL;
static WidgetT         *sWpModeDrop    = NULL;
static char             sWallpaperPath[DVX_MAX_PATH];
static FileEntryT      *sWpaperEntries = NULL;  // stb_ds dynamic array
static const char     **sWpaperLabels  = NULL;  // stb_ds dynamic array

// Video tab
static const VideoModeInfoT *sVideoModes = NULL;  // points into ctx, not owned
static int32_t               sVideoCount = 0;
static const char          **sVideoLabels = NULL;  // stb_ds dynamic array
static WidgetT              *sVideoList   = NULL;

// Theme list
static FileEntryT      *sThemeEntries  = NULL;  // stb_ds dynamic array
static const char     **sThemeLabels   = NULL;  // stb_ds dynamic array
static WidgetT         *sThemeList     = NULL;

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

int32_t     appMain(DxeAppContextT *ctx);
static void buildMouseTab(WidgetT *page);
static void buildColorsTab(WidgetT *page);
static void buildDesktopTab(WidgetT *page);
static void buildVideoTab(WidgetT *page);
static int32_t mapAccelName(const char *name);
static const char *mapAccelValue(int32_t val);
static void onAccelChange(WidgetT *w);
static void onApplyTheme(WidgetT *w);
static void onBrowseTheme(WidgetT *w);
static void onResetColors(WidgetT *w);
static void onApplyWallpaper(WidgetT *w);
static void onWallpaperMode(WidgetT *w);
static void onCancel(WidgetT *w);
static void onChooseWallpaper(WidgetT *w);
static void onClearWallpaper(WidgetT *w);
static void onClose(WindowT *win);
static void onColorSelect(WidgetT *w);
static void onColorSlider(WidgetT *w);
static void onDblClickSlider(WidgetT *w);
static void onDblClickTest(WidgetT *w);
static void onOk(WidgetT *w);
static void onSaveTheme(WidgetT *w);
static void onVideoApply(WidgetT *w);
static void onWheelChange(WidgetT *w);
static void saveSnapshot(void);
static void restoreSnapshot(void);
static void scanWallpapers(void);
static void scanThemes(void);
static void updateColorSliders(void);
static void updateDblClickLabel(void);
static void updateSwatch(void);


// ============================================================
// buildColorsTab
// ============================================================

static void buildColorsTab(WidgetT *page) {
    // Color list on the left, sliders on the right
    WidgetT *hbox = wgtHBox(page);
    hbox->weight  = 100;
    hbox->spacing = wgtPixels(CP_SPACING);

    // Left side: color name list + theme controls
    WidgetT *leftVbox = wgtVBox(hbox);
    leftVbox->weight  = 50;
    leftVbox->spacing = wgtPixels(CP_SPACING_SMALL);

    wgtLabel(leftVbox, "System Colors:");

    // Build color name list
    static const char *colorNames[ColorCountE];

    for (int32_t i = 0; i < ColorCountE; i++) {
        colorNames[i] = dvxColorLabel((ColorIdE)i);
    }

    sColorList = wgtListBox(leftVbox);
    sColorList->weight   = 100;
    sColorList->onChange  = onColorSelect;
    wgtListBoxSetItems(sColorList, colorNames, ColorCountE);
    wgtListBoxSetSelected(sColorList, 0);

    // Theme row
    WidgetT *themeRow = wgtHBox(leftVbox);
    themeRow->spacing = wgtPixels(CP_SPACING_SMALL);

    wgtLabel(themeRow, "Theme:");

    scanThemes();
    sThemeList = wgtDropdown(themeRow);
    sThemeList->weight = 100;
    wgtDropdownSetItems(sThemeList, sThemeLabels, arrlen(sThemeEntries));

    WidgetT *loadBtn = wgtButton(themeRow, "Apply");
    loadBtn->onClick = onApplyTheme;
    loadBtn->prefW   = wgtPixels(CP_THEME_BTN_W);

    WidgetT *browseBtn = wgtButton(themeRow, "Load...");
    browseBtn->onClick = onBrowseTheme;
    browseBtn->prefW   = wgtPixels(CP_THEME_BTN_W);

    WidgetT *saveBtn = wgtButton(themeRow, "Save As...");
    saveBtn->onClick = onSaveTheme;
    saveBtn->prefW   = wgtPixels(CP_SAVE_BTN_W);

    WidgetT *resetBtn = wgtButton(themeRow, "Reset");
    resetBtn->onClick = onResetColors;
    resetBtn->prefW   = wgtPixels(CP_THEME_BTN_W);

    // Right side: RGB sliders in a non-weighted inner box so they stay
    // at natural size.  The outer box absorbs extra vertical space.
    WidgetT *rightVbox = wgtVBox(hbox);
    rightVbox->weight  = 50;

    WidgetT *sliderBox = wgtVBox(rightVbox);
    sliderBox->spacing = wgtPixels(CP_SPACING_SMALL);

    wgtLabel(sliderBox, "Red:");
    sRedSldr = wgtSlider(sliderBox, 0, 255);
    sRedSldr->onChange = onColorSlider;
    sRedLbl = wgtLabel(sliderBox, "0");
    wgtLabelSetAlign(sRedLbl, AlignEndE);

    wgtLabel(sliderBox, "Green:");
    sGreenSldr = wgtSlider(sliderBox, 0, 255);
    sGreenSldr->onChange = onColorSlider;
    sGreenLbl = wgtLabel(sliderBox, "0");
    wgtLabelSetAlign(sGreenLbl, AlignEndE);

    wgtLabel(sliderBox, "Blue:");
    sBlueSldr = wgtSlider(sliderBox, 0, 255);
    sBlueSldr->onChange = onColorSlider;
    sBlueLbl = wgtLabel(sliderBox, "0");
    wgtLabelSetAlign(sBlueLbl, AlignEndE);

    wgtLabel(sliderBox, "Preview:");
    sColorSwatch = wgtCanvas(sliderBox, CP_SWATCH_W, CP_SWATCH_H);

    // Absorb remaining vertical space below the sliders
    wgtSpacer(rightVbox)->weight = 100;

    updateColorSliders();
}


// ============================================================
// buildDesktopTab
// ============================================================

static void buildDesktopTab(WidgetT *page) {
    wgtLabel(page, "Wallpaper:");

    scanWallpapers();
    sWpaperList = wgtListBox(page);
    sWpaperList->weight     = 100;
    sWpaperList->onDblClick = onApplyWallpaper;
    wgtListBoxSetItems(sWpaperList, sWpaperLabels, arrlen(sWpaperEntries));

    sWallpaperLbl = wgtLabel(page, sWallpaperPath[0] ? sWallpaperPath : "(none)");

    WidgetT *btnRow = wgtHBox(page);
    btnRow->spacing = wgtPixels(CP_SPACING);

    WidgetT *applyBtn = wgtButton(btnRow, "Apply");
    applyBtn->onClick = onApplyWallpaper;
    applyBtn->prefW   = wgtPixels(CP_WPAPER_BTN_W);

    WidgetT *chooseBtn = wgtButton(btnRow, "Browse...");
    chooseBtn->onClick = onChooseWallpaper;
    chooseBtn->prefW   = wgtPixels(CP_WPAPER_BTN_W);

    WidgetT *clearBtn = wgtButton(btnRow, "Clear");
    clearBtn->onClick  = onClearWallpaper;
    clearBtn->prefW    = wgtPixels(CP_WPAPER_BTN_W);

    wgtSpacer(btnRow)->weight = 100;
    wgtLabel(btnRow, "Mode:");

    static const char *modeItems[] = {"Stretch", "Tile", "Center"};
    sWpModeDrop = wgtDropdown(btnRow);
    sWpModeDrop->onChange = onWallpaperMode;
    wgtDropdownSetItems(sWpModeDrop, modeItems, 3);
    wgtDropdownSetSelected(sWpModeDrop, (int32_t)sAc->wallpaperMode);
}


// ============================================================
// buildMouseTab
// ============================================================

static void buildMouseTab(WidgetT *page) {
    page->spacing = wgtPixels(6);

    // Scroll direction
    WidgetT *wheelRow = wgtHBox(page);
    wheelRow->spacing = wgtPixels(CP_SPACING);
    wgtLabel(wheelRow, "Scroll Wheel:");

    static const char *wheelItems[] = {"Normal", "Reversed"};
    sWheelDrop = wgtDropdown(wheelRow);
    sWheelDrop->weight  = 100;
    sWheelDrop->onChange = onWheelChange;
    wgtDropdownSetItems(sWheelDrop, wheelItems, 2);
    wgtDropdownSetSelected(sWheelDrop, sAc->wheelDirection < 0 ? 1 : 0);

    wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL);

    // Double-click speed
    wgtLabel(page, "Double-Click Speed:");

    WidgetT *dblRow = wgtHBox(page);
    dblRow->spacing = wgtPixels(CP_SPACING);

    wgtLabel(dblRow, "Fast");
    sDblClickSldr = wgtSlider(dblRow, 200, 900);
    sDblClickSldr->weight   = 100;
    sDblClickSldr->onChange  = onDblClickSlider;

    int32_t dblMs = prefsGetInt(sPrefs, "mouse", "doubleclick", 500);
    wgtSliderSetValue(sDblClickSldr, dblMs);
    wgtLabel(dblRow, "Slow  ");
    sDblClickLbl = wgtLabel(dblRow, "");
    sDblClickLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W);
    updateDblClickLabel();

    wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL);

    // Acceleration
    WidgetT *accelRow = wgtHBox(page);
    accelRow->spacing = wgtPixels(CP_SPACING);
    wgtLabel(accelRow, "Acceleration:");

    static const char *accelItems[] = {"Off", "Low", "Medium", "High"};
    sAccelDrop = wgtDropdown(accelRow);
    sAccelDrop->weight  = 100;
    sAccelDrop->onChange = onAccelChange;
    wgtDropdownSetItems(sAccelDrop, accelItems, 4);

    const char *accelStr = prefsGetString(sPrefs, "mouse", "acceleration", "medium");

    if (strcmp(accelStr, "off") == 0) {
        wgtDropdownSetSelected(sAccelDrop, 0);
    } else if (strcmp(accelStr, "low") == 0) {
        wgtDropdownSetSelected(sAccelDrop, 1);
    } else if (strcmp(accelStr, "high") == 0) {
        wgtDropdownSetSelected(sAccelDrop, 3);
    } else {
        wgtDropdownSetSelected(sAccelDrop, 2);
    }

    wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL);

    // Double-click test area
    WidgetT *testRow = wgtHBox(page);
    testRow->spacing = wgtPixels(CP_SPACING);

    WidgetT *testBtn = wgtButton(testRow, "Test Area");
    testBtn->onDblClick = onDblClickTest;
    testBtn->prefW      = wgtPixels(CP_TEST_BTN_W);

    sDblTestLbl = wgtLabel(testRow, "Double-click the button to test");
}


// ============================================================
// buildVideoTab
// ============================================================

static void buildVideoTab(WidgetT *page) {
    wgtLabel(page, "Available Video Modes:");

    // Read modes enumerated at init time
    sVideoModes = dvxGetVideoModes(sAc, &sVideoCount);

    // Build label strings
    arrsetlen(sVideoLabels, 0);
    static char (*labelBufs)[48] = NULL;
    arrfree(labelBufs);
    labelBufs = NULL;
    arrsetlen(labelBufs, sVideoCount);

    for (int32_t i = 0; i < sVideoCount; i++) {
        const char *depthName;

        switch (sVideoModes[i].bpp) {
            case 8:  depthName = "256 colors";          break;
            case 15: depthName = "32 thousand colors";   break;
            case 16: depthName = "65 thousand colors";   break;
            case 24: depthName = "16 million colors";    break;
            case 32: depthName = "16 million colors+";   break;
            default: depthName = "";                     break;
        }

        snprintf(labelBufs[i], 48, "%ldx%ld %s", (long)sVideoModes[i].w, (long)sVideoModes[i].h, depthName);
        arrput(sVideoLabels, (const char *)labelBufs[i]);
    }

    sVideoList = wgtListBox(page);
    sVideoList->weight     = 100;
    sVideoList->onDblClick = onVideoApply;
    wgtListBoxSetItems(sVideoList, sVideoLabels, sVideoCount);

    // Select the current mode
    for (int32_t i = 0; i < sVideoCount; i++) {
        if (sVideoModes[i].w == sAc->display.width &&
            sVideoModes[i].h == sAc->display.height &&
            sVideoModes[i].bpp == sAc->display.format.bitsPerPixel) {
            wgtListBoxSetSelected(sVideoList, i);
            break;
        }
    }

    WidgetT *applyBtn = wgtButton(page, "Apply Mode");
    applyBtn->onClick = onVideoApply;
    applyBtn->prefW   = wgtPixels(CP_APPLY_BTN_W);
}



// ============================================================
// mapAccelName / mapAccelValue
// ============================================================

static int32_t mapAccelName(const char *name) {
    if (strcmp(name, "off") == 0)    return 10000;
    if (strcmp(name, "low") == 0)    return 100;
    if (strcmp(name, "medium") == 0) return 64;
    if (strcmp(name, "high") == 0)   return 32;
    return 64;
}


static const char *mapAccelValue(int32_t idx) {
    switch (idx) {
        case 0:  return "off";
        case 1:  return "low";
        case 2:  return "medium";
        case 3:  return "high";
        default: return "medium";
    }
}


// ============================================================
// Callbacks  -- Mouse tab
// ============================================================

static void onWheelChange(WidgetT *w) {
    int32_t sel = wgtDropdownGetSelected(w);
    int32_t dir = (sel == 1) ? -1 : 1;
    int32_t dbl = wgtSliderGetValue(sDblClickSldr);

    const char *accelName = mapAccelValue(wgtDropdownGetSelected(sAccelDrop));
    int32_t     accelVal  = mapAccelName(accelName);

    dvxSetMouseConfig(sAc, dir, dbl, accelVal);
}


static void onDblClickSlider(WidgetT *w) {
    (void)w;
    updateDblClickLabel();

    int32_t dir = (wgtDropdownGetSelected(sWheelDrop) == 1) ? -1 : 1;
    int32_t dbl = wgtSliderGetValue(sDblClickSldr);

    const char *accelName = mapAccelValue(wgtDropdownGetSelected(sAccelDrop));
    int32_t     accelVal  = mapAccelName(accelName);

    dvxSetMouseConfig(sAc, dir, dbl, accelVal);
}


static void onDblClickTest(WidgetT *w) {
    (void)w;
    static int32_t count = 0;
    count++;

    static char buf[48];
    snprintf(buf, sizeof(buf), "Double-click detected! (%ld)", (long)count);
    wgtSetText(sDblTestLbl, buf);
}


static void onAccelChange(WidgetT *w) {
    int32_t dir = (wgtDropdownGetSelected(sWheelDrop) == 1) ? -1 : 1;
    int32_t dbl = wgtSliderGetValue(sDblClickSldr);

    const char *accelName = mapAccelValue(wgtDropdownGetSelected(w));
    int32_t     accelVal  = mapAccelName(accelName);

    dvxSetMouseConfig(sAc, dir, dbl, accelVal);
}


// ============================================================
// Callbacks  -- Colors tab
// ============================================================

static void onColorSelect(WidgetT *w) {
    (void)w;
    updateColorSliders();
}


static void onColorSlider(WidgetT *w) {
    (void)w;

    int32_t sel = wgtListBoxGetSelected(sColorList);

    if (sel < 0 || sel >= ColorCountE) {
        return;
    }

    uint8_t r = (uint8_t)wgtSliderGetValue(sRedSldr);
    uint8_t g = (uint8_t)wgtSliderGetValue(sGreenSldr);
    uint8_t b = (uint8_t)wgtSliderGetValue(sBlueSldr);

    // Update label text
    static char rBuf[8];
    static char gBuf[8];
    static char bBuf[8];
    snprintf(rBuf, sizeof(rBuf), "%d", r);
    snprintf(gBuf, sizeof(gBuf), "%d", g);
    snprintf(bBuf, sizeof(bBuf), "%d", b);
    wgtSetText(sRedLbl, rBuf);
    wgtSetText(sGreenLbl, gBuf);
    wgtSetText(sBlueLbl, bBuf);

    dvxSetColor(sAc, (ColorIdE)sel, r, g, b);
    updateSwatch();
}


static void onApplyTheme(WidgetT *w) {
    (void)w;

    int32_t sel = wgtDropdownGetSelected(sThemeList);

    if (sel < 0 || sel >= arrlen(sThemeEntries)) {
        return;
    }

    dvxLoadTheme(sAc, sThemeEntries[sel].path);
    updateColorSliders();
}


static void onBrowseTheme(WidgetT *w) {
    (void)w;

    FileFilterT filters[] = {
        { "Theme Files (*.thm)", "*.thm" },
        { "All Files (*.*)", "*.*" }
    };
    char path[DVX_MAX_PATH];

    if (dvxFileDialog(sAc, "Load Theme", FD_OPEN, THEME_DIR, filters, 2, path, sizeof(path))) {
        dvxLoadTheme(sAc, path);
        updateColorSliders();
    }
}


static void onSaveTheme(WidgetT *w) {
    (void)w;

    FileFilterT filters[] = {
        { "Theme Files (*.thm)", "*.thm" },
        { "All Files (*.*)", "*.*" }
    };
    char path[DVX_MAX_PATH];

    if (dvxFileDialog(sAc, "Save Theme", FD_SAVE, THEME_DIR, filters, 2, path, sizeof(path))) {
        dvxSaveTheme(sAc, path);
        scanThemes();
        wgtDropdownSetItems(sThemeList, sThemeLabels, arrlen(sThemeEntries));
    }
}


static void onResetColors(WidgetT *w) {
    (void)w;
    dvxResetColorScheme(sAc);
    updateColorSliders();
}


// ============================================================
// Callbacks  -- Desktop tab
// ============================================================

static void onApplyWallpaper(WidgetT *w) {
    (void)w;

    int32_t sel = wgtListBoxGetSelected(sWpaperList);

    if (sel < 0 || sel >= arrlen(sWpaperEntries)) {
        return;
    }

    if (dvxSetWallpaper(sAc, sWpaperEntries[sel].path)) {
        snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sWpaperEntries[sel].path);
        wgtSetText(sWallpaperLbl, sWallpaperPath);
    } else {
        dvxMessageBox(sAc, "Error", "Could not load wallpaper image.", MB_OK | MB_ICONERROR);
    }
}


static void onChooseWallpaper(WidgetT *w) {
    (void)w;

    FileFilterT filters[] = {
        { "Images (*.bmp;*.jpg;*.png)", "*.bmp;*.jpg;*.png" },
        { "All Files (*.*)", "*.*" }
    };
    char path[DVX_MAX_PATH];

    if (dvxFileDialog(sAc, "Choose Wallpaper", FD_OPEN, "CONFIG/WPAPER", filters, 2, path, sizeof(path))) {
        if (dvxSetWallpaper(sAc, path)) {
            strncpy(sWallpaperPath, path, sizeof(sWallpaperPath) - 1);
            sWallpaperPath[sizeof(sWallpaperPath) - 1] = '\0';
            wgtSetText(sWallpaperLbl, sWallpaperPath);
        } else {
            dvxMessageBox(sAc, "Error", "Could not load wallpaper image.", MB_OK | MB_ICONERROR);
        }
    }
}


static void onClearWallpaper(WidgetT *w) {
    (void)w;
    dvxSetWallpaper(sAc, NULL);
    sWallpaperPath[0] = '\0';
    wgtSetText(sWallpaperLbl, "(none)");
}


static void onWallpaperMode(WidgetT *w) {
    int32_t sel = wgtDropdownGetSelected(w);

    if (sel >= 0 && sel <= 2) {
        dvxSetWallpaperMode(sAc, (WallpaperModeE)sel);
    }
}


// ============================================================
// Callbacks  -- Video tab
// ============================================================

static int32_t sVideoConfirmResult = -1;

static void onVideoConfirmYes(WidgetT *w) {
    (void)w;
    sVideoConfirmResult = 1;
}


static void onVideoConfirmNo(WidgetT *w) {
    (void)w;
    sVideoConfirmResult = 0;
}


static void onVideoConfirmClose(WindowT *win) {
    (void)win;
    sVideoConfirmResult = 0;
}


static void onVideoApply(WidgetT *w) {
    (void)w;

    // Guard against re-entrancy: the confirmation dialog runs a nested
    // dvxUpdate loop which can process pending events that call us again.
    static bool sInProgress = false;

    if (sInProgress) {
        return;
    }

    sInProgress = true;

    int32_t sel = wgtListBoxGetSelected(sVideoList);

    if (sel < 0 || sel >= sVideoCount) {
        sInProgress = false;
        return;
    }

    const VideoModeInfoT *m = &sVideoModes[sel];

    int32_t oldW   = sAc->display.width;
    int32_t oldH   = sAc->display.height;
    int32_t oldBpp = sAc->display.format.bitsPerPixel;

    if (m->w == oldW && m->h == oldH && m->bpp == oldBpp) {
        sInProgress = false;
        return;
    }

    if (dvxChangeVideoMode(sAc, m->w, m->h, m->bpp) != 0) {
        dvxMessageBox(sAc, "Error", "Failed to change video mode.", MB_OK | MB_ICONERROR);
        sInProgress = false;
        return;
    }

    // Confirmation with 10-second auto-revert countdown.  Run a nested
    // event loop that updates the dialog title each second.  If the user
    // clicks Yes the mode is kept; No or timeout reverts to the old mode.
    #define CONFIRM_SECONDS 10

    WindowT *confirmWin = dvxCreateWindowCentered(sAc, "Keep this mode?", CP_CONFIRM_W, CP_CONFIRM_H, false);

    if (!confirmWin) {
        dvxChangeVideoMode(sAc, oldW, oldH, oldBpp);
        sInProgress = false;
        return;
    }

    confirmWin->modal   = true;
    confirmWin->onClose = onVideoConfirmClose;
    sAc->modalWindow    = confirmWin;

    WidgetT *root = wgtInitWindow(sAc, confirmWin);
    root->spacing = wgtPixels(CP_SPACING);

    static char msgBuf[80];
    snprintf(msgBuf, sizeof(msgBuf), "Reverting in %d seconds...", CONFIRM_SECONDS);
    WidgetT *msgLbl = wgtLabel(root, msgBuf);

    WidgetT *btnRow = wgtHBox(root);
    btnRow->align   = AlignEndE;
    btnRow->spacing = wgtPixels(CP_SPACING);

    WidgetT *yesBtn = wgtButton(btnRow, "Yes");
    yesBtn->prefW   = wgtPixels(CP_CONFIRM_BTN_W);
    yesBtn->onClick = onVideoConfirmYes;

    WidgetT *noBtn = wgtButton(btnRow, "No");
    noBtn->prefW    = wgtPixels(CP_CONFIRM_BTN_W);
    noBtn->onClick  = onVideoConfirmNo;

    dvxFitWindow(sAc, confirmWin);

    sVideoConfirmResult = -1;
    clock_t lastSecond  = clock();
    int32_t secondsLeft = CONFIRM_SECONDS;

    while (sAc->running && secondsLeft > 0 && sVideoConfirmResult < 0) {
        dvxUpdate(sAc);

        clock_t now = clock();

        if ((now - lastSecond) >= CLOCKS_PER_SEC) {
            lastSecond = now;
            secondsLeft--;
            snprintf(msgBuf, sizeof(msgBuf), "Reverting in %ld seconds...", (long)secondsLeft);
            wgtSetText(msgLbl, msgBuf);
        }
    }

    sAc->modalWindow = NULL;
    dvxDestroyWindow(sAc, confirmWin);

    if (sVideoConfirmResult != 1) {
        dvxChangeVideoMode(sAc, oldW, oldH, oldBpp);
    }

    sInProgress = false;
}


// ============================================================
// Callbacks  -- OK / Cancel / Close
// ============================================================

static void onOk(WidgetT *w) {
    (void)w;

    // Save mouse settings
    int32_t wheelSel = wgtDropdownGetSelected(sWheelDrop);
    prefsSetString(sPrefs, "mouse", "wheel", wheelSel == 1 ? "reversed" : "normal");
    prefsSetInt(sPrefs, "mouse", "doubleclick", wgtSliderGetValue(sDblClickSldr));
    prefsSetString(sPrefs, "mouse", "acceleration", mapAccelValue(wgtDropdownGetSelected(sAccelDrop)));

    // Save colors to INI
    for (int32_t i = 0; i < ColorCountE; i++) {
        uint8_t r;
        uint8_t g;
        uint8_t b;
        dvxGetColor(sAc, (ColorIdE)i, &r, &g, &b);

        char val[16];
        snprintf(val, sizeof(val), "%d,%d,%d", r, g, b);
        prefsSetString(sPrefs, "colors", dvxColorName((ColorIdE)i), val);
    }

    // Save desktop settings
    if (sWallpaperPath[0]) {
        prefsSetString(sPrefs, "desktop", "wallpaper", sWallpaperPath);
    } else {
        prefsRemove(sPrefs, "desktop", "wallpaper");
    }

    const char *modeStr = "stretch";

    if (sAc->wallpaperMode == WallpaperTileE) {
        modeStr = "tile";
    } else if (sAc->wallpaperMode == WallpaperCenterE) {
        modeStr = "center";
    }

    prefsSetString(sPrefs, "desktop", "mode", modeStr);

    // Save video settings
    prefsSetInt(sPrefs, "video", "width", sAc->display.width);
    prefsSetInt(sPrefs, "video", "height", sAc->display.height);
    prefsSetInt(sPrefs, "video", "bpp", sAc->display.format.bitsPerPixel);

    prefsSave(sPrefs);
    prefsClose(sPrefs);
    sPrefs = NULL;
    dvxDestroyWindow(sAc, sWin);
    sWin = NULL;
}


static void onCancel(WidgetT *w) {
    (void)w;
    restoreSnapshot();
    prefsClose(sPrefs);
    sPrefs = NULL;
    dvxDestroyWindow(sAc, sWin);
    sWin = NULL;
}


static void onClose(WindowT *win) {
    restoreSnapshot();
    prefsClose(sPrefs);
    sPrefs = NULL;
    dvxDestroyWindow(sAc, win);
    sWin = NULL;
}


// ============================================================
// saveSnapshot / restoreSnapshot
// ============================================================

static char sSavedWallpaperPath[DVX_MAX_PATH];

static void saveSnapshot(void) {
    memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb));
    sSavedWheelDir      = sAc->wheelDirection;
    sSavedDblClick      = (int32_t)(sAc->dblClickTicks * 1000 / CLOCKS_PER_SEC);
    sSavedAccel         = wgtDropdownGetSelected(sAccelDrop);
    sSavedVideoW        = sAc->display.width;
    sSavedVideoH        = sAc->display.height;
    sSavedVideoBpp      = sAc->display.format.bitsPerPixel;

    // Save the active wallpaper path and mode from the context
    snprintf(sSavedWallpaperPath, sizeof(sSavedWallpaperPath), "%s", sAc->wallpaperPath);
    snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sAc->wallpaperPath);
    sSavedWpMode = sAc->wallpaperMode;
}


static void restoreSnapshot(void) {
    // Restore colors
    memcpy(sAc->colorRgb, sSavedColorRgb, sizeof(sSavedColorRgb));
    dvxApplyColorScheme(sAc);

    // Restore mouse
    const char *accelName = mapAccelValue(sSavedAccel);
    int32_t     accelVal  = mapAccelName(accelName);
    dvxSetMouseConfig(sAc, sSavedWheelDir, sSavedDblClick, accelVal);

    // Restore video mode if changed
    if (sAc->display.width != sSavedVideoW ||
        sAc->display.height != sSavedVideoH ||
        sAc->display.format.bitsPerPixel != sSavedVideoBpp) {
        dvxChangeVideoMode(sAc, sSavedVideoW, sSavedVideoH, sSavedVideoBpp);
    }

    // Restore wallpaper only if path or mode changed
    if (strcmp(sAc->wallpaperPath, sSavedWallpaperPath) != 0 ||
        sAc->wallpaperMode != sSavedWpMode) {
        sAc->wallpaperMode = sSavedWpMode;

        if (sSavedWallpaperPath[0]) {
            dvxSetWallpaper(sAc, sSavedWallpaperPath);
        } else {
            dvxSetWallpaper(sAc, NULL);
        }
    }
}


// ============================================================
// scanThemes
// ============================================================

static void scanThemes(void) {
    arrsetlen(sThemeEntries, 0);
    arrsetlen(sThemeLabels, 0);

    DIR *dir = opendir(THEME_DIR);

    if (!dir) {
        return;
    }

    struct dirent *ent;

    while ((ent = readdir(dir)) != NULL) {
        char *dot = strrchr(ent->d_name, '.');

        if (!dot || strcasecmp(dot, THEME_EXT) != 0) {
            continue;
        }

        FileEntryT entry = {0};
        int32_t nameLen = (int32_t)(dot - ent->d_name);

        if (nameLen >= (int32_t)sizeof(entry.name)) {
            nameLen = (int32_t)sizeof(entry.name) - 1;
        }

        memcpy(entry.name, ent->d_name, nameLen);
        entry.name[nameLen] = '\0';
        snprintf(entry.path, sizeof(entry.path), "%s/%s", THEME_DIR, entry.name);
        arrput(sThemeEntries, entry);
    }

    closedir(dir);

    // Build label array now that sThemeEntries is stable
    for (int32_t i = 0; i < arrlen(sThemeEntries); i++) {
        arrput(sThemeLabels, sThemeEntries[i].name);
    }
}


// ============================================================
// scanWallpapers
// ============================================================

static void scanWallpapers(void) {
    arrsetlen(sWpaperEntries, 0);
    arrsetlen(sWpaperLabels, 0);

    DIR *dir = opendir(WPAPER_DIR);

    if (!dir) {
        return;
    }

    struct dirent *ent;

    while ((ent = readdir(dir)) != NULL) {
        char *dot = strrchr(ent->d_name, '.');

        if (!dot) {
            continue;
        }

        if (strcasecmp(dot, ".BMP") != 0 &&
            strcasecmp(dot, ".JPG") != 0 &&
            strcasecmp(dot, ".PNG") != 0) {
            continue;
        }

        FileEntryT entry = {0};

        // Length-clamped memcpy instead of strncpy/snprintf because GCC
        // warns about both when d_name (255) exceeds the buffer (64),
        // even though truncation is intentional and safe.
        int32_t nl = (int32_t)strlen(ent->d_name);

        if (nl >= (int32_t)sizeof(entry.name)) {
            nl = (int32_t)sizeof(entry.name) - 1;
        }

        memcpy(entry.name, ent->d_name, nl);
        entry.name[nl] = '\0';
        snprintf(entry.path, sizeof(entry.path), "%s/%s", WPAPER_DIR, entry.name);
        arrput(sWpaperEntries, entry);
    }

    closedir(dir);

    // Build label array now that sWpaperEntries is stable
    for (int32_t i = 0; i < arrlen(sWpaperEntries); i++) {
        arrput(sWpaperLabels, sWpaperEntries[i].name);
    }
}


// ============================================================
// updateColorSliders
// ============================================================

static void updateColorSliders(void) {
    int32_t sel = wgtListBoxGetSelected(sColorList);

    if (sel < 0 || sel >= ColorCountE) {
        return;
    }

    uint8_t r;
    uint8_t g;
    uint8_t b;
    dvxGetColor(sAc, (ColorIdE)sel, &r, &g, &b);

    wgtSliderSetValue(sRedSldr, r);
    wgtSliderSetValue(sGreenSldr, g);
    wgtSliderSetValue(sBlueSldr, b);

    static char rBuf[8];
    static char gBuf[8];
    static char bBuf[8];
    snprintf(rBuf, sizeof(rBuf), "%d", r);
    snprintf(gBuf, sizeof(gBuf), "%d", g);
    snprintf(bBuf, sizeof(bBuf), "%d", b);
    wgtSetText(sRedLbl, rBuf);
    wgtSetText(sGreenLbl, gBuf);
    wgtSetText(sBlueLbl, bBuf);
    updateSwatch();
}


// ============================================================
// updateDblClickLabel
// ============================================================

static void updateDblClickLabel(void) {
    static char buf[32];
    snprintf(buf, sizeof(buf), "%ld ms", (long)wgtSliderGetValue(sDblClickSldr));
    wgtSetText(sDblClickLbl, buf);
}


// ============================================================
// updateSwatch
// ============================================================

static void updateSwatch(void) {
    if (!sColorSwatch) {
        return;
    }

    uint8_t r = (uint8_t)wgtSliderGetValue(sRedSldr);
    uint8_t g = (uint8_t)wgtSliderGetValue(sGreenSldr);
    uint8_t b = (uint8_t)wgtSliderGetValue(sBlueSldr);

    uint32_t color = packColor(&sAc->display, r, g, b);
    wgtCanvasClear(sColorSwatch, color);
}


// ============================================================
// appMain
// ============================================================

int32_t appMain(DxeAppContextT *ctx) {
    sCtx  = ctx;
    sAc   = ctx->shellCtx;
    sPrefs = prefsLoad("CONFIG/DVX.INI");

    int32_t winX = (sAc->display.width - CP_WIN_W) / 2;
    int32_t winY = (sAc->display.height - CP_WIN_H) / 2;

    sWin = dvxCreateWindow(sAc, "Control Panel", winX, winY, CP_WIN_W, CP_WIN_H, true);

    if (!sWin) {
        return -1;
    }

    sWin->onClose = onClose;

    saveSnapshot();

    WidgetT *root = wgtInitWindow(sAc, sWin);

    // Tab control fills the window
    WidgetT *tabs = wgtTabControl(root);
    tabs->weight  = 100;

    WidgetT *mouseTab   = wgtTabPage(tabs, "Mouse");
    WidgetT *colorsTab  = wgtTabPage(tabs, "Colors");
    WidgetT *desktopTab = wgtTabPage(tabs, "Desktop");
    WidgetT *videoTab   = wgtTabPage(tabs, "Video");

    buildMouseTab(mouseTab);
    buildColorsTab(colorsTab);
    buildDesktopTab(desktopTab);
    buildVideoTab(videoTab);

    // OK / Cancel buttons at bottom
    WidgetT *btnRow = wgtHBox(root);
    btnRow->align   = AlignEndE;
    btnRow->spacing = wgtPixels(CP_SPACING);

    WidgetT *okBtn = wgtButton(btnRow, "OK");
    okBtn->onClick = onOk;
    okBtn->prefW   = wgtPixels(CP_OK_CANCEL_BTN_W);

    WidgetT *cancelBtn = wgtButton(btnRow, "Cancel");
    cancelBtn->onClick = onCancel;
    cancelBtn->prefW   = wgtPixels(CP_OK_CANCEL_BTN_W);

    dvxFitWindow(sAc, sWin);
    return 0;
}
