/*
 * KateRTX · SPV loader/runner · ONE LULZ
 * Loads & runs SPV plugs (JS catalog + compute .comp) · launches engine ELF
 * X11 console · no Konsole · no Python · linear BGS→BGF · no duo
 *
 *   gcc -O2 -o bin/krtx-spv src/krtx_spv.c -lX11
 *   ./bin/krtx-spv          # the one
 *   commands: help list in <id> run engine pack law one quit
 */
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <ctype.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#define MAX_LINES 56
#define MAX_COLS 100
#define MAX_PLUGS 128
#define LINE_H 15
#define PAD 8

typedef struct {
  char id[64];
  char kind[8]; /* js | comp */
  char path[512];
} Plug;

static char lines[MAX_LINES][MAX_COLS];
static int nlines;
static char input[MAX_COLS];
static int inlen;
static char root_path[512];
static char spvs_js[640];
static char spvs_comp[640];
static char engine_bin[640];
static char out_dir[640];
static Plug plugs[MAX_PLUGS];
static int nplugs;
static int sel; /* selection index */

static void push(const char *s) {
  if (nlines >= MAX_LINES) {
    memmove(lines[0], lines[1], (size_t)(MAX_LINES - 1) * MAX_COLS);
    nlines = MAX_LINES - 1;
  }
  snprintf(lines[nlines], MAX_COLS, "%s", s ? s : "");
  nlines++;
}

static int ends_with(const char *s, const char *suf) {
  size_t a = strlen(s), b = strlen(suf);
  return a >= b && strcmp(s + a - b, suf) == 0;
}

static void scan_plugs(void) {
  nplugs = 0;
  DIR *d = opendir(spvs_js);
  if (d) {
    struct dirent *e;
    while ((e = readdir(d)) != NULL && nplugs < MAX_PLUGS) {
      if (!ends_with(e->d_name, ".js")) continue;
      Plug *p = &plugs[nplugs];
      memset(p, 0, sizeof *p);
      snprintf(p->id, sizeof p->id, "%s", e->d_name);
      /* strip .js */
      size_t L = strlen(p->id);
      if (L > 3) p->id[L - 3] = 0;
      snprintf(p->kind, sizeof p->kind, "js");
      snprintf(p->path, sizeof p->path, "%s/%s", spvs_js, e->d_name);
      nplugs++;
    }
    closedir(d);
  }
  d = opendir(spvs_comp);
  if (d) {
    struct dirent *e;
    while ((e = readdir(d)) != NULL && nplugs < MAX_PLUGS) {
      if (!ends_with(e->d_name, ".comp")) continue;
      Plug *p = &plugs[nplugs];
      memset(p, 0, sizeof *p);
      snprintf(p->id, sizeof p->id, "%s", e->d_name);
      size_t L = strlen(p->id);
      if (L > 5) p->id[L - 5] = 0;
      snprintf(p->kind, sizeof p->kind, "comp");
      snprintf(p->path, sizeof p->path, "%s/%s", spvs_comp, e->d_name);
      nplugs++;
    }
    closedir(d);
  }
}

static Plug *find_plug(const char *id) {
  for (int i = 0; i < nplugs; i++)
    if (!strcmp(plugs[i].id, id)) return &plugs[i];
  return NULL;
}

static char *read_file(const char *path, size_t *out_len) {
  FILE *f = fopen(path, "rb");
  if (!f) return NULL;
  fseek(f, 0, SEEK_END);
  long n = ftell(f);
  fseek(f, 0, SEEK_SET);
  if (n < 0 || n > 8 * 1024 * 1024) {
    fclose(f);
    return NULL;
  }
  char *b = (char *)malloc((size_t)n + 1);
  if (!b) {
    fclose(f);
    return NULL;
  }
  if (fread(b, 1, (size_t)n, f) != (size_t)n) {
    free(b);
    fclose(f);
    return NULL;
  }
  b[n] = 0;
  fclose(f);
  if (out_len) *out_len = (size_t)n;
  return b;
}

static void launch_engine(void) {
  if (access(engine_bin, X_OK) != 0) {
    push("err: krtx-engine missing · ./build.sh");
    return;
  }
  pid_t p = fork();
  if (p == 0) {
    char root[640];
    snprintf(root, sizeof root, "%s/engine", root_path);
    char *argv[] = {engine_bin, "--root", root, "--spvs", spvs_comp, NULL};
    execv(engine_bin, argv);
    _exit(127);
  }
  if (p > 0)
    push("SPV runner · engine ELF · compute plugs hotswap 1-9 [ ]");
  else
    push("err: fork engine");
}

/* write self-contained HTML runner for one JS plug · SPV loader in-browser free */
static int write_js_runner(const Plug *pl, char *out_path, size_t out_sz) {
  char js_path[640], dom_path[640];
  snprintf(js_path, sizeof js_path, "%s/%s.js", spvs_js, pl->id);
  snprintf(dom_path, sizeof dom_path, "%s/%s.dom.html", spvs_js, pl->id);
  char *js = read_file(js_path, NULL);
  if (!js) {
    push("err: cannot read plug js");
    return -1;
  }
  char *dom = read_file(dom_path, NULL);
  if (!dom) dom = strdup("<div data-plug='root'></div>");
  mkdir(out_dir, 0755);
  snprintf(out_path, out_sz, "%s/run_%s.html", out_dir, pl->id);
  FILE *o = fopen(out_path, "w");
  if (!o) {
    free(js);
    free(dom);
    push("err: write runner");
    return -1;
  }
  fprintf(o,
          "<!DOCTYPE html><html><head><meta charset=utf-8>"
          "<title>KateRTX SPV · %s</title>"
          "<style>"
          "body{margin:0;background:#0a0c0b;color:#e8e4df;font:14px system-ui,sans-serif}"
          "#bar{padding:10px 14px;background:#121614;border-bottom:1px solid #1e2822;"
          "font:12px ui-monospace,monospace;color:#05f2a0}"
          "#stage{padding:12px;min-height:70vh}"
          ".spv-hud{font:12px ui-monospace,monospace;color:#05f2a0;margin-top:8px}"
          "</style></head><body>"
          "<div id=bar>KateRTX SPV loader · plug=<b>%s</b> · rides BGF · free never folds · one lulz</div>"
          "<div id=stage>%s</div>"
          "<script>\n%s\n</script>"
          "<script>\n"
          "(function(){\n"
          "  var NAME='%s.js';\n"
          "  var R=window.BGRTX_SPV_PLUGS||{};\n"
          "  var P=R[NAME];\n"
          "  var root=document.getElementById('stage');\n"
          "  if(!P||!P.mount){root.innerHTML+='<pre>no mount for '+NAME+'</pre>';return;}\n"
          "  window.__KRTX_UNMOUNT=P.mount(root,{onSample:function(s){\n"
          "    var b=document.getElementById('bar');\n"
          "    if(b&&s) b.textContent='SPV '+NAME+' field='+s.field+' free='+s.free+' f='+s.frame+' · never fold';\n"
          "  }});\n"
          "})();\n"
          "</script></body></html>\n",
          pl->id, pl->id, dom, js, pl->id);
  fclose(o);
  free(js);
  free(dom);
  return 0;
}

static void open_html(const char *path) {
  /* prefer project-local viewers if any; else waterfox/xdg — still SPV pack is owned */
  const char *try_list[] = {
      "waterfox", "firefox", "chromium", "google-chrome", "xdg-open", NULL};
  for (int i = 0; try_list[i]; i++) {
    if (!strcmp(try_list[i], "xdg-open") || !access("/usr/local/bin/waterfox", X_OK) ||
        !access("/usr/bin/waterfox", X_OK) || system("command -v waterfox >/dev/null 2>&1") == 0 ||
        system("command -v firefox >/dev/null 2>&1") == 0 ||
        system("command -v chromium >/dev/null 2>&1") == 0) {
      /* use execlp chain */
    }
  }
  pid_t p = fork();
  if (p == 0) {
    /* try waterfox then xdg-open */
    execlp("waterfox", "waterfox", path, (char *)NULL);
    execlp("firefox", "firefox", path, (char *)NULL);
    execlp("xdg-open", "xdg-open", path, (char *)NULL);
    _exit(127);
  }
  if (p > 0) {
    char b[MAX_COLS];
    snprintf(b, sizeof b, "SPV runner → %s", path);
    push(b);
  } else
    push("err: open runner");
}

static void run_plug(const char *id) {
  Plug *pl = find_plug(id);
  if (!pl) {
    char b[MAX_COLS];
    snprintf(b, sizeof b, "no plug: %s · list", id);
    push(b);
    return;
  }
  if (!strcmp(pl->kind, "comp")) {
    push("compute SPV · launching engine loader (hotswap folder)");
    launch_engine();
    return;
  }
  char path[640];
  if (write_js_runner(pl, path, sizeof path) == 0) {
    char b[MAX_COLS];
    snprintf(b, sizeof b, "loaded SPV js · %s", pl->id);
    push(b);
    open_html(path);
  }
}

static void list_plugs(void) {
  char b[MAX_COLS];
  snprintf(b, sizeof b, "SPV loader · %d plugs · js+comp", nplugs);
  push(b);
  for (int i = 0; i < nplugs && i < 40; i++) {
    snprintf(b, sizeof b, "%s%2d  %-8s %s", (i == sel ? ">" : " "), i, plugs[i].kind, plugs[i].id);
    push(b);
  }
  if (nplugs > 40) push("  …");
  push("in <id> | run <id> | one · engine · law");
}

static void one_lulz(void) {
  push("=== ONE LULZ · SPV loader runner ===");
  push("engine compute + sample JS grin");
  launch_engine();
  Plug *g = find_plug("grin");
  if (!g) g = find_plug("kate");
  if (!g && nplugs) g = &plugs[0];
  if (g && !strcmp(g->kind, "js")) {
    char path[640];
    if (write_js_runner(g, path, sizeof path) == 0) open_html(path);
  }
  push("done · linear BGS→BGF · SPV rides · GRIN free=1");
}

static void help(void) {
  push("KateRTX SPV · loader/runner · ONE LULZ · linear");
  push("  help           this");
  push("  list           all SPV js + compute plugs");
  push("  in <id>        load+run plug (js→html runner · comp→engine)");
  push("  run <id>       same as in");
  push("  engine         krtx-engine ELF fullscreen");
  push("  one | lulz     engine + grin (or first) · the one");
  push("  law            BGS→BGF→SDF pins · free ride");
  push("  rescan         rescan spvs/");
  push("  status         paths");
  push("  clear | quit");
}

static void status(void) {
  char b[MAX_COLS];
  snprintf(b, sizeof b, "ROOT %s", root_path);
  push(b);
  snprintf(b, sizeof b, "JS   %s", spvs_js);
  push(b);
  snprintf(b, sizeof b, "COMP %s", spvs_comp);
  push(b);
  snprintf(b, sizeof b, "ENG  %s %s", engine_bin,
           access(engine_bin, X_OK) == 0 ? "ELF-ok" : "MISSING");
  push(b);
  snprintf(b, sizeof b, "plugs=%d sel=%d", nplugs, sel);
  push(b);
}

static void handle(const char *line) {
  char cmd[64] = {0};
  char arg[64] = {0};
  const char *p = line;
  int i = 0;
  while (*p && isspace((unsigned char)*p)) p++;
  while (*p && !isspace((unsigned char)*p) && i < 63) cmd[i++] = (char)tolower((unsigned char)*p++);
  cmd[i] = 0;
  while (*p && isspace((unsigned char)*p)) p++;
  i = 0;
  while (*p && i < 63) arg[i++] = *p++;
  arg[i] = 0;
  /* trim arg */
  while (i > 0 && isspace((unsigned char)arg[i - 1])) arg[--i] = 0;

  if (!cmd[0]) return;
  if (!strcmp(cmd, "help") || !strcmp(cmd, "?")) help();
  else if (!strcmp(cmd, "list") || !strcmp(cmd, "ls")) list_plugs();
  else if (!strcmp(cmd, "in") || !strcmp(cmd, "run") || !strcmp(cmd, "load")) {
    if (!arg[0]) push("usage: in <plug-id>");
    else run_plug(arg);
  } else if (!strcmp(cmd, "engine") || !strcmp(cmd, "rtx") || !strcmp(cmd, "gfx"))
    launch_engine();
  else if (!strcmp(cmd, "one") || !strcmp(cmd, "lulz") || !strcmp(cmd, "go"))
    one_lulz();
  else if (!strcmp(cmd, "law")) {
    push("SPV loader · LINEAR line · pins · not only-1-engine");
    push("BGS → BGF(+EZZIE PHI THERMO) → SDF · SPV ride");
    push("  pin BGS  field_one v|1");
    push("  pin BGF  (a-b)|1 measure");
    push("  atoms   EZZIE (a^b)|1 · PHI (d*s)|1 · THERMO band|1");
    push("  pin SDF  shell place store");
    push("  pin SPV  free|1 rides BGF never fold");
    push("atoms still linear · ride BGF · ESSIE != EZZIE");
    push("free=1 TRUTH pin · know_her 7|1 · not ground");
    push("TRUTH local · no curl");
  } else if (!strcmp(cmd, "rescan")) {
    scan_plugs();
    char b[MAX_COLS];
    snprintf(b, sizeof b, "rescanned · %d plugs", nplugs);
    push(b);
  } else if (!strcmp(cmd, "status") || !strcmp(cmd, "st"))
    status();
  else if (!strcmp(cmd, "clear") || !strcmp(cmd, "cls"))
    nlines = 0;
  else if (!strcmp(cmd, "quit") || !strcmp(cmd, "exit") || !strcmp(cmd, "q"))
    exit(0);
  else {
    /* bare id → try run */
    if (find_plug(cmd)) run_plug(cmd);
    else {
      char b[MAX_COLS];
      snprintf(b, sizeof b, "unknown: %s · help", cmd);
      push(b);
    }
  }
}

static void draw(Display *dpy, Window win, GC gc, int W, int H) {
  XSetForeground(dpy, gc, 0x0a0c0b);
  XFillRectangle(dpy, win, gc, 0, 0, (unsigned)W, (unsigned)H);
  /* header */
  XSetForeground(dpy, gc, 0x121614);
  XFillRectangle(dpy, win, gc, 0, 0, (unsigned)W, 22);
  XSetForeground(dpy, gc, 0xe8c547);
  const char *hdr = "KateRTX SPV · linear BGS→BGF · one lulz · no duo";
  XDrawString(dpy, win, gc, PAD, 15, hdr, (int)strlen(hdr));

  XSetForeground(dpy, gc, 0x05f2a0);
  int y = 22 + PAD + 12;
  for (int i = 0; i < nlines; i++) {
    XDrawString(dpy, win, gc, PAD, y, lines[i], (int)strlen(lines[i]));
    y += LINE_H;
    if (y > H - 36) break;
  }
  XSetForeground(dpy, gc, 0xe8c547);
  char prompt[MAX_COLS + 8];
  snprintf(prompt, sizeof prompt, "spv> %s", input);
  XDrawString(dpy, win, gc, PAD, H - PAD - 4, prompt, (int)strlen(prompt));
}

static void resolve_root(void) {
  const char *r = getenv("KATERTX_ROOT");
  if (!r || !r[0]) {
    char self[512];
    ssize_t n = readlink("/proc/self/exe", self, sizeof self - 1);
    if (n > 0) {
      self[n] = 0;
      char *slash = strrchr(self, '/');
      if (slash) {
        *slash = 0;
        slash = strrchr(self, '/');
        if (slash) {
          *slash = 0;
          r = self;
        }
      }
    }
  }
  if (!r || !r[0]) r = ".";
  snprintf(root_path, sizeof root_path, "%s", r);
  snprintf(spvs_js, sizeof spvs_js, "%s/spvs", root_path);
  snprintf(spvs_comp, sizeof spvs_comp, "%s/engine/spvs", root_path);
  snprintf(engine_bin, sizeof engine_bin, "%s/bin/krtx-engine", root_path);
  snprintf(out_dir, sizeof out_dir, "%s/out", root_path);
}

int main(int argc, char **argv) {
  resolve_root();
  scan_plugs();

  /* CLI one-shot: krtx-spv one | krtx-spv in grin | krtx-spv list */
  if (argc >= 2) {
    if (!strcmp(argv[1], "one") || !strcmp(argv[1], "lulz")) {
      one_lulz();
      /* keep process if GUI needed — for one-shot engine fork is enough */
      if (argc >= 2 && (!strcmp(argv[1], "one") || !strcmp(argv[1], "lulz"))) {
        /* still open GUI for feedback unless --headless */
        int headless = 0;
        for (int i = 2; i < argc; i++)
          if (!strcmp(argv[i], "--headless")) headless = 1;
        if (headless) return 0;
      }
    } else if (!strcmp(argv[1], "list")) {
      for (int i = 0; i < nplugs; i++)
        printf("%-5s  %-16s  %s\n", plugs[i].kind, plugs[i].id, plugs[i].path);
      return 0;
    } else if ((!strcmp(argv[1], "in") || !strcmp(argv[1], "run")) && argc >= 3) {
      run_plug(argv[2]);
      /* fall through to GUI so user sees result, or exit after short */
    } else if (!strcmp(argv[1], "engine")) {
      launch_engine();
      return 0;
    }
  }

  Display *dpy = XOpenDisplay(NULL);
  if (!dpy) {
    fprintf(stderr, "krtx-spv: no DISPLAY · try: krtx-spv list | engine | in <id>\n");
    if (argc >= 2) return 0;
    return 1;
  }
  int scr = DefaultScreen(dpy);
  int W = 960, H = 600;
  Window win = XCreateSimpleWindow(dpy, RootWindow(dpy, scr), 60, 40, (unsigned)W, (unsigned)H, 1,
                                   WhitePixel(dpy, scr), BlackPixel(dpy, scr));
  XStoreName(dpy, win, "KateRTX SPV · loader/runner · one lulz");
  XSelectInput(dpy, win, ExposureMask | KeyPressMask | StructureNotifyMask);
  GC gc = XCreateGC(dpy, win, 0, NULL);
  XFontStruct *font = XLoadQueryFont(dpy, "fixed");
  if (font) XSetFont(dpy, gc, font->fid);
  Atom wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
  XSetWMProtocols(dpy, win, &wm_delete, 1);
  XMapWindow(dpy, win);

  push("SPV loader ready · one lulz");
  char b[MAX_COLS];
  snprintf(b, sizeof b, "%d plugs scanned · type help | list | one", nplugs);
  push(b);
  if (argc >= 3 && (!strcmp(argv[1], "in") || !strcmp(argv[1], "run"))) {
    /* already ran */
  } else if (argc >= 2 && (!strcmp(argv[1], "one") || !strcmp(argv[1], "lulz"))) {
    /* already */
  }

  int running = 1;
  while (running) {
    XEvent ev;
    XNextEvent(dpy, &ev);
    if (ev.type == Expose)
      draw(dpy, win, gc, W, H);
    else if (ev.type == ConfigureNotify) {
      W = ev.xconfigure.width;
      H = ev.xconfigure.height;
    } else if (ev.type == ClientMessage) {
      if ((Atom)ev.xclient.data.l[0] == wm_delete) running = 0;
    } else if (ev.type == KeyPress) {
      KeySym ks;
      char kb[8];
      int n = XLookupString(&ev.xkey, kb, sizeof kb, &ks, NULL);
      if (ks == XK_Return || ks == XK_KP_Enter) {
        char line[MAX_COLS];
        snprintf(line, sizeof line, "spv> %s", input);
        push(line);
        handle(input);
        inlen = 0;
        input[0] = 0;
        draw(dpy, win, gc, W, H);
      } else if (ks == XK_BackSpace) {
        if (inlen > 0) {
          input[--inlen] = 0;
          draw(dpy, win, gc, W, H);
        }
      } else if (ks == XK_Escape)
        running = 0;
      else if (n == 1 && kb[0] >= 32 && kb[0] < 127 && inlen < MAX_COLS - 1) {
        input[inlen++] = kb[0];
        input[inlen] = 0;
        draw(dpy, win, gc, W, H);
      }
    }
  }
  if (font) XFreeFont(dpy, font);
  XFreeGC(dpy, gc);
  XDestroyWindow(dpy, win);
  XCloseDisplay(dpy);
  return 0;
}
