#ifndef CELL_H
#define CELL_H

#include "quickjs.h"
#include "blob.h"

#ifdef __cplusplus
extern "C" {
#endif

// blob fns
JSValue js_core_blob_use(JSContext *js);
JSValue js_new_blob_stoned_copy(JSContext *js, void *data, size_t bytes);
void *js_get_blob_data(JSContext *js, size_t *size, JSValue v); // bytes
void *js_get_blob_data_bits(JSContext *js, size_t *bits, JSValue v); // bits
int js_is_blob(JSContext *js, JSValue v);

double cell_random();
uint64_t cell_random_fit();

int JS_ArrayLength(JSContext *js, JSValue a);
 
int js2bool(JSContext *js, JSValue v);
JSValue bool2js(JSContext *js, int b);
double js2number(JSContext *js, JSValue v);
JSValue number2js(JSContext *js, double g);

JSValue wota2value(JSContext *js, void *v);
void *value2wota(JSContext *js, JSValue v, JSValue replacer, size_t *bytes);

JSValue nota2value(JSContext *js, void *nota);
void *value2nota(JSContext *js, JSValue v);

JSValue js_core_json_use(JSContext *js);

#define CELL_HOOK_ENTER 1
#define CELL_HOOK_EXIT 2
typedef void (*cell_hook)(const char *name, int type);
void cell_trace_sethook(cell_hook);

// Macros to help with creating scripts
#define MIST_CFUNC_DEF(name, length, func1, props) { name, props, JS_DEF_CFUNC, 0, .u = { .func = { length, JS_CFUNC_generic, { .generic = func1 } } } }

#define MIST_FUNC_DEF(TYPE, FN, LEN) MIST_CFUNC_DEF(#FN, LEN, js_##TYPE##_##FN, 0)
#define PROTO_FUNC_DEF(TYPE, FN, LEN) MIST_CFUNC_DEF(#FN, LEN, js_##TYPE##_##FN, 0)

#define JS_SETSIG JSContext *js, JSValue self, JSValue val
#define JSC_CCALL(NAME, ...) static JSValue js_##NAME (JSContext *js, JSValue self, int argc, JSValue *argv) { \
  JSValue ret = JS_NULL; \
  __VA_ARGS__  ;\
  return ret; \
}

#define JSC_CCALL_EXTERN(NAME, ...) JSValue js_##NAME (JSContext *js, JSValue self, int argc, JSValue *argv) { \
  JSValue ret = JS_NULL; \
  __VA_ARGS__  ;\
  return ret; \
}

#define JSC_SCALL(NAME, ...) JSC_CCALL(NAME, \
  const char *str = JS_ToCString(js,argv[0]); \
  __VA_ARGS__  ;\
  JS_FreeCString(js,str); \
)

#define JSC_SSCALL(NAME, ...) JSC_CCALL(NAME, \
  const char *str = NULL; \
  const char *str2 = NULL; \
  if (!JS_IsNull(argv[0])) str = JS_ToCString(js,argv[0]); \
  if (!JS_IsNull(argv[1])) str2 = JS_ToCString(js,argv[1]); \
  __VA_ARGS__ ; \
  JS_FreeCString(js,str2); \
  JS_FreeCString(js,str); \
) \

#define QJSCLASS(TYPE, ...)\
JSClassID js_##TYPE##_id;\
static void js_##TYPE##_finalizer(JSRuntime *rt, JSValue val){\
TYPE *n = JS_GetOpaque(val, js_##TYPE##_id); \
TYPE##_free(rt,n);}\
static JSClassDef js_##TYPE##_class = {\
  .class_name = #TYPE,\
  .finalizer = js_##TYPE##_finalizer,\
};\
TYPE *js2##TYPE (JSContext *js, JSValue val) { \
  if (JS_GetClassID(val) != js_##TYPE##_id) return NULL; \
  return JS_GetOpaque(val,js_##TYPE##_id); \
}\
JSValue TYPE##2js(JSContext *js, TYPE *n) { \
  JSValue j = JS_NewObjectClass(js,js_##TYPE##_id);\
  JS_SetOpaque(j,n);\
  __VA_ARGS__ \
  return j; }\
\

#define QJSCLASSMARK(TYPE, ...)\
static void js_##TYPE##_finalizer(JSRuntime *rt, JSValue val){\
TYPE *n = JS_GetOpaque(val, js_##TYPE##_id);\
TYPE##_free(rt,n);}\
static JSClassDef js_##TYPE##_class = {\
  .class_name = #TYPE,\
  .finalizer = js_##TYPE##_finalizer,\
};\
TYPE *js2##TYPE (JSContext *js, JSValue val) { \
  if (JS_GetClassID(val) != js_##TYPE##_id) return NULL; \
  return JS_GetOpaque(val,js_##TYPE##_id); \
}\
JSValue TYPE##2js(JSContext *js, TYPE *n) { \
  JSValue j = JS_NewObjectClass(js,js_##TYPE##_id);\
  JS_SetOpaque(j,n);\
  __VA_ARGS__ \
  return j; }\
\

#define QJSCLASSMARK_EXTERN(TYPE, ...)\
static void js_##TYPE##_finalizer(JSRuntime *rt, JSValue val){\
TYPE *n = JS_GetOpaque(val, js_##TYPE##_id);\
TYPE##_free(rt,n);}\
static JSClassDef js_##TYPE##_class = {\
  .class_name = #TYPE,\
  .finalizer = js_##TYPE##_finalizer,\
};\
extern JSClassID js_##TYPE##_id;\
TYPE *js2##TYPE (JSContext *js, JSValue val) { \
  if (JS_GetClassID(val) != js_##TYPE##_id) return NULL; \
  return JS_GetOpaque(val,js_##TYPE##_id); \
}\
JSValue TYPE##2js(JSContext *js, TYPE *n) { \
  JSValue j = JS_NewObjectClass(js,js_##TYPE##_id);\
  JS_SetOpaque(j,n);\
  __VA_ARGS__ \
  return j; }\
\

/* Defines a class and uses its function list as its prototype */
#define QJSCLASSPREP_FUNCS(TYPE) \
QJSCLASSPREP_NO_FUNCS(TYPE) \
JS_SetPropertyFunctionList(js, TYPE##_proto, js_##TYPE##_funcs, countof(js_##TYPE##_funcs)); \

#define QJSCLASSPREP_NO_FUNCS(TYPE) \
JS_NewClassID(&js_##TYPE##_id);\
JS_NewClass(js, js_##TYPE##_id, &js_##TYPE##_class);\
JSValue TYPE##_proto = JS_NewObject(js); \
JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \

#define QJSCLASSPREP_FUNCS_CTOR(TYPE, CTOR_ARGC) \
  ({ \
    QJSCLASSPREP_FUNCS(TYPE); \
    JSValue TYPE##_ctor = JS_NewCFunction2(js, js_##TYPE##_constructor, #TYPE, CTOR_ARGC, JS_CFUNC_generic, 0); \
    TYPE##_ctor; \
  })

#define countof(x) (sizeof(x)/sizeof((x)[0]))

/* GC safety macros for C functions that allocate multiple heap objects.
   Any allocation call (JS_NewObject, JS_SetPropertyStr, etc.) can trigger GC.
   JS_ROOT style: explicit, use .val to access the rooted value.
   JS_LOCAL style: transparent, GC updates the C local through a pointer. */

#define JS_FRAME(ctx) \
  JSContext *_js_ctx = (ctx); \
  JSGCRef *_js_gc_frame = JS_GetGCFrame(_js_ctx); \
  JSLocalRef *_js_local_frame = JS_GetLocalFrame(_js_ctx)

#define JS_ROOT(name, init) \
  JSGCRef name; \
  JS_PushGCRef(_js_ctx, &name); \
  name.val = (init)

#define JS_LOCAL(name, init) \
  JSValue name = (init); \
  JSLocalRef name##__lr; \
  name##__lr.ptr = &name; \
  JS_PushLocalRef(_js_ctx, &name##__lr)

#define JS_RETURN(val) do { \
  JSValue _js_ret = (val); \
  JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
  return _js_ret; \
} while (0)

#define JS_RETURN_NULL() do { \
  JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
  return JS_NULL; \
} while (0)

#define JS_RETURN_EX() do { \
  JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
  return JS_EXCEPTION; \
} while (0)

// Safe integer property extraction (null → 0)
#define JS_GETINT(JS, TARGET, VALUE, PROP) { \
  JSValue __##PROP##__v = JS_GetPropertyStr(JS, VALUE, #PROP); \
  TARGET = JS_IsNull(__##PROP##__v) ? 0 : (int)js2number(JS, __##PROP##__v); \
  JS_FreeValue(JS, __##PROP##__v); }

// Common macros for property access
#define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \
TARGET = js2##TYPE(JS, __##PROP##__v); \
JS_FreeValue(JS,__##PROP##__v); }\

#define JS_GETATOM(JS, TARGET, VALUE, ATOM, TYPE) {\
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#ATOM); \
TARGET = js2##TYPE(JS, __##PROP##__v); \
JS_FreeValue(JS,__##PROP##__v); }\

// X-macro enum definition system for string<->enum conversion
#define ENUM_MAPPING_TABLE(ENUM) \
  static const struct { int value; const char *name; } ENUM##_mapping[]

#define JS2ENUM(NAME)                                         \
int js2##NAME(JSContext *js, JSValue v) {                    \
  if (JS_IsNull(v)) return 0;                                \
  const char *str = JS_ToCString(js, v);                     \
  if (!str) return 0;                                        \
  for(int i = 0; i < sizeof(NAME##_mapping)/sizeof(NAME##_mapping[0]); i++) \
    if(!strcmp(NAME##_mapping[i].name, str)) {               \
      JS_FreeCString(js, str);                               \
      return NAME##_mapping[i].value;                        \
    }                                                        \
  JS_FreeCString(js, str);                                   \
  return 0;                                                  \
}                                                            \
JSValue NAME##2js(JSContext *js, int enumval) {              \
  for(int i = 0; i < sizeof(NAME##_mapping)/sizeof(NAME##_mapping[0]); i++) \
    if(NAME##_mapping[i].value == enumval)                   \
      return JS_NewString(js, NAME##_mapping[i].name);       \
  return JS_NULL;                                            \
}

#define CELL_USE_INIT(c) \
JSValue CELL_USE_NAME(JSContext *js) { do { c ; } while(0);  }

#define CELL_USE_FUNCS(FUNCS) \
JSValue CELL_USE_NAME(JSContext *js) { \
  JS_FRAME(js); \
  JS_LOCAL(mod, JS_NewObject(js)); \
  JS_SetPropertyFunctionList(js, mod, FUNCS, countof(FUNCS)); \
  JS_RETURN(mod); }

#define CELL_PROGRAM_INIT(c) \
JSValue CELL_USE_NAME(JSContext *js) { do { c ; } while(0); }


#ifdef __cplusplus
}
#endif

#endif
