#ifdef _WIN32
#include <windows.h>
#endif

#include "wota.h"

#define STB_DS_IMPLEMENTATION
#include "stb_ds.h"

#include "cell.h"
#include "cell_internal.h"
#include "cJSON.h"

#define BOOTSTRAP_MCODE      "boot/bootstrap.cm.mcode"
#define ENGINE_SRC           "internal/engine.cm"
#define CELL_SHOP_DIR ".cell"
#define CELL_CORE_DIR "packages/core"

#include <math.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#include "monocypher.h"

/* Test suite declarations */
int run_c_test_suite(JSContext *ctx);
static int run_test_suite(size_t heap_size);

cell_rt *root_cell = NULL;
static char *shop_path = NULL;
static char *core_path = NULL;
static int native_mode = 0;
static JSRuntime *g_runtime = NULL;

// Compute blake2b hash of data and return hex string (caller must free)
static char *compute_blake2_hex(const char *data, size_t size) {
  uint8_t hash[32];
  crypto_blake2b(hash, 32, (const uint8_t *)data, size);
  char *hex = malloc(65);
  for (int i = 0; i < 32; i++)
    snprintf(hex + i * 2, 3, "%02x", hash[i]);
  return hex;
}

// Build cache path: shop_path/build/<hex> (caller must free)
static char *build_cache_path(const char *hex) {
  if (!shop_path) return NULL;
  size_t len = strlen(shop_path) + strlen("/build/") + 64 + 1;
  char *path = malloc(len);
  snprintf(path, len, "%s/build/%s", shop_path, hex);
  return path;
}

// Write binary data to file
static int write_cache_file(const char *path, const uint8_t *data, size_t size) {
  FILE *fh = fopen(path, "wb");
  if (!fh) return 0;
  size_t written = fwrite(data, 1, size, fh);
  fclose(fh);
  return written == size;
}

// Load cached .mach or compile from .mcode and cache result
// Returns heap-allocated binary data and sets *out_size, or NULL on failure
static char *load_or_cache_bootstrap(const char *mcode_data, size_t mcode_size, size_t *out_size) {
  char *hex = compute_blake2_hex(mcode_data, mcode_size);
  char *cpath = build_cache_path(hex);
  free(hex);

  if (cpath) {
    // Try loading from cache
    FILE *fh = fopen(cpath, "rb");
    if (fh) {
      fseek(fh, 0, SEEK_END);
      long file_size = ftell(fh);
      fseek(fh, 0, SEEK_SET);
      char *data = malloc(file_size);
      if (data && fread(data, 1, file_size, fh) == (size_t)file_size) {
        fclose(fh);
        free(cpath);
        *out_size = file_size;
        return data;
      }
      free(data);
      fclose(fh);
    }
  }

  // Cache miss: compile mcode to binary
  cJSON *mcode = cJSON_Parse(mcode_data);
  if (!mcode) { free(cpath); return NULL; }

  MachCode *mc = mach_compile_mcode(mcode);
  cJSON_Delete(mcode);
  if (!mc) { free(cpath); return NULL; }

  size_t bin_size;
  uint8_t *bin = JS_SerializeMachCode(mc, &bin_size);
  JS_FreeMachCode(mc);
  if (!bin) { free(cpath); return NULL; }

  // Write to cache
  if (cpath) {
    write_cache_file(cpath, bin, bin_size);
    free(cpath);
  }

  *out_size = bin_size;
  return (char *)bin;
}

// Get the home directory
static const char* get_home_dir(void) {
  const char *home = getenv("HOME");
  if (!home) {
    home = getenv("USERPROFILE"); // Windows fallback
  }
  return home;
}

// Resolve shop_path and core_path
// core: --core flag > CELL_CORE env > derived from shop
// shop: --shop flag > CELL_SHOP env > ~/.cell
int find_cell_shop(const char *shop_override, const char *core_override)
{
  // Resolve shop_path
  if (shop_override) {
    shop_path = strdup(shop_override);
  } else {
    const char *env = getenv("CELL_SHOP");
    if (env) {
      shop_path = strdup(env);
    } else {
      const char *home = get_home_dir();
      if (!home && !core_override && !getenv("CELL_CORE")) {
        printf("ERROR: Could not determine home directory. Set HOME environment variable.\n");
        return 0;
      }
      if (home) {
        size_t path_len = strlen(home) + strlen("/" CELL_SHOP_DIR) + 1;
        shop_path = malloc(path_len);
        if (shop_path)
          snprintf(shop_path, path_len, "%s/" CELL_SHOP_DIR, home);
      }
    }
  }

  // Resolve core_path
  if (core_override) {
    core_path = strdup(core_override);
  } else {
    const char *env = getenv("CELL_CORE");
    if (env) {
      core_path = strdup(env);
    } else if (shop_path) {
      size_t core_len = strlen(shop_path) + strlen("/" CELL_CORE_DIR) + 1;
      core_path = malloc(core_len);
      if (core_path)
        snprintf(core_path, core_len, "%s/" CELL_CORE_DIR, shop_path);
    }
  }

  if (!core_path) {
    printf("ERROR: No core path. Use --core <path> or set CELL_CORE.\n");
    return 0;
  }

  // Check if the core directory exists
  struct stat st;
  if (stat(core_path, &st) != 0 || !S_ISDIR(st.st_mode)) {
    printf("ERROR: Core not found at %s\n", core_path);
    return 0;
  }

  return 1;
}

// Load a file from the core directory
static char* load_core_file(const char *filename, size_t *out_size) {
  if (!core_path) return NULL;

  size_t path_len = strlen(core_path) + 1 + strlen(filename) + 1;
  char *full_path = malloc(path_len);
  if (!full_path) return NULL;

  snprintf(full_path, path_len, "%s/%s", core_path, filename);

  FILE *fh = fopen(full_path, "rb");
  free(full_path);

  if (!fh) return NULL;

  fseek(fh, 0, SEEK_END);
  long file_size = ftell(fh);
  fseek(fh, 0, SEEK_SET);

  char *data = malloc(file_size + 1);
  if (!data) {
    fclose(fh);
    return NULL;
  }

  if (fread(data, 1, file_size, fh) != (size_t)file_size) {
    free(data);
    fclose(fh);
    return NULL;
  }

  fclose(fh);
  data[file_size] = 0;

  if (out_size) *out_size = file_size;
  return data;
}

// Try loading engine.cm from source-hash cache
// Returns heap-allocated binary data and sets *out_size, or NULL on cache miss
static char *try_engine_cache(size_t *out_size) {
  size_t src_size;
  char *src = load_core_file(ENGINE_SRC, &src_size);
  if (!src) return NULL;

  char *hex = compute_blake2_hex(src, src_size);
  free(src);
  char *cpath = build_cache_path(hex);
  if (!cpath) { free(hex); return NULL; }
  free(hex);

  FILE *fh = fopen(cpath, "rb");
  if (!fh) { free(cpath); return NULL; }
  free(cpath);

  fseek(fh, 0, SEEK_END);
  long file_size = ftell(fh);
  fseek(fh, 0, SEEK_SET);
  char *data = malloc(file_size);
  if (data && fread(data, 1, file_size, fh) == (size_t)file_size) {
    fclose(fh);
    *out_size = file_size;
    return data;
  }
  free(data);
  fclose(fh);
  return NULL;
}

// Get the core path for use by scripts
const char* cell_get_core_path(void) {
  return core_path;
}

void actor_disrupt(cell_rt *crt)
{
  crt->disrupt = 1;
  JS_SetPauseFlag(crt->context, 2);
  if (crt->state != ACTOR_RUNNING)
    actor_free(crt);
}

JSValue js_core_os_use(JSContext *js);
JSValue js_core_json_use(JSContext *js);

void script_startup(cell_rt *prt)
{
  if (!g_runtime) {
    g_runtime = JS_NewRuntime();
  }
  JSContext *js = JS_NewContext(g_runtime);

  JS_SetContextOpaque(js, prt);
  prt->context = js;

  /* Set per-actor heap memory limit */
  JS_SetHeapMemoryLimit(js, ACTOR_MEMORY_LIMIT);

  /* Register all GCRef fields so the Cheney GC can relocate them. */
  JS_AddGCRef(js, &prt->idx_buffer_ref);
  JS_AddGCRef(js, &prt->on_exception_ref);
  JS_AddGCRef(js, &prt->message_handle_ref);
  JS_AddGCRef(js, &prt->unneeded_ref);
  JS_AddGCRef(js, &prt->actor_sym_ref);
  prt->idx_buffer_ref.val = JS_NULL;
  prt->on_exception_ref.val = JS_NULL;
  prt->message_handle_ref.val = JS_NULL;
  prt->unneeded_ref.val = JS_NULL;
  prt->actor_sym_ref.val = JS_NULL;

  cell_rt *crt = JS_GetContextOpaque(js);
  JS_FreeValue(js, js_core_blob_use(js));

  // Try engine fast-path: load engine.cm from source-hash cache
  size_t bin_size;
  char *bin_data = try_engine_cache(&bin_size);

  if (!bin_data) {
    // Cold path: run bootstrap to seed cache, then retry
    size_t boot_size;
    char *boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
    if (!boot_data) {
      printf("ERROR: Could not load bootstrap from %s!\n", core_path);
      return;
    }
    size_t boot_bin_size;
    char *boot_bin = load_or_cache_bootstrap(boot_data, boot_size, &boot_bin_size);
    free(boot_data);
    if (!boot_bin) {
      printf("ERROR: Failed to compile bootstrap mcode!\n");
      return;
    }

    // Build env for bootstrap (only needs os, core_path, shop_path)
    JSGCRef boot_env_ref;
    JS_AddGCRef(js, &boot_env_ref);
    boot_env_ref.val = JS_NewObject(js);
    JSValue btmp;
    btmp = js_core_os_use(js);
    JS_SetPropertyStr(js, boot_env_ref.val, "os", btmp);
    if (core_path) {
      btmp = JS_NewString(js, core_path);
      JS_SetPropertyStr(js, boot_env_ref.val, "core_path", btmp);
    }
    btmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
    JS_SetPropertyStr(js, boot_env_ref.val, "shop_path", btmp);
    JSValue boot_env = JS_Stone(js, boot_env_ref.val);
    JS_DeleteGCRef(js, &boot_env_ref);

    crt->state = ACTOR_RUNNING;
    JSValue bv = JS_RunMachBin(js, (const uint8_t *)boot_bin, boot_bin_size, boot_env);
    free(boot_bin);
    uncaught_exception(js, bv);
    crt->state = ACTOR_IDLE;

    // Retry engine from cache
    bin_data = try_engine_cache(&bin_size);
    if (!bin_data) {
      printf("ERROR: Bootstrap ran but engine.cm not in cache!\n");
      return;
    }
  }

  // Build engine environment
  JSGCRef env_ref;
  JS_AddGCRef(js, &env_ref);
  env_ref.val = JS_NewObject(js);
  JSValue tmp;
  tmp = js_core_os_use(js);
  JS_SetPropertyStr(js, env_ref.val, "os", tmp);
  tmp = js_core_json_use(js);
  JS_SetPropertyStr(js, env_ref.val, "json", tmp);

  crt->actor_sym_ref.val = JS_NewObject(js);
  JS_CellStone(js, crt->actor_sym_ref.val);
  JS_SetActorSym(js, JS_DupValue(js, crt->actor_sym_ref.val));
  JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));

  // Always set init (even if null)
  if (crt->init_wota) {
    JSGCRef init_ref;
    JS_PushGCRef(js, &init_ref);
    init_ref.val = wota2value(js, crt->init_wota);
    JS_SetPropertyStr(js, env_ref.val, "init", init_ref.val);
    JS_PopGCRef(js, &init_ref);
    free(crt->init_wota);
    crt->init_wota = NULL;
  } else {
    JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
  }

  // Set args to null for actor spawn (not CLI mode)
  JS_SetPropertyStr(js, env_ref.val, "args", JS_NULL);

  if (core_path) {
    tmp = JS_NewString(js, core_path);
    JS_SetPropertyStr(js, env_ref.val, "core_path", tmp);
  }
  tmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
  JS_SetPropertyStr(js, env_ref.val, "shop_path", tmp);

  // Stone the environment
  JSValue hidden_env = JS_Stone(js, env_ref.val);
  JS_DeleteGCRef(js, &env_ref);

  // Run engine from binary
  crt->state = ACTOR_RUNNING;
  JSValue v = JS_RunMachBin(js, (const uint8_t *)bin_data, bin_size, hidden_env);
  free(bin_data);
  uncaught_exception(js, v);
  crt->state = ACTOR_IDLE;
  set_actor_state(crt);
}

static void signal_handler(int sig)
{
  const char *str = NULL;
#ifndef TARGET_PLAYDATE
  switch (sig) {
    case SIGABRT: str = "SIGABRT"; break;
    case SIGFPE:  str = "SIGFPE";  break;
    case SIGILL:  str = "SIGILL";  break;
    case SIGINT:  str = "SIGINT";  break;
    case SIGSEGV: str = "SIGSEGV"; break;
    case SIGTERM: str = "SIGTERM"; break;
  }
#endif
  if (!str) return;

  exit_handler();
}

/* Run the C test suite with minimal runtime setup */
static int run_test_suite(size_t heap_size)
{
  JSRuntime *rt = JS_NewRuntime();
  if (!rt) {
    printf("Failed to create JS runtime\n");
    return 1;
  }

  JSContext *ctx = JS_NewContextWithHeapSize(rt, heap_size);
  if (!ctx) {
    printf("Failed to create JS context\n");
    JS_FreeRuntime(rt);
    return 1;
  }

  int result = run_c_test_suite(ctx);

  JS_FreeContext(ctx);
  JS_FreeRuntime(rt);

  return result;
}

static void print_usage(const char *prog)
{
  printf("Usage: %s [options] <program> [args...]\n\n", prog);
  printf("Run a cell program (.ce actor).\n\n");
  printf("Options:\n");
  printf("  --core <path>              Set core path directly (overrides CELL_CORE)\n");
  printf("  --shop <path>              Set shop path (overrides CELL_SHOP)\n");
  printf("  --dev                      Dev mode (shop=.cell, core=.)\n");
  printf("  --native                   Use AOT native code instead of bytecode\n");
  printf("  --heap <size>              Initial heap size (e.g. 256MB, 1GB)\n");
  printf("  --test [heap_size]         Run C test suite\n");
  printf("  -h, --help                 Show this help message\n");
  printf("\nEnvironment:\n");
  printf("  CELL_CORE                  Core path (default: <shop>/packages/core)\n");
  printf("  CELL_SHOP                  Shop path (default: ~/.cell)\n");
  printf("\nRecompile after changes:  make\n");
  printf("Bootstrap from scratch:  make bootstrap\n");
}

int cell_init(int argc, char **argv)
{
  /* Check for --help flag */
  if (argc >= 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) {
    print_usage(argv[0]);
    return 0;
  }

  /* Check for --test flag to run C test suite */
  if (argc >= 2 && strcmp(argv[1], "--test") == 0) {
    size_t heap_size = 64 * 1024; /* 64KB default */
    if (argc >= 3) {
      heap_size = strtoull(argv[2], NULL, 0);
      /* Round up to power of 2 for buddy allocator */
      size_t p = 1;
      while (p < heap_size) p <<= 1;
      heap_size = p;
    }
    return run_test_suite(heap_size);
  }

  /* Default: run script through engine pipeline */
  int arg_start = 1;
  size_t heap_size = 1024 * 1024; /* 1MB default */
  const char *shop_override = NULL;
  const char *core_override = NULL;

  // Parse flags (order-independent)
  while (arg_start < argc && argv[arg_start][0] == '-') {
    if (strcmp(argv[arg_start], "--shop") == 0) {
      if (arg_start + 1 >= argc) {
        printf("ERROR: --shop requires a path argument\n");
        return 1;
      }
      shop_override = argv[arg_start + 1];
      arg_start += 2;
    } else if (strcmp(argv[arg_start], "--core") == 0) {
      if (arg_start + 1 >= argc) {
        printf("ERROR: --core requires a path argument\n");
        return 1;
      }
      core_override = argv[arg_start + 1];
      arg_start += 2;
    } else if (strcmp(argv[arg_start], "--heap") == 0) {
      if (arg_start + 1 >= argc) {
        printf("ERROR: --heap requires a size argument (e.g. 1GB, 256MB, 65536)\n");
        return 1;
      }
      char *end = NULL;
      heap_size = strtoull(argv[arg_start + 1], &end, 0);
      if (end && (*end == 'G' || *end == 'g')) heap_size *= 1024ULL * 1024 * 1024;
      else if (end && (*end == 'M' || *end == 'm')) heap_size *= 1024ULL * 1024;
      else if (end && (*end == 'K' || *end == 'k')) heap_size *= 1024ULL;
      arg_start += 2;
    } else if (strcmp(argv[arg_start], "--dev") == 0) {
      shop_override = ".cell";
      core_override = ".";
      mkdir(".cell", 0755);
      mkdir(".cell/build", 0755);
      mkdir(".cell/cache", 0755);
      mkdir(".cell/packages", 0755);
      /* Ensure .cell/packages/core -> . symlink exists */
      struct stat lst;
      if (lstat(".cell/packages/core", &lst) != 0)
        symlink("../..", ".cell/packages/core");
      arg_start++;
    } else if (strcmp(argv[arg_start], "--native") == 0) {
      native_mode = 1;
      arg_start++;
    } else {
      break;
    }
  }

  if (arg_start >= argc) {
    print_usage(argv[0]);
    return 1;
  }

  if (!find_cell_shop(shop_override, core_override)) return 1;

  actor_initialize();

  g_runtime = JS_NewRuntime();
  if (!g_runtime) {
    printf("Failed to create JS runtime\n");
    return 1;
  }
  JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, heap_size);
  if (!ctx) {
    printf("Failed to create JS context\n");
    JS_FreeRuntime(g_runtime);
    return 1;
  }

  /* Create a cell_rt for the CLI context so JS-C bridge functions work */
  cell_rt *cli_rt = calloc(sizeof(*cli_rt), 1);
  cli_rt->mutex = malloc(sizeof(pthread_mutex_t));
  pthread_mutexattr_t mattr;
  pthread_mutexattr_init(&mattr);
  pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
  pthread_mutex_init(cli_rt->mutex, &mattr);
  cli_rt->msg_mutex = malloc(sizeof(pthread_mutex_t));
  pthread_mutex_init(cli_rt->msg_mutex, &mattr);
  pthread_mutexattr_destroy(&mattr);

  cli_rt->context = ctx;
  JS_SetContextOpaque(ctx, cli_rt);

  JS_AddGCRef(ctx, &cli_rt->idx_buffer_ref);
  JS_AddGCRef(ctx, &cli_rt->on_exception_ref);
  JS_AddGCRef(ctx, &cli_rt->message_handle_ref);
  JS_AddGCRef(ctx, &cli_rt->unneeded_ref);
  JS_AddGCRef(ctx, &cli_rt->actor_sym_ref);
  cli_rt->idx_buffer_ref.val = JS_NULL;
  cli_rt->on_exception_ref.val = JS_NULL;
  cli_rt->message_handle_ref.val = JS_NULL;
  cli_rt->unneeded_ref.val = JS_NULL;
  cli_rt->actor_sym_ref.val = JS_NewObject(ctx);
  JS_CellStone(ctx, cli_rt->actor_sym_ref.val);
  JS_SetActorSym(ctx, JS_DupValue(ctx, cli_rt->actor_sym_ref.val));

  root_cell = cli_rt;

  JS_FreeValue(ctx, js_core_blob_use(ctx));

  int exit_code = 0;

  // Try engine fast-path: load engine.cm from source-hash cache
  size_t bin_size;
  char *bin_data = try_engine_cache(&bin_size);

  if (!bin_data) {
    // Cold path: run bootstrap to seed cache, then retry
    size_t boot_size;
    char *boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
    if (!boot_data) {
      printf("ERROR: Could not load bootstrap from %s\n", core_path);
      return 1;
    }
    size_t boot_bin_size;
    char *boot_bin = load_or_cache_bootstrap(boot_data, boot_size, &boot_bin_size);
    free(boot_data);
    if (!boot_bin) {
      printf("ERROR: Failed to compile bootstrap mcode\n");
      return 1;
    }

    // Build env for bootstrap (os, core_path, shop_path required;
    // args, json, actorsym provided for compatibility)
    JSGCRef boot_env_ref;
    JS_AddGCRef(ctx, &boot_env_ref);
    boot_env_ref.val = JS_NewObject(ctx);
    JSValue btmp;
    btmp = js_core_os_use(ctx);
    JS_SetPropertyStr(ctx, boot_env_ref.val, "os", btmp);
    btmp = JS_NewString(ctx, core_path);
    JS_SetPropertyStr(ctx, boot_env_ref.val, "core_path", btmp);
    btmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
    JS_SetPropertyStr(ctx, boot_env_ref.val, "shop_path", btmp);
    JS_SetPropertyStr(ctx, boot_env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
    btmp = js_core_json_use(ctx);
    JS_SetPropertyStr(ctx, boot_env_ref.val, "json", btmp);
    JS_SetPropertyStr(ctx, boot_env_ref.val, "init", JS_NULL);
    JSGCRef boot_args_ref;
    JS_AddGCRef(ctx, &boot_args_ref);
    boot_args_ref.val = JS_NewArray(ctx);
    for (int i = arg_start; i < argc; i++) {
      JSValue str = JS_NewString(ctx, argv[i]);
      JS_ArrayPush(ctx, &boot_args_ref.val, str);
    }
    JS_SetPropertyStr(ctx, boot_env_ref.val, "args", boot_args_ref.val);
    JS_DeleteGCRef(ctx, &boot_args_ref);
    JSValue boot_env = JS_Stone(ctx, boot_env_ref.val);
    JS_DeleteGCRef(ctx, &boot_env_ref);

    JSValue boot_result = JS_RunMachBin(ctx, (const uint8_t *)boot_bin, boot_bin_size, boot_env);
    free(boot_bin);
    if (JS_IsException(boot_result)) {
      JS_GetException(ctx);
      printf("ERROR: Bootstrap failed\n");
      return 1;
    }

    // Retry engine from cache (new-style bootstrap seeds it)
    bin_data = try_engine_cache(&bin_size);
    if (!bin_data) {
      // Old-style bootstrap already ran the program — skip engine load
      goto check_actors;
    }
  }

  {
    // Build engine environment
    JSGCRef env_ref;
    JS_AddGCRef(ctx, &env_ref);
    env_ref.val = JS_NewObject(ctx);
    JSValue tmp;
    tmp = js_core_os_use(ctx);
    JS_SetPropertyStr(ctx, env_ref.val, "os", tmp);
    tmp = JS_NewString(ctx, core_path);
    JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
    tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
    JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp);
    JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
    tmp = js_core_json_use(ctx);
    JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
    if (native_mode) {
      JSGCRef init_ref;
      JS_AddGCRef(ctx, &init_ref);
      init_ref.val = JS_NewObject(ctx);
      JS_SetPropertyStr(ctx, init_ref.val, "native_mode", JS_NewBool(ctx, 1));
      JS_SetPropertyStr(ctx, env_ref.val, "init", init_ref.val);
      JS_DeleteGCRef(ctx, &init_ref);
    } else {
      JS_SetPropertyStr(ctx, env_ref.val, "init", JS_NULL);
    }
    JSGCRef args_ref;
    JS_AddGCRef(ctx, &args_ref);
    args_ref.val = JS_NewArray(ctx);
    for (int i = arg_start; i < argc; i++) {
      JSValue str = JS_NewString(ctx, argv[i]);
      JS_ArrayPush(ctx, &args_ref.val, str);
    }
    JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val);
    JS_DeleteGCRef(ctx, &args_ref);
    JSValue hidden_env = JS_Stone(ctx, env_ref.val);

    JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
    JS_DeleteGCRef(ctx, &env_ref);
    free(bin_data);

    if (JS_IsException(result)) {
      JS_GetException(ctx);
      exit_code = 1;
    } else if (!JS_IsNull(result)) {
      const char *str = JS_ToCString(ctx, result);
      if (str) {
        printf("%s\n", str);
        JS_FreeCString(ctx, str);
      }
    }
  }

check_actors:
  if (scheduler_actor_count() > 0) {
    scheduler_enable_quiescence();
    actor_loop();
    exit_handler();
    exit(0);
  }

  /* No actors spawned — clean up CLI context */
  JS_DeleteGCRef(ctx, &cli_rt->idx_buffer_ref);
  JS_DeleteGCRef(ctx, &cli_rt->on_exception_ref);
  JS_DeleteGCRef(ctx, &cli_rt->message_handle_ref);
  JS_DeleteGCRef(ctx, &cli_rt->unneeded_ref);
  JS_DeleteGCRef(ctx, &cli_rt->actor_sym_ref);

  pthread_mutex_destroy(cli_rt->mutex);
  free(cli_rt->mutex);
  pthread_mutex_destroy(cli_rt->msg_mutex);
  free(cli_rt->msg_mutex);
  free(cli_rt);
  root_cell = NULL;

  JS_FreeContext(ctx);
  JS_FreeRuntime(g_runtime);
  g_runtime = NULL;

  exit_handler();
  return exit_code;
}

int JS_ArrayLength(JSContext *js, JSValue a)
{
  int64_t len;
  JS_GetLength(js, a, &len);
  return len;
}

int js2bool(JSContext *js, JSValue v) { return JS_ToBool(js,v); }
JSValue bool2js(JSContext *js, int b) { return JS_NewBool(js,b); }

JSValue number2js(JSContext *js, double g) { return JS_NewFloat64(js,g); }
double js2number(JSContext *js, JSValue v) {
  double g;
  JS_ToFloat64(js, &g, v);
  if (isnan(g)) g = 0;
  return g;
}

uint64_t cell_random_fit() {
  uint64_t buf;
  randombytes((uint8_t *)&buf, sizeof(buf));
  return buf >> 11;
}

double cell_random() {
  uint64_t buf = cell_random_fit();
  return (double)buf / 9007199254740992.0;
}

void cell_trace_sethook(cell_hook)
{

}

int uncaught_exception(JSContext *js, JSValue v)
{
  int has_exc = JS_HasException(js);
  int is_exc = JS_IsException(v);
  if (!has_exc && !is_exc)
    return 1;
  /* Error message and backtrace were already printed to stderr
     by JS_ThrowError2 / print_backtrace. Just clear the flag. */
  if (has_exc)
    JS_GetException(js);
  cell_rt *crt = JS_GetContextOpaque(js);
  if (crt && !JS_IsNull(crt->on_exception_ref.val)) {
    /* Disable interruption so actor_die can send messages
       without being re-interrupted. */
    JS_SetPauseFlag(js, 0);
    JSValue err = JS_NewString(js, "interrupted");
    JS_Call(js, crt->on_exception_ref.val, JS_NULL, 1, &err);
    /* Clear any secondary exception from the callback. */
    if (JS_HasException(js))
      JS_GetException(js);
  }
  return 0;
}
