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

// formrt.h -- DVX BASIC form runtime
//
// Bridges BASIC programs to the DVX widget system. Control types
// are resolved dynamically via WgtIfaceT interface descriptors
// registered by .wgt DXE files. Properties and methods are
// dispatched generically through descriptors. Events fire by
// looking up ControlName_EventName in the compiled module's
// procedure table and calling into the VM.

#ifndef DVXBASIC_FORMRT_H
#define DVXBASIC_FORMRT_H

#include "../runtime/vm.h"
#include "../runtime/values.h"
#include "dvxApp.h"
#include "dvxWgt.h"
#include "frmParser.h"

// ============================================================
// Forward declarations
// ============================================================

typedef struct BasFormT BasFormT;
typedef struct BasControlT BasControlT;

// ============================================================
// Limits
// ============================================================

// Form and control names share the single identifier length BAS_MAX_IDENT
// (vm.h) with the compiler, so name-keyed lookups never truncate.
// Window size for a form that gives no Width/Height; the IDE designer
// uses the same values for a new form.
#define BAS_DEFAULT_FORM_W       400
#define BAS_DEFAULT_FORM_H       300

// ============================================================
// Property descriptors
// ============================================================
//
// The single source of truth for the property names the form runtime
// accepts on the form object itself and on every non-menu control.
// getProp/setProp dispatch, the .frm loader's value classification,
// and the "Valid: ..." runtime-error lists are all driven from these
// tables; the IDE validator and property grid should consume them too
// instead of keeping their own copies.

typedef struct {
    const char *name;
    uint8_t     type;       // WGT_IFACE_STRING / WGT_IFACE_INT / WGT_IFACE_BOOL
    bool        readable;
    bool        writable;
} BasPropDescT;

// Form-object properties (FormName.Prop), in table order.
const BasPropDescT *basFormRtFormProps(int32_t *count);

// Properties every non-menu control accepts before its widget interface
// is consulted, in table order.
const BasPropDescT *basFormRtCommonProps(int32_t *count);

// Case-insensitive lookups; NULL when the name is not in the table.
const BasPropDescT *basFormRtFindFormProp(const char *name);
const BasPropDescT *basFormRtFindCommonProp(const char *name);

// ============================================================
// Common methods
// ============================================================
//
// Methods callCommonMethod() accepts on every non-menu control (the
// toolbar button methods are answered only by a Toolbar).  The IDE
// validator consumes this table instead of keeping its own list.

typedef enum {
    BAS_CM_SETFOCUS,
    BAS_CM_REFRESH,
    BAS_CM_SETREADONLY,
    BAS_CM_SETENABLED,
    BAS_CM_SETVISIBLE,
    BAS_CM_POPUPMENU,
    BAS_CM_CREATEMENU,
    BAS_CM_ADDMENUITEM,
    BAS_CM_ADDMENUSEPARATOR,
    BAS_CM_ADDSUBMENU,
    BAS_CM_DESTROYMENU,
    BAS_CM_ADDBUTTON,
    BAS_CM_ADDTEXTBUTTON,
    BAS_CM_ADDSEPARATOR,
    BAS_CM_CLEAR,
    BAS_CM_BUTTONCOUNT,
    BAS_CM_COUNT
} BasCommonMethodE;

#define BAS_CM_NONE (-1)

// Case-insensitive lookup; BAS_CM_NONE when name is not a common method.
int32_t basFormRtCommonMethodId(const char *name);

// ============================================================
// Menu ID to name mapping for event dispatch
// ============================================================

typedef struct {
    int32_t         id;
    char            name[BAS_MAX_IDENT];
    BasControlT    *proxy;   // heap-allocated proxy for property access (widget=NULL, menuId stored)
} BasMenuIdMapT;

// Named popup / context menu owned by a form.
typedef struct {
    char     name[BAS_MAX_IDENT];
    MenuT   *menu;        // wmCreateMenu root, or a submenu owned by its root
    bool     ownsMenu;    // true only for the root that wmFreeMenu must free
} BasFrmPopupMenuT;

// ============================================================
// Control instance (a widget on a form)
// ============================================================

#define BAS_MAX_EVENT_OVERRIDES 16

// Event handler override (SetEvent)
typedef struct {
    char eventName[BAS_MAX_IDENT];    // e.g. "Click"
    char handlerName[BAS_MAX_IDENT];  // e.g. "HandleOkClick"
} BasEventOverrideT;

typedef struct BasControlT {
    char              name[BAS_MAX_IDENT];     // VB control name (e.g. "Command1")
    char              typeName[BAS_MAX_IDENT]; // VB type name (e.g. "CommandButton")
    int32_t           index;                       // control array index (-1 = not in array)
    WidgetT          *widget;                      // the DVX widget
    BasFormT         *form;                        // owning form
    const WgtIfaceT  *iface;                       // interface descriptor (from .wgt)
    char              dataSource[BAS_MAX_IDENT]; // name of Data control for binding
    char              dataField[BAS_MAX_IDENT];  // column name for binding
    char              helpTopic[BAS_MAX_IDENT];  // help topic ID for F1
    char             *tooltip;                       // heap-owned tooltip text (NULL = none); wgtSetTooltip references this buffer, so it must outlive the widget
    int32_t           menuId;                        // WM menu item ID (>0 for menu items, 0 for controls)
    BasEventOverrideT eventOverrides[BAS_MAX_EVENT_OVERRIDES];
    int32_t           eventOverrideCount;
    // Re-entrancy guard: set while an event handler for this control
    // is running.  runSubLoop pumps events during long-running handlers
    // (painting, draw loops), and without this guard a lingering
    // mouse-up or repaint cycle can re-fire the same Click on top of
    // itself -- an unbounded recursion.  firingEventName records which
    // event is in flight so different-event delivery (e.g. LostFocus
    // while Click is still running) is allowed through.
    bool              eventFiring;
    char              firingEventName[BAS_MAX_IDENT];
} BasControlT;

// ============================================================
// Form instance (a DVX window with controls)
// ============================================================

typedef struct BasFormT {
    char              name[BAS_MAX_IDENT]; // form name (e.g. "Form1")
    WindowT          *window;                  // DVX window
    WidgetT          *root;                    // widget root (from wgtInitWindow)
    WidgetT          *contentBox;              // VBox/HBox for user controls
    AppContextT      *ctx;                     // DVX app context
    BasControlT     **controls;                // stb_ds array of heap-allocated pointers
    BasVmT           *vm;                      // VM for event dispatch
    BasModuleT       *module;                  // compiled module (for SUB lookup)
    // Form-level properties (accumulated during .frm parsing)
    int32_t           frmWidth;
    int32_t           frmHeight;
    int32_t           frmLeft;
    int32_t           frmTop;
    bool              frmResizable;
    bool              frmHasResizable;         // true if Resizable was explicitly set
    bool              frmCentered;
    bool              frmAutoSize;
    char              frmLayout[32];            // "VBox", "HBox", or "WrapBox"
    char              helpTopic[BAS_MAX_IDENT]; // form-level help topic
    // Per-form variable storage (allocated at load, freed at unload)
    BasValueT        *formVars;
    int32_t           formVarCount;
    // Menu ID to name mapping (for event dispatch)
    BasMenuIdMapT    *menuIdMap;
    int32_t           menuIdMapCount;
    // Named popup (context) menus.  Allocated standalone via
    // wmCreateMenu() either from .frm `Begin Menu` with Visible=False
    // or at runtime via CreateMenu/AddMenuItem BASIC calls.  Click
    // dispatch reuses menuIdMap above so Foo_Click-style handlers
    // work the same as for menu-bar items.  Freed on form unload.
    BasFrmPopupMenuT *popupMenus;  // stb_ds array
    int32_t           popupMenuCount;
    // True from the moment Unload is committed (QueryUnload not
    // cancelled) until the form is freed.  Blocks re-entrant Unload of
    // the same form from its own Unload/QueryUnload handlers.
    bool              unloading;
    // True once detachFormForUnload has run: the form is out of
    // rt->forms, its window is hidden, and it is event-inert
    // (fireCtrlEvent drops events for it).  Distinct from `unloading`,
    // which is also true while QueryUnload/Unload still fire.
    bool              detached;
    // Synthetic control entry for the form itself, so that
    // FormName.Property works through the same getProp/setProp path.
    BasControlT       formCtrl;
} BasFormT;

// ============================================================
// Form runtime context
// ============================================================

// Cached .frm source for reload after unload
typedef struct {
    char    formName[BAS_MAX_IDENT];
    char   *frmSource;                         // malloc'd copy of .frm text
    int32_t frmSourceLen;
} BasFrmCacheT;

typedef struct {
    AppContextT      *ctx;                     // DVX app context
    BasVmT           *vm;                      // shared VM instance
    BasModuleT       *module;                  // compiled module
    BasFormT        **forms;                    // stb_ds array of heap-allocated pointers
    BasFormT         *currentForm;             // form currently dispatching events
    char              helpFile[DVX_MAX_PATH];  // project help file path (for F1)
    BasFrmCacheT     *frmCache;                // stb_ds array of cached .frm sources
    int32_t           frmCacheCount;
    // Set true when a runtime error has halted the program; the event
    // loop exits at the next pump so the app doesn't stumble forward
    // with a halted VM.
    bool              terminated;
    // Set true by hosts that want to handle runtime-error reporting
    // themselves (the IDE shows the error in the output pane and
    // navigates the editor to the line).  When true,
    // basFormRtRuntimeError still logs and halts but skips the modal
    // MessageBox -- this removes a stray second OK press the IDE users
    // were seeing when the dialog's event pump raced with other
    // queued events.
    bool              suppressErrorDialog;
    // Deferred teardown.  While eventDepth > 0 some frame on the stack
    // still holds BASIC form/control pointers -- a VM event handler, or
    // a native bridge that reads them after firing (see the rule-N
    // brackets in formrt.c) -- so Unload/RemoveControl must not free
    // form vars, BasControlT structs, or BasFormT out from under it.
    // QueryUnload/Unload events fire immediately and the object is
    // detached (unfindable, hidden, event-inert); only the frees are
    // queued here, in stb_ds dynamic arrays with no cap, and flushed
    // when the outermost frame returns (see rtEventLeave).
    int32_t           eventDepth;
    BasFormT        **pendingUnload;    // stb_ds array of detached forms
    BasControlT     **pendingCtrlFree;  // stb_ds array of removed controls
    // Name of the most recent basFormRtFindCtrl lookup.  The VM's
    // OP_FIND_CTRL opcode discards the name string after the lookup,
    // so when a subsequent OP_CALL_METHOD / OP_SET_PROP sees a NULL
    // ctrl we'd otherwise have no way to tell the user WHICH control
    // was missing.  This is the last requested name; if the lookup
    // succeeded this value is still the name of that control.
    char              lastLookupName[BAS_MAX_IDENT];
} BasFormRtT;

// ============================================================
// API
// ============================================================

// Initialize the form runtime with a DVX context and a compiled module.
BasFormRtT *basFormRtCreate(AppContextT *ctx, BasVmT *vm, BasModuleT *module);

// Load all cached .frm forms and show the startup form.  Called before
// bytecode execution begins.
void basFormRtLoadAllForms(BasFormRtT *rt, const char *startupFormName);

// VB-style event loop: pump DVX events until all forms are closed.
// Called after bytecode halts to keep the app alive for event handlers.
void basFormRtEventLoop(BasFormRtT *rt);

// Simple non-debug execution: run bytecode in slices with dvxUpdate
// between slices, then enter the event loop. Used by the standalone stub.
void basFormRtRunSimple(BasFormRtT *rt);

// Destroy the form runtime and all forms/controls.
void basFormRtDestroy(BasFormRtT *rt);

// Close all serial/secLink resources and unregister the idle pollers.
// Called by basFormRtDestroy and by the stub's _appShutdown force-kill hook.
void basFormRtSerialShutdown(void);

// Wire up the VM's UI callbacks to this form runtime.
void basFormRtBindVm(BasFormRtT *rt);

// ---- UI callback implementations (match BasUiCallbacksT) ----

BasValueT basFormRtGetProp(void *ctx, void *ctrlRef, const char *propName);
void      basFormRtSetProp(void *ctx, void *ctrlRef, const char *propName, BasValueT value);
BasValueT basFormRtCallMethod(void *ctx, void *ctrlRef, const char *methodName, BasValueT *args, int32_t argc);
void     *basFormRtCreateCtrl(void *ctx, void *formRef, const char *typeName, const char *ctrlName);
void     *basFormRtFindCtrl(void *ctx, void *formRef, const char *ctrlName);
void     *basFormRtFindCtrlIdx(void *ctx, void *formRef, const char *ctrlName, int32_t index);
void     *basFormRtLoadForm(void *ctx, const char *formName);
void      basFormRtUnloadForm(void *ctx, void *formRef);
void      basFormRtShowForm(void *ctx, void *formRef, bool modal);
void      basFormRtHideForm(void *ctx, void *formRef);
int32_t   basFormRtMsgBox(void *ctx, const char *message, int32_t flags, const char *title);

// ---- Extern call callbacks (shared by IDE and stub) ----

void *basExternResolve(void *ctx, const char *libName, const char *funcName);
BasValueT basExternCall(void *ctx, void *funcPtr, const char *libName, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType);

// ---- Form caching ----

// Register .frm source text for lazy loading when bytecode calls Load.
void basFormRtRegisterFrm(BasFormRtT *rt, const char *formName, const char *source, int32_t sourceLen);

// ---- Widget creation ----

// Create a widget by resolved (DVX) type name. Returns NULL if the type
// is unknown.  createWidgetByIface dispatches on the interface
// descriptor's createSig; pass allowData=true for an empty pixel-data
// widget (runtime path), or false to refuse (design-time path).
WidgetT *createWidget(const char *wgtTypeName, WidgetT *parent);
WidgetT *createWidgetByIface(const WgtIfaceT *iface, const void *api, WidgetT *parent, bool allowData);

// Resolve a VB-style name ("CommandButton") to a DVX widget name
// ("button"). Falls back to the same name if already canonical. Returns
// NULL if the type is unknown.
const char *resolveTypeName(const char *typeName);

// Apply a "Key = Value" string pair to a widget property via the
// interface descriptor. Handles ENUM/INT/BOOL/STRING dispatch.  Returns
// true if the property was set, false if unsupported type / no setter.
bool wgtApplyPropFromString(WidgetT *w, const WgtPropDescT *p, const char *val);

// Read a widget property via the interface getter and format it into
// out as a .frm-style value ("True", "False", enum name, or decimal
// string).  Returns true if the property was read; false if the getter
// is NULL, the type is not serializable, or the enum value is unknown.
bool wgtPropValueToString(const WidgetT *w, const WgtPropDescT *p, char *out, int32_t outSize);

// ---- Form window creation ----

WidgetT  *basFormRtCreateContentBox(WidgetT *root, const char *layout);
WindowT  *basFormRtCreateFormWindow(AppContextT *ctx, const char *title, const char *layout, bool resizable, bool centered, bool autoSize, int32_t width, int32_t height, int32_t left, int32_t top, WidgetT **outRoot, WidgetT **outContentBox);

// ---- Dynamic form/control API ----

void     *basFormRtCreateForm(void *ctx, const char *formName, int32_t width, int32_t height);
void     *basFormRtCreateCtrlEx(void *ctx, void *formRef, const char *typeName, const char *ctrlName, void *parentRef);
void      basFormRtSetEvent(void *ctx, void *ctrlRef, const char *eventName, const char *handlerName);
void      basFormRtRemoveCtrl(void *ctx, void *formRef, const char *ctrlName);

// ---- Event dispatch ----

bool basFormRtFireEvent(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName);
bool basFormRtFireEventArgs(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName, const BasValueT *args, int32_t argCount);

// ---- Form file loading ----

BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen);

#endif // DVXBASIC_FORMRT_H
