// 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.

// shellTaskMgr.c  -- System Task Manager
//
// Shell-level Task Manager window.  Accessible via Ctrl+Esc regardless
// of which app is focused or whether the desktop app is running.  Lists
// all running apps with Switch To, End Task, and Run buttons.

#include "shellTaskMgr.h"
#include "../dvxshell/shellApp.h"
#include "dvxDlg.h"
#include "dvxWgt.h"
#include "box/box.h"
#include "button/button.h"
#include "label/label.h"
#include "listView/listView.h"
#include "dvxWm.h"
#include "dvxPlat.h"
#include "stb_ds_wrap.h"

#include <stdio.h>
#include <string.h>
#include "dvxMem.h"

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

#define TM_COL_COUNT     6
#define TM_WIN_W         580
#define TM_WIN_H         280
#define TM_LIST_PREF_H   160
#define TM_BTN_SPACING   8
#define TM_BTN_W         90

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

static ShellAppT *getRunningAppByIndex(int32_t sel);
static void       onTmClose(WindowT *win);
static void       onTmEndTask(WidgetT *w);
static void       onTmRun(WidgetT *w);
static void       onTmSwitchTo(WidgetT *w);
static void       refreshTaskList(void);
void              shellTaskMgrOpen(AppContextT *ctx);
void              shellTaskMgrRefresh(void);
static void       taskmgrInit(void) __attribute__((constructor));
static void       updateStatusText(void);

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

// Per-row string storage for the list view (must outlive each refresh cycle)
typedef struct {
    char name[SHELL_APP_NAME_MAX];
    char title[MAX_TITLE_LEN];
    char file[64];
    char type[12];
    char mem[16];
} TmRowStringsT;

static AppContextT    *sCtx         = NULL;
static WindowT        *sTmWindow    = NULL;
static WidgetT        *sTmListView  = NULL;
static WidgetT        *sTmStatusLbl = NULL;
static const char    **sCells       = NULL;  // dynamic array of cell pointers
static TmRowStringsT  *sRowStrs     = NULL;  // dynamic array of per-row strings


static ShellAppT *getRunningAppByIndex(int32_t sel) {
    int32_t idx = 0;

    for (int32_t i = 1; i < shellAppSlotCount(); i++) {
        ShellAppT *app = shellGetApp(i);

        if (app && app->state == AppStateRunningE) {
            if (idx == sel) {
                return app;
            }
            idx++;
        }
    }

    return NULL;
}


static void onTmClose(WindowT *win) {
    shellUnregisterDesktopUpdate(shellTaskMgrRefresh);
    arrfree(sCells);
    arrfree(sRowStrs);
    sCells       = NULL;
    sRowStrs     = NULL;
    sTmListView  = NULL;
    sTmStatusLbl = NULL;
    sTmWindow    = NULL;
    dvxDestroyWindow(sCtx, win);
}


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

    if (!sTmListView) {
        return;
    }

    int32_t sel = wgtListViewGetSelected(sTmListView);

    if (sel < 0) {
        return;
    }

    ShellAppT *app = getRunningAppByIndex(sel);

    if (app) {
        shellForceKillApp(sCtx, app);
        shellDesktopUpdate();
    }
}


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

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

    if (dvxFileDialog(sCtx, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) {
        shellLoadApp(sCtx, path);
    }
}


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

    if (!sTmListView) {
        return;
    }

    int32_t sel = wgtListViewGetSelected(sTmListView);

    if (sel < 0) {
        return;
    }

    ShellAppT *app = getRunningAppByIndex(sel);

    if (!app) {
        return;
    }

    // Scan the window stack top-down (highest Z first) to find the app's
    // topmost window, restore it if minimized, then raise and focus it.
    for (int32_t j = sCtx->stack.count - 1; j >= 0; j--) {
        WindowT *win = sCtx->stack.windows[j];

        if (win->appId == app->appId) {
            if (win->minimized) {
                wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, &sCtx->display, win);
            }

            wmRaiseWindow(&sCtx->stack, &sCtx->dirty, j);
            wmSetFocus(&sCtx->stack, &sCtx->dirty, sCtx->stack.count - 1);
            return;
        }
    }
}


static void refreshTaskList(void) {
    if (!sTmListView) {
        return;
    }

    // Reset dynamic arrays (keep allocations for reuse)
    arrsetlen(sCells, 0);
    arrsetlen(sRowStrs, 0);

    // Pass 1: collect all row data.  Must finish before building cell
    // pointers because arrput may reallocate, invalidating earlier
    // pointers into the array.
    for (int32_t i = 1; i < shellAppSlotCount(); i++) {
        ShellAppT *app = shellGetApp(i);

        if (app && app->state == AppStateRunningE) {
            TmRowStringsT row = {0};

            // Copy the name into the per-row storage alongside the derived
            // columns so every cell pointer handed to the list view is backed
            // by the same stable sRowStrs entry.
            snprintf(row.name, sizeof(row.name), "%s", app->name);

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

                if (win->appId == app->appId && win->visible) {
                    snprintf(row.title, sizeof(row.title), "%s", win->title);
                    break;
                }
            }

            char *sep = platformPathDirEnd(app->path);
            const char *fname = sep ? sep + 1 : app->path;
            snprintf(row.file, sizeof(row.file), "%s", fname);
            snprintf(row.type, sizeof(row.type), "%s", app->hasMainLoop ? "Task" : "Callback");

            uint32_t memKb = dvxMemGetAppUsage(app->appId) / 1024;

            if (memKb >= 1024) {
                snprintf(row.mem, sizeof(row.mem), "%lu MB", (unsigned long)(memKb / 1024));
            } else {
                snprintf(row.mem, sizeof(row.mem), "%lu KB", (unsigned long)memKb);
            }

            arrput(sRowStrs, row);
        }
    }

    // Pass 2: build cell pointer array.  sRowStrs is stable now.
    int32_t rowCount = arrlen(sRowStrs);

    for (int32_t r = 0; r < rowCount; r++) {
        arrput(sCells, sRowStrs[r].name);
        arrput(sCells, sRowStrs[r].title);
        arrput(sCells, sRowStrs[r].file);
        arrput(sCells, sRowStrs[r].type);
        arrput(sCells, sRowStrs[r].mem);
        arrput(sCells, "Running");
    }

    wgtListViewSetData(sTmListView, sCells, rowCount);
}


void shellTaskMgrOpen(AppContextT *ctx) {
    sCtx = ctx;

    if (sTmWindow) {
        // Already open  -- raise and focus
        for (int32_t i = 0; i < ctx->stack.count; i++) {
            if (ctx->stack.windows[i] == sTmWindow) {
                wmRaiseWindow(&ctx->stack, &ctx->dirty, i);
                wmSetFocus(&ctx->stack, &ctx->dirty, ctx->stack.count - 1);
                break;
            }
        }

        return;
    }

    int32_t winW = TM_WIN_W;
    int32_t winH = TM_WIN_H;
    int32_t winX = (ctx->display.width - winW) / 2;
    int32_t winY = (ctx->display.height - winH) / 3;

    sTmWindow = dvxCreateWindow(ctx, "Task Manager", winX, winY, winW, winH, true);

    if (!sTmWindow) {
        return;
    }

    sTmWindow->onClose = onTmClose;
    sTmWindow->appId   = 0;  // owned by the shell, not any app

    WidgetT *root = wgtInitWindow(ctx, sTmWindow);

    static ListViewColT tmCols[TM_COL_COUNT];
    tmCols[0].title = "Name";
    tmCols[0].width = wgtPercent(18);
    tmCols[0].align = ListViewAlignLeftE;
    tmCols[1].title = "Title";
    tmCols[1].width = wgtPercent(26);
    tmCols[1].align = ListViewAlignLeftE;
    tmCols[2].title = "File";
    tmCols[2].width = wgtPercent(18);
    tmCols[2].align = ListViewAlignLeftE;
    tmCols[3].title = "Type";
    tmCols[3].width = wgtPercent(12);
    tmCols[3].align = ListViewAlignLeftE;
    tmCols[4].title = "Memory";
    tmCols[4].width = wgtPercent(12);
    tmCols[4].align = ListViewAlignRightE;
    tmCols[5].title = "Status";
    tmCols[5].width = wgtPercent(14);
    tmCols[5].align = ListViewAlignLeftE;

    sTmListView = wgtListView(root);
    sTmListView->weight     = WGT_WEIGHT_FILL;
    sTmListView->prefH      = wgtPixels(TM_LIST_PREF_H);
    sTmListView->onDblClick = onTmSwitchTo;
    wgtListViewSetColumns(sTmListView, tmCols, TM_COL_COUNT);

    WidgetT *btnRow = wgtHBox(root);
    btnRow->spacing = wgtPixels(TM_BTN_SPACING);

    sTmStatusLbl = wgtLabel(btnRow, "");
    sTmStatusLbl->weight = WGT_WEIGHT_FILL;

    WidgetT *switchBtn = wgtButton(btnRow, "&Switch To");
    switchBtn->onClick = onTmSwitchTo;
    switchBtn->prefW   = wgtPixels(TM_BTN_W);

    WidgetT *endBtn = wgtButton(btnRow, "&End Task");
    endBtn->onClick = onTmEndTask;
    endBtn->prefW   = wgtPixels(TM_BTN_W);

    WidgetT *runBtn = wgtButton(btnRow, "&Run...");
    runBtn->onClick = onTmRun;
    runBtn->prefW   = wgtPixels(TM_BTN_W);

    shellRegisterDesktopUpdate(shellTaskMgrRefresh);
    refreshTaskList();
    updateStatusText();
    dvxFitWindow(ctx, sTmWindow);
}


void shellTaskMgrRefresh(void) {
    if (sTmWindow) {
        refreshTaskList();
        updateStatusText();
    }
}


// DXE constructor -- register Ctrl+Esc handler with the shell
static void taskmgrInit(void) {
    shellCtrlEscFn = shellTaskMgrOpen;
}


static void updateStatusText(void) {
    if (!sTmStatusLbl) {
        return;
    }

    static char buf[128];
    int32_t  count = shellRunningAppCount();
    uint32_t totalKb;
    uint32_t freeKb;

    bool hasMem = platformGetMemoryInfo(&totalKb, &freeKb);

    int32_t pos = snprintf(buf, sizeof(buf), "Applications: %ld", (long)count);

    if (hasMem && totalKb > 0) {
        uint32_t usedKb = totalKb - freeKb;
        snprintf(buf + pos, sizeof(buf) - pos, "  Memory: %lu/%lu MB", (unsigned long)(usedKb / 1024), (unsigned long)(totalKb / 1024));
    }

    wgtSetText(sTmStatusLbl, buf);
}
