// calog.h -- embed multi-language scripting into a C/C++ host.
//
// calog is a script broker: register native C functions once and call them from any
// embedded engine (Lua, JavaScript/QuickJS, Squirrel, my-basic), passing values by a
// single canonical type. This header is the entire public surface. A CalogT is a
// self-contained runtime -- independent runtimes may coexist in one process, and one
// host thread may drive several by pumping each in turn (don't share values across
// them). See design.md.
//
// Threading model (design.md sec 6):
//   - Scripts run fire-and-forget on their own context threads; calogContextEval
//     returns immediately.
//   - A registered native runs on the HOST thread (the thread that calls
//     calogCreate/calogPump), serialized, so host C code is never called
//     concurrently and needs no locking. The host services those calls by calling
//     calogPump() in its own loop.
//   - calogRegisterInline is the escape hatch: an inline native runs directly on the
//     calling script's thread (no host hop) -- YOU guarantee it is thread-safe.
//   - A script error is delivered to the error handler (calogSetErrorHandler) on the
//     host thread during calogPump; script results come back by calling natives.

#ifndef CALOG_H
#define CALOG_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

// Maximum nesting depth honored by the recursive copy/marshal paths. A deeper
// structure, or a cycle, fails with calogErrDepthE; the free path relies on the same
// invariant (aggregates must be acyclic).
#define CALOG_MAX_DEPTH 64

// Growth multiplier for growable buffers. Must stay a power of two (the registry
// derives its probe mask from a power-of-two slot count).
#define CALOG_GROWTH_FACTOR 2

typedef enum CalogStatusE {
    calogOkE             = 0,
    calogErrOomE         = 1,
    calogErrDepthE       = 2,
    calogErrTypeE        = 3,
    calogErrNotFoundE    = 4,
    calogErrArgE         = 5,
    calogErrRangeE       = 6,
    calogErrUnsupportedE = 7,
    calogErrDeadE        = 8
} CalogStatusE;

typedef enum CalogTypeE {
    calogNilE    = 0,
    calogBoolE   = 1,
    calogIntE    = 2,
    calogRealE   = 3,
    calogStringE = 4,
    calogAggE    = 5,
    calogFnE     = 6
} CalogTypeE;

// Disambiguates an empty or mixed container so it round-trips deterministically onto
// engines (such as my-basic) that model lists and dicts as distinct values.
typedef enum CalogKindE {
    calogListE = 0,
    calogMapE  = 1,
    calogBothE = 2
} CalogKindE;

typedef struct CalogT       CalogT;         // the scripting runtime (registry + actor)
typedef struct CalogContextT CalogContextT; // one engine instance on its own thread
typedef struct CalogAggT    CalogAggT;
typedef struct CalogFnT     CalogFnT;       // a script function value (refcounted)
typedef struct CalogPairT   CalogPairT;
typedef struct CalogStringT CalogStringT;
typedef struct CalogValueT  CalogValueT;

// Length-prefixed, binary-safe string. bytes is always NUL-terminated at
// bytes[length] so plain C consumers can read it, while length permits embedded NULs.
struct CalogStringT {
    char    *bytes;
    int64_t  length;
};

struct CalogValueT {
    CalogTypeE type;
    union {
        bool          b;
        int64_t       i;
        double        r;
        CalogStringT  s;
        CalogAggT    *agg;
        CalogFnT     *fn;
    } as;
};

struct CalogPairT {
    CalogValueT key;
    CalogValueT value;
};

// Hybrid container: the sequence part lives in array[0, arrayCount), the keyed part
// in pairs[0, pairCount). Only scalar and string keys are meaningful (see
// calogValueEquals); aggregate/function keys match by pointer identity only.
struct CalogAggT {
    CalogKindE    kind;
    CalogValueT  *array;
    int64_t       arrayCount;
    int64_t       arrayCap;
    CalogPairT   *pairs;
    int64_t       pairCount;
    int64_t       pairCap;
};

// The uniform native-function signature. result is nil on entry; on failure write a
// message into result with calogFail and return a non-zero CalogStatusE.
typedef int32_t (*CalogNativeFnT)(CalogValueT *args, int32_t argCount, CalogValueT *result, void *userData);

// Delivered on the host thread (during calogPump) when a fire-and-forget script
// fails: contextId is the failing context, message is the engine's error text.
typedef void (*CalogErrorFnT)(uint64_t contextId, const char *message, void *userData);

// Per-engine interpreter lifecycle + execution. Every hook runs on the context's OWN
// thread. createInterpreter exposes every registered native (calog does this via the
// adapter); a NULL engine makes a synthetic context with no interpreter. Embedders
// may supply a custom engine, but normally pass a built-in vtable below.
typedef struct CalogEngineT {
    const char        *name;
    // NULL-terminated list of file extensions this engine handles, without the leading
    // dot (e.g. {"lua", NULL}). calogContextLoad uses it to pick an engine by filename.
    const char *const *extensions;
    int32_t (*createInterpreter)(CalogContextT *context, void **interpOut);
    void    (*destroyInterpreter)(void *interp);
    int32_t (*runSource)(void *interp, const char *source, CalogValueT *result);
} CalogEngineT;

// ---- runtime ----
CalogT *calogCreate(void);
void    calogDestroy(CalogT *calog);
void    calogPump(CalogT *calog);   // run pending script->native calls on THIS thread
void    calogSetErrorHandler(CalogT *calog, CalogErrorFnT fn, void *userData);

// ---- natives ----
// A name beginning with "__" is INTERNAL: it is callable via calogCall but is NOT exposed
// into any engine's global namespace, so scripts can neither see nor shadow it.
int32_t calogRegister(CalogT *calog, const char *name, CalogNativeFnT fn, void *userData);       // runs on the host thread
int32_t calogRegisterInline(CalogT *calog, const char *name, CalogNativeFnT fn, void *userData); // runs on the calling script thread
int32_t calogCall(CalogT *calog, const char *name, CalogValueT *args, int32_t argCount, CalogValueT *result);
int32_t calogFail(CalogValueT *result, int32_t status, const char *message);
const char *calogTypeName(CalogTypeE type);

// ---- values ----
void         calogValueNil(CalogValueT *value);
void         calogValueBool(CalogValueT *value, bool b);
void         calogValueInt(CalogValueT *value, int64_t i);
void         calogValueReal(CalogValueT *value, double r);
int32_t      calogValueString(CalogValueT *value, const char *bytes, int64_t length);
void         calogValueAgg(CalogValueT *value, CalogAggT *aggregate);
void         calogValueFn(CalogValueT *value, CalogFnT *fn);
int32_t      calogValueCopy(CalogValueT *dst, const CalogValueT *src);
void         calogValueMove(CalogValueT *dst, CalogValueT *src);
void         calogValueFree(CalogValueT *value);
bool         calogValueEquals(const CalogValueT *a, const CalogValueT *b);

// ---- aggregates ----
int32_t      calogAggCreate(CalogAggT **out, CalogKindE kind);
void         calogAggFree(CalogAggT *aggregate);
CalogValueT *calogAggGet(CalogAggT *aggregate, const CalogValueT *key);
int32_t      calogAggPush(CalogAggT *aggregate, CalogValueT *value);
int32_t      calogAggSet(CalogAggT *aggregate, CalogValueT *key, CalogValueT *value);

// ---- function values ----
// Wrap a host native as a host-owned function value (runs on the host thread during
// calogPump, like calogRegister, but anonymous). Hand the result to a script via a
// native's result to give the script a callable; release your reference with
// calogFnRelease once you no longer hold it.
int32_t      calogFnFromNative(CalogFnT **out, CalogT *calog, CalogNativeFnT fn, void *userData);
int32_t      calogFnInvoke(CalogFnT *fn, CalogValueT *args, int32_t argCount, CalogValueT *result);
void         calogFnRetain(CalogFnT *fn);
void         calogFnRelease(CalogFnT *fn);

// ---- contexts + engines ----
CalogContextT *calogContextOpen(CalogT *calog, const CalogEngineT *engine);  // create + start; NULL on failure
// Make an engine available to calogContextLoad. Call at setup, before loading;
// registration order is the search priority.
void           calogRegisterEngine(CalogT *calog, const CalogEngineT *engine);
// Search the registered engines for a readable file "<baseFileName>.<ext>" (first
// engine, then first extension, that names an existing file wins), open a context on
// that engine, load the file into it (fire-and-forget), and return the context. NULL if
// no file matched or the load failed.
CalogContextT *calogContextLoad(CalogT *calog, const char *baseFileName);
int32_t        calogContextEval(CalogContextT *context, const char *source); // fire-and-forget
void           calogContextClose(CalogContextT *context);
uint64_t       calogContextId(const CalogContextT *context);
bool           calogContextFinished(const CalogContextT *context); // true once its thread has exited (safe to reap)
uint64_t       calogCurrentId(void);   // context id of the calling thread (0 = host thread)
CalogT        *calogCurrent(void);     // runtime of the calling context (NULL if none)
void           calogCurrentRetire(void);       // ask the calling context to retire itself (deferred; see calogTask)
bool           calogCurrentShuttingDown(void); // true once the calling context has been asked to close

extern const CalogEngineT calogLuaEngine;
extern const CalogEngineT calogJsEngine;
extern const CalogEngineT calogSquirrelEngine;
extern const CalogEngineT calogMyBasicEngine;
extern const CalogEngineT calogBerryEngine;
extern const CalogEngineT calogS7Engine;
extern const CalogEngineT calogWrenEngine;
extern const CalogEngineT calogMrubyEngine;

// Register the built-in engines selected at build time, so calogContextLoad works
// without hand-registering each. Define CALOG_WITH_LUA / _JS / _SQUIRREL / _MYBASIC (in
// the TU that calls this -- normally via your build's engine flags) for each engine you
// link; only those are referenced here, so unselected engines and their archives stay
// unlinked. The listed order is the load-search priority. Being header-inline, this
// emits nothing unless actually called.
static inline void calogRegisterBuiltinEngines(CalogT *calog) {
#ifdef CALOG_WITH_LUA
    calogRegisterEngine(calog, &calogLuaEngine);
#endif
#ifdef CALOG_WITH_JS
    calogRegisterEngine(calog, &calogJsEngine);
#endif
#ifdef CALOG_WITH_SQUIRREL
    calogRegisterEngine(calog, &calogSquirrelEngine);
#endif
#ifdef CALOG_WITH_MYBASIC
    calogRegisterEngine(calog, &calogMyBasicEngine);
#endif
#ifdef CALOG_WITH_BERRY
    calogRegisterEngine(calog, &calogBerryEngine);
#endif
#ifdef CALOG_WITH_S7
    calogRegisterEngine(calog, &calogS7Engine);
#endif
#ifdef CALOG_WITH_WREN
    calogRegisterEngine(calog, &calogWrenEngine);
#endif
#ifdef CALOG_WITH_MRUBY
    calogRegisterEngine(calog, &calogMrubyEngine);
#endif
    (void)calog;   // no-op if the build selected no engines
}

#endif
