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

// formcfm.c -- Compiled form format implementation
//
// Serializes the layout portion of a .frm file (the Begin/End block
// structure) into a compact binary format. The BASIC code that follows
// the form layout is NOT included -- it's already compiled into the
// bytecode module.
//
// Binary format:
//   magic          4 bytes  "DCFM"
//   version        uint16   1
//   formName       len-prefixed string
//   caption        len-prefixed string
//   layout         len-prefixed string ("VBox", "HBox", etc.)
//   width          int16
//   height         int16
//   left           int16
//   top            int16
//   flags          uint8 (bit 0=resizable, 1=centered, 2=autoSize)
//   controlCount   uint16
//   menuItemCount  uint16
//
//   Controls[controlCount]:
//     typeName       len-prefixed string
//     ctrlName       len-prefixed string
//     parentIndex    int16 (-1 = form root)
//     arrayIndex     int16 (-1 = not array)
//     propertyCount  uint16
//     properties[]:
//       key          len-prefixed string
//       valueType    uint8 (0=string, 1=int, 2=bool)
//       value        type-dependent
//
//   MenuItems[menuItemCount]:
//     name           len-prefixed string
//     caption        len-prefixed string
//     level          uint8
//     flags          uint8 (bit 0=checked, 1=radioCheck, 2=enabled)

#include "formcfm.h"
#include "../compiler/opcodes.h"
#include "thirdparty/stb_ds_wrap.h"

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

// ============================================================
// Write helpers (same pattern as serialize.c)
// ============================================================

typedef struct {
    uint8_t *buf;
    int32_t  len;
    int32_t  cap;
} CfmBufT;


static void cbufInit(CfmBufT *b) {
    b->cap = 2048;
    b->buf = (uint8_t *)malloc(b->cap);
    b->len = 0;
}


static void cbufGrow(CfmBufT *b, int32_t need) {
    while (b->len + need > b->cap) {
        b->cap *= 2;
    }

    b->buf = (uint8_t *)realloc(b->buf, b->cap);
}


static void cbufWrite(CfmBufT *b, const void *data, int32_t len) {
    cbufGrow(b, len);
    memcpy(b->buf + b->len, data, len);
    b->len += len;
}


static void cbufU8(CfmBufT *b, uint8_t v) {
    cbufWrite(b, &v, 1);
}


static void cbufU16(CfmBufT *b, uint16_t v) {
    cbufWrite(b, &v, 2);
}


static void cbufI16(CfmBufT *b, int16_t v) {
    cbufWrite(b, &v, 2);
}


static void cbufStr(CfmBufT *b, const char *s) {
    uint16_t len = s ? (uint16_t)strlen(s) : 0;
    cbufU16(b, len);

    if (len > 0) {
        cbufWrite(b, s, len);
    }
}


// ============================================================
// Read helpers
// ============================================================

typedef struct {
    const uint8_t *data;
    int32_t        len;
    int32_t        pos;
} CfmReaderT;


static bool crOk(const CfmReaderT *r, int32_t need) {
    return r->pos + need <= r->len;
}


static uint8_t crU8(CfmReaderT *r) {
    return crOk(r, 1) ? r->data[r->pos++] : 0;
}


static uint16_t crU16(CfmReaderT *r) {
    if (!crOk(r, 2)) {
        return 0;
    }

    uint16_t v;
    memcpy(&v, r->data + r->pos, 2);
    r->pos += 2;
    return v;
}


static int16_t crI16(CfmReaderT *r) {
    if (!crOk(r, 2)) {
        return 0;
    }

    int16_t v;
    memcpy(&v, r->data + r->pos, 2);
    r->pos += 2;
    return v;
}


static int32_t crI32(CfmReaderT *r) {
    if (!crOk(r, 4)) {
        return 0;
    }

    int32_t v;
    memcpy(&v, r->data + r->pos, 4);
    r->pos += 4;
    return v;
}


static char *crStr(CfmReaderT *r) {
    uint16_t len = crU16(r);

    if (len == 0 || !crOk(r, len)) {
        char *s = (char *)malloc(1);
        s[0] = '\0';
        return s;
    }

    char *s = (char *)malloc(len + 1);
    memcpy(s, r->data + r->pos, len);
    s[len] = '\0';
    r->pos += len;
    return s;
}


// ============================================================
// .frm text parser for compilation
// ============================================================

#define MAX_LINE 512

typedef struct {
    char    typeName[BAS_MAX_CTRL_NAME];
    char    ctrlName[BAS_MAX_CTRL_NAME];
    int16_t parentIndex;
    int16_t arrayIndex;

    // Properties
    struct {
        char    key[64];
        uint8_t valueType; // 0=string, 1=int, 2=bool
        char    strVal[256];
        int32_t intVal;
    } props[32];

    int32_t propCount;
} CfmControlT;


typedef struct {
    char    name[BAS_MAX_CTRL_NAME];
    char    caption[256];
    uint8_t level;
    uint8_t flags; // bit 0=checked, 1=radioCheck, 2=enabled
} CfmMenuItemT;


static void parseFrmKeyVal(const char *line, char *key, char *value) {
    key[0]   = '\0';
    value[0] = '\0';

    while (*line == ' ' || *line == '\t') {
        line++;
    }

    int32_t ki = 0;

    while (*line && *line != ' ' && *line != '\t' && *line != '=' && ki < 63) {
        key[ki++] = *line++;
    }

    key[ki] = '\0';

    while (*line == ' ' || *line == '\t') {
        line++;
    }

    if (*line == '=') {
        line++;
    }

    while (*line == ' ' || *line == '\t') {
        line++;
    }

    // Value: strip surrounding quotes if present
    int32_t vi  = 0;
    bool    inQ = false;

    if (*line == '"') {
        inQ = true;
        line++;
    }

    while (*line && vi < 255) {
        if (inQ && *line == '"') {
            break;
        }

        value[vi++] = *line++;
    }

    value[vi] = '\0';
}


// ============================================================
// basFormCompile
// ============================================================

uint8_t *basFormCompile(const char *frmSource, int32_t frmLen, int32_t *outLen) {
    if (!frmSource || frmLen <= 0 || !outLen) {
        return NULL;
    }

    // Parse the .frm text to extract form layout
    char formName[BAS_MAX_CTRL_NAME] = "";
    char caption[256]                = "";
    char layout[BAS_MAX_CTRL_NAME]   = "VBox";
    int16_t width     = 0;
    int16_t height    = 0;
    int16_t left      = 0;
    int16_t top       = 0;
    bool    resizable = true;
    bool    centered  = true;
    bool    autoSize  = true;

    CfmControlT  *controls = NULL;
    int32_t       controlCount = 0;
    int32_t       controlCap = 32;
    controls = (CfmControlT *)calloc(controlCap, sizeof(CfmControlT));

    CfmMenuItemT *menuItems = NULL;
    int32_t       menuCount = 0;
    int32_t       menuCap = 16;
    menuItems = (CfmMenuItemT *)calloc(menuCap, sizeof(CfmMenuItemT));

    // Parent index stack for nested containers
    int16_t parentStack[BAS_MAX_FRM_NESTING];
    int32_t nestDepth    = 0;
    int32_t menuNestDepth = 0;
    bool    inForm        = false;
    bool    inMenu        = false;
    int32_t currentCtrl   = -1;

    const char *pos = frmSource;
    const char *end = frmSource + frmLen;

    while (pos < end) {
        const char *lineStart = pos;

        while (pos < end && *pos != '\n' && *pos != '\r') {
            pos++;
        }

        int32_t lineLen = (int32_t)(pos - lineStart);

        if (pos < end && *pos == '\r') {
            pos++;
        }

        if (pos < end && *pos == '\n') {
            pos++;
        }

        char line[MAX_LINE];

        if (lineLen >= MAX_LINE) {
            lineLen = MAX_LINE - 1;
        }

        memcpy(line, lineStart, lineLen);
        line[lineLen] = '\0';

        char *trimmed = line;

        while (*trimmed == ' ' || *trimmed == '\t') {
            trimmed++;
        }

        if (*trimmed == '\0' || *trimmed == '\'') {
            continue;
        }

        // Stop at code section (first SUB/FUNCTION/DIM/executable statement after End Form)
        if (!inForm && formName[0]) {
            break;
        }

        // VERSION line -- skip
        if (strncasecmp(trimmed, "VERSION ", 8) == 0) {
            continue;
        }

        // Begin block
        if (strncasecmp(trimmed, "Begin ", 6) == 0) {
            char *rest = trimmed + 6;
            char  typeName[BAS_MAX_CTRL_NAME];
            char  ctrlName[BAS_MAX_CTRL_NAME];

            int32_t ti = 0;

            while (*rest && *rest != ' ' && *rest != '\t' && ti < BAS_MAX_CTRL_NAME - 1) {
                typeName[ti++] = *rest++;
            }

            typeName[ti] = '\0';

            while (*rest == ' ' || *rest == '\t') {
                rest++;
            }

            int32_t ci = 0;

            while (*rest && *rest != ' ' && *rest != '\t' && *rest != '\r' && *rest != '\n' && ci < BAS_MAX_CTRL_NAME - 1) {
                ctrlName[ci++] = *rest++;
            }

            ctrlName[ci] = '\0';

            if (strcasecmp(typeName, "Form") == 0) {
                snprintf(formName, BAS_MAX_CTRL_NAME, "%s", ctrlName);
                snprintf(caption, sizeof(caption), "%s", ctrlName);
                inForm    = true;
                nestDepth = 0;

                for (int32_t i = 0; i < BAS_MAX_FRM_NESTING; i++) {
                    parentStack[i] = -1;
                }
            } else if (strcasecmp(typeName, "Menu") == 0 && inForm) {
                if (menuCount >= menuCap) {
                    menuCap *= 2;
                    menuItems = (CfmMenuItemT *)realloc(menuItems, menuCap * sizeof(CfmMenuItemT));
                }

                CfmMenuItemT *mi = &menuItems[menuCount++];
                memset(mi, 0, sizeof(CfmMenuItemT));
                snprintf(mi->name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
                mi->level = (uint8_t)menuNestDepth;
                mi->flags = 0x04; // enabled by default
                inMenu = true;
                menuNestDepth++;
                currentCtrl = -1;
            } else if (inForm) {
                if (controlCount >= controlCap) {
                    controlCap *= 2;
                    controls = (CfmControlT *)realloc(controls, controlCap * sizeof(CfmControlT));
                }

                CfmControlT *ctrl = &controls[controlCount];
                memset(ctrl, 0, sizeof(CfmControlT));
                snprintf(ctrl->typeName, BAS_MAX_CTRL_NAME, "%s", typeName);
                snprintf(ctrl->ctrlName, BAS_MAX_CTRL_NAME, "%s", ctrlName);
                ctrl->parentIndex = (nestDepth > 0) ? parentStack[nestDepth - 1] : -1;
                ctrl->arrayIndex  = -1;

                currentCtrl = controlCount;
                controlCount++;

                // Check if this is a container -- push onto parent stack
                const char *wgtName = wgtFindByBasName(typeName);

                if (wgtName) {
                    const WgtIfaceT *iface = wgtGetIface(wgtName);

                    if (iface && iface->isContainer) {
                        if (nestDepth < BAS_MAX_FRM_NESTING) {
                            parentStack[nestDepth] = (int16_t)currentCtrl;
                        }

                        nestDepth++;
                    }
                }
            }

            continue;
        }

        // End block
        if (strncasecmp(trimmed, "End", 3) == 0 && (trimmed[3] == '\0' || trimmed[3] == ' ' || trimmed[3] == '\t' || trimmed[3] == '\r' || trimmed[3] == '\n')) {
            if (inMenu) {
                menuNestDepth--;

                if (menuNestDepth <= 0) {
                    inMenu        = false;
                    menuNestDepth = 0;
                }
            } else if (nestDepth > 0) {
                nestDepth--;
            } else {
                // End Form
                inForm = false;
            }

            currentCtrl = -1;
            continue;
        }

        // Property assignment inside a block
        if (inForm) {
            char key[64];
            char value[256];
            parseFrmKeyVal(trimmed, key, value);

            if (!key[0]) {
                continue;
            }

            // Form-level properties
            if (currentCtrl < 0 && !inMenu) {
                if (strcasecmp(key, "Caption") == 0) {
                    snprintf(caption, sizeof(caption), "%s", value);
                } else if (strcasecmp(key, "Layout") == 0) {
                    snprintf(layout, BAS_MAX_CTRL_NAME, "%s", value);
                } else if (strcasecmp(key, "Width") == 0) {
                    width = (int16_t)atoi(value);
                } else if (strcasecmp(key, "Height") == 0) {
                    height = (int16_t)atoi(value);
                } else if (strcasecmp(key, "Left") == 0) {
                    left = (int16_t)atoi(value);
                } else if (strcasecmp(key, "Top") == 0) {
                    top = (int16_t)atoi(value);
                } else if (strcasecmp(key, "Resizable") == 0) {
                    resizable = (strcasecmp(value, "True") == 0);
                } else if (strcasecmp(key, "Centered") == 0) {
                    centered = (strcasecmp(value, "True") == 0);
                } else if (strcasecmp(key, "AutoSize") == 0) {
                    autoSize = (strcasecmp(value, "True") == 0);
                }
            } else if (inMenu && menuCount > 0) {
                CfmMenuItemT *mi = &menuItems[menuCount - 1];

                if (strcasecmp(key, "Caption") == 0) {
                    snprintf(mi->caption, sizeof(mi->caption), "%s", value);
                } else if (strcasecmp(key, "Checked") == 0) {
                    if (strcasecmp(value, "True") == 0) {
                        mi->flags |= 0x01;
                    }
                } else if (strcasecmp(key, "Enabled") == 0) {
                    if (strcasecmp(value, "False") == 0) {
                        mi->flags &= ~0x04;
                    }
                }
            } else if (currentCtrl >= 0 && currentCtrl < controlCount) {
                CfmControlT *ctrl = &controls[currentCtrl];

                // Index property for control arrays
                if (strcasecmp(key, "Index") == 0) {
                    ctrl->arrayIndex = (int16_t)atoi(value);
                    continue;
                }

                if (ctrl->propCount < 32) {
                    int32_t pi = ctrl->propCount++;
                    snprintf(ctrl->props[pi].key, 64, "%s", key);

                    // Detect value type
                    if (strcasecmp(value, "True") == 0) {
                        ctrl->props[pi].valueType = 2;
                        ctrl->props[pi].intVal    = 1;
                    } else if (strcasecmp(value, "False") == 0) {
                        ctrl->props[pi].valueType = 2;
                        ctrl->props[pi].intVal    = 0;
                    } else if (value[0] == '-' || isdigit((unsigned char)value[0])) {
                        ctrl->props[pi].valueType = 1;
                        ctrl->props[pi].intVal    = atoi(value);
                    } else {
                        ctrl->props[pi].valueType = 0;
                        snprintf(ctrl->props[pi].strVal, 256, "%s", value);
                    }
                }
            }
        }
    }

    // Serialize to binary
    CfmBufT b;
    cbufInit(&b);

    cbufWrite(&b, "DCFM", 4);
    cbufU16(&b, 1); // version
    cbufStr(&b, formName);
    cbufStr(&b, caption);
    cbufStr(&b, layout);
    cbufI16(&b, width);
    cbufI16(&b, height);
    cbufI16(&b, left);
    cbufI16(&b, top);

    uint8_t flags = 0;
    if (resizable) { flags |= 0x01; }
    if (centered)  { flags |= 0x02; }
    if (autoSize)  { flags |= 0x04; }
    cbufU8(&b, flags);

    cbufU16(&b, (uint16_t)controlCount);
    cbufU16(&b, (uint16_t)menuCount);

    for (int32_t i = 0; i < controlCount; i++) {
        CfmControlT *ctrl = &controls[i];
        cbufStr(&b, ctrl->typeName);
        cbufStr(&b, ctrl->ctrlName);
        cbufI16(&b, ctrl->parentIndex);
        cbufI16(&b, ctrl->arrayIndex);
        cbufU16(&b, (uint16_t)ctrl->propCount);

        for (int32_t j = 0; j < ctrl->propCount; j++) {
            cbufStr(&b, ctrl->props[j].key);
            cbufU8(&b, ctrl->props[j].valueType);

            switch (ctrl->props[j].valueType) {
                case 0: // string
                    cbufStr(&b, ctrl->props[j].strVal);
                    break;

                case 1: // int
                    cbufWrite(&b, &ctrl->props[j].intVal, 4);
                    break;

                case 2: // bool
                    cbufU8(&b, ctrl->props[j].intVal ? 1 : 0);
                    break;
            }
        }
    }

    for (int32_t i = 0; i < menuCount; i++) {
        CfmMenuItemT *mi = &menuItems[i];
        cbufStr(&b, mi->name);
        cbufStr(&b, mi->caption);
        cbufU8(&b, mi->level);
        cbufU8(&b, mi->flags);
    }

    free(controls);
    free(menuItems);

    *outLen = b.len;
    return b.buf;
}


// ============================================================
// basFormLoadCompiled
// ============================================================

BasFormT *basFormLoadCompiled(BasFormRtT *rt, const uint8_t *data, int32_t dataLen) {
    if (!rt || !data || dataLen < 10) {
        return NULL;
    }

    CfmReaderT r = { data, dataLen, 0 };

    // Check magic
    if (data[0] != 'D' || data[1] != 'C' || data[2] != 'F' || data[3] != 'M') {
        return NULL;
    }

    r.pos = 4;

    uint16_t version = crU16(&r);

    if (version != 1) {
        return NULL;
    }

    char *formName = crStr(&r);
    char *caption  = crStr(&r);
    char *layout   = crStr(&r);
    int16_t width  = crI16(&r);
    int16_t height = crI16(&r);
    int16_t left   = crI16(&r);
    int16_t top    = crI16(&r);
    uint8_t flags  = crU8(&r);
    bool resizable = (flags & 0x01) != 0;
    bool centered  = (flags & 0x02) != 0;
    bool autoSize  = (flags & 0x04) != 0;
    uint16_t controlCount = crU16(&r);
    uint16_t menuCount    = crU16(&r);

    // Create the form
    WidgetT *root;
    WidgetT *contentBox;
    WindowT *win = basFormRtCreateFormWindow(rt->ctx, caption, layout, resizable, centered, autoSize, width, height, left, top, &root, &contentBox);

    if (!win) {
        free(formName);
        free(caption);
        free(layout);
        return NULL;
    }

    BasFormT *form = (BasFormT *)calloc(1, sizeof(BasFormT));
    snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName);
    snprintf(form->frmLayout, sizeof(form->frmLayout), "%s", layout);
    form->frmWidth      = width;
    form->frmHeight     = height;
    form->frmLeft       = left;
    form->frmTop        = top;
    form->frmResizable  = resizable;
    form->frmCentered   = centered;
    form->frmAutoSize   = autoSize;
    form->window        = win;
    form->root          = root;
    form->contentBox    = contentBox;
    form->ctx           = rt->ctx;
    form->vm            = rt->vm;
    form->module        = rt->module;

    // Synthetic control for form-level property access
    memset(&form->formCtrl, 0, sizeof(form->formCtrl));
    snprintf(form->formCtrl.name, BAS_MAX_CTRL_NAME, "%s", formName);
    form->formCtrl.widget = root;
    form->formCtrl.form   = form;

    // Window callbacks
    win->onClose  = NULL; // stub sets these after
    win->onResize = NULL;
    win->onFocus  = NULL;
    win->onBlur   = NULL;

    // Create controls
    for (uint16_t i = 0; i < controlCount; i++) {
        char *typeName = crStr(&r);
        char *ctrlName = crStr(&r);
        int16_t parentIndex = crI16(&r);
        int16_t arrayIndex  = crI16(&r);
        uint16_t propCount  = crU16(&r);

        // Determine parent widget
        WidgetT *parent = contentBox;

        if (parentIndex >= 0 && parentIndex < (int16_t)i) {
            // Find the parent control's widget
            BasControlT *parentCtrl = form->controls[parentIndex];
            parent = parentCtrl->widget;
        }

        // Resolve widget type and create
        const char *wgtTypeName = wgtFindByBasName(typeName);
        WidgetT *widget = NULL;

        if (wgtTypeName) {
            widget = createWidget(wgtTypeName, parent);
        }

        if (!widget) {
            // Skip properties
            for (uint16_t j = 0; j < propCount; j++) {
                free(crStr(&r)); // key
                uint8_t vt = crU8(&r);
                if (vt == 0)      { free(crStr(&r)); }
                else if (vt == 1) { crI32(&r); }
                else              { crU8(&r); }
            }

            free(typeName);
            free(ctrlName);
            continue;
        }

        wgtSetName(widget, ctrlName);

        // Create control entry
        BasControlT *ctrl = (BasControlT *)calloc(1, sizeof(BasControlT));
        snprintf(ctrl->name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
        snprintf(ctrl->typeName, BAS_MAX_CTRL_NAME, "%s", typeName);
        ctrl->index  = arrayIndex;
        ctrl->widget = widget;
        ctrl->form   = form;
        ctrl->iface  = wgtGetIface(wgtTypeName);
        arrput(form->controls, ctrl);

        widget->userData = ctrl;

        // Apply properties
        for (uint16_t j = 0; j < propCount; j++) {
            char *key = crStr(&r);
            uint8_t vt = crU8(&r);
            BasValueT val;
            memset(&val, 0, sizeof(val));

            switch (vt) {
                case 0: { // string
                    char *sv = crStr(&r);
                    val = basValStringFromC(sv);
                    free(sv);
                    break;
                }

                case 1: // int
                    val = basValLong(crI32(&r));
                    break;

                case 2: // bool
                    val = basValBool(crU8(&r) != 0);
                    break;
            }

            basFormRtSetProp(rt, ctrl, key, val);
            basValRelease(&val);
            free(key);
        }

        free(typeName);
        free(ctrlName);
    }

    // TODO: menu items (skip for now, read and discard)
    for (uint16_t i = 0; i < menuCount; i++) {
        free(crStr(&r)); // name
        free(crStr(&r)); // caption
        crU8(&r);        // level
        crU8(&r);        // flags
    }

    // Add form to runtime
    arrput(rt->forms, form);

    free(formName);
    free(caption);
    free(layout);

    return form;
}


bool basFormGetCompiledName(const uint8_t *data, int32_t dataLen, char *nameBuf, int32_t bufSize) {
    if (!data || dataLen < 10 || !nameBuf || bufSize < 2) {
        return false;
    }

    if (data[0] != 'D' || data[1] != 'C' || data[2] != 'F' || data[3] != 'M') {
        return false;
    }

    // Skip magic (4) + version (2) = offset 6
    // Form name is the first length-prefixed string
    if (dataLen < 8) {
        return false;
    }

    uint16_t nameLen;
    memcpy(&nameLen, data + 6, 2);

    if (nameLen == 0 || 8 + nameLen > dataLen) {
        return false;
    }

    int32_t copyLen = (nameLen < bufSize - 1) ? nameLen : bufSize - 1;
    memcpy(nameBuf, data + 8, copyLen);
    nameBuf[copyLen] = '\0';
    return true;
}
