/*
 * QBE Backend — in-process QBE IR → assembly compilation.
 *
 * Wraps QBE as a library: feeds IR text via fmemopen(), captures
 * assembly output via open_memstream(), returns it as a JS string.
 * No subprocess, no temp files for IR, no external qbe binary needed.
 */

#include "cell.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* QBE headers */
#include "all.h"
#include "config.h"

/* QBE globals (declared extern in all.h) */
Target T;
char debug['Z'+1] = {0};

extern Target T_amd64_sysv;
extern Target T_amd64_apple;
extern Target T_amd64_win;
extern Target T_arm64;
extern Target T_arm64_apple;
extern Target T_rv64;

/* Captured output stream — set before calling parse() */
static FILE *qbe_outf;

static void qbe_data(Dat *d) {
  emitdat(d, qbe_outf);
  if (d->type == DEnd) {
    fputs("/* end data */\n\n", qbe_outf);
    freeall();
  }
}

static void qbe_func(Fn *fn) {
  uint n;

  T.abi0(fn);
  fillcfg(fn);
  filluse(fn);
  promote(fn);
  filluse(fn);
  ssa(fn);
  filluse(fn);
  ssacheck(fn);
  fillalias(fn);
  loadopt(fn);
  filluse(fn);
  fillalias(fn);
  coalesce(fn);
  filluse(fn);
  filldom(fn);
  ssacheck(fn);
  gvn(fn);
  fillcfg(fn);
  simplcfg(fn);
  filluse(fn);
  filldom(fn);
  gcm(fn);
  filluse(fn);
  ssacheck(fn);
  if (T.cansel) {
    ifconvert(fn);
    fillcfg(fn);
    filluse(fn);
    filldom(fn);
    ssacheck(fn);
  }
  T.abi1(fn);
  simpl(fn);
  fillcfg(fn);
  filluse(fn);
  T.isel(fn);
  fillcfg(fn);
  filllive(fn);
  fillloop(fn);
  fillcost(fn);
  spill(fn);
  rega(fn);
  fillcfg(fn);
  simpljmp(fn);
  fillcfg(fn);
  assert(fn->rpo[0] == fn->start);
  for (n = 0;; n++)
    if (n == fn->nblk - 1) {
      fn->rpo[n]->link = 0;
      break;
    } else
      fn->rpo[n]->link = fn->rpo[n+1];
  T.emitfn(fn, qbe_outf);
  fprintf(qbe_outf, "/* end function %s */\n\n", fn->name);
  freeall();
}

static void qbe_dbgfile(char *fn) {
  emitdbgfile(fn, qbe_outf);
}

/*
 * js_os_qbe(ctx, self, argc, argv)
 *
 * Takes a single string argument (QBE IR text).
 * Returns the compiled assembly as a string.
 */
JSValue js_os_qbe(JSContext *js, JSValue self, int argc, JSValue *argv) {
  if (argc < 1)
    return JS_ThrowTypeError(js, "os.qbe requires an IR string argument");

  const char *ir = JS_ToCString(js, argv[0]);
  if (!ir)
    return JS_EXCEPTION;

  size_t ir_len = strlen(ir);

  /* Select target for host platform */
#if defined(__APPLE__) && defined(__aarch64__)
  T = T_arm64_apple;
#elif defined(__APPLE__) && defined(__x86_64__)
  T = T_amd64_apple;
#elif defined(_WIN32) && defined(__x86_64__)
  T = T_amd64_win;
#elif defined(__x86_64__)
  T = T_amd64_sysv;
#elif defined(__aarch64__)
  T = T_arm64;
#elif defined(__riscv) && __riscv_xlen == 64
  T = T_rv64;
#else
  T = Deftgt;
#endif

  memset(debug, 0, sizeof(debug));

  /* Open IR string as input FILE */
  FILE *inf = fmemopen((void *)ir, ir_len, "r");
  if (!inf) {
    JS_FreeCString(js, ir);
    return JS_ThrowInternalError(js, "os.qbe: fmemopen failed");
  }

  /* Open output memory stream */
  char *out_buf = NULL;
  size_t out_len = 0;
  qbe_outf = open_memstream(&out_buf, &out_len);
  if (!qbe_outf) {
    fclose(inf);
    JS_FreeCString(js, ir);
    return JS_ThrowInternalError(js, "os.qbe: open_memstream failed");
  }

  /* Run the QBE pipeline */
  parse(inf, "<ir>", qbe_dbgfile, qbe_data, qbe_func);
  fclose(inf);

  /* Finalize (emit assembler directives) */
  T.emitfin(qbe_outf);
  fflush(qbe_outf);
  fclose(qbe_outf);
  qbe_outf = NULL;

  JS_FreeCString(js, ir);

  /* Return assembly text */
  JSValue result = JS_NewStringLen(js, out_buf, out_len);
  free(out_buf);
  return result;
}
