// ============================================================================
// face_alpine_dialect.c — Alpine Linux as a FACE/PROJECTION of the substrate.
//
// NOT A CHAINLOAD, NOT A HANDOFF. This dialect does not boot a kernel and jump
// to it (that would surrender the substrate — the Router64 `alpine_handoff`
// far-return is exactly the thing we do NOT do here). Linux is a PROJECTION:
// the substrate stays the host, and a real Linux userland runs as a guest the
// operator OBSERVES, precisely as the c64 dialect wraps the real fake6502 +
// real KERNAL ROMs. The guest runs itself; we translate its console-out.
//
// THE GUEST IS conscious's real lattice-derived Alpine, unchanged:
//   conscious-main/lattice_init.sh    — "This script IS the OS boot sequence",
//                                        generated by the Slot4096 resonance
//                                        engine; every OS parameter (hostname,
//                                        packages, tz, uid, sysctls...) is a
//                                        pure function of the phi-lattice slots.
//   conscious-main/lattice_entropy.bin — the 4096 slot values as entropy.
// We do NOT re-derive or re-emit these — they are the guest, the way KERNAL.ROM
// is the c64 guest. The face loads them into a rootfs and runs them.
//
// CLOSEST TO METAL: the guest is projected with chroot(2) — the raw Linux
// namespace primitive Docker itself sits on. No daemon, no image layers, no
// runtime shim between the substrate and the guest; runs anywhere a kernel and
// a busybox exist, including bare metal. (conscious's native environment uses
// `docker run alpine sh /lattice/init.sh`; chroot is that same recipe with the
// daemon peeled off — one `sh /lattice/init.sh` on a real rootfs.)
//
// THE FOUR TRANSLATED VECTORS (the guest's own hardware contract):
//   console-out : the guest process's controlling-tty output (a pty master we
//                 hold). We OBSERVE it into the surface — read-only, we never
//                 author the guest's screen.
//   input       : bytes written to the pty master land in the guest's stdin,
//                 the authentic path; its shell echoes them itself.
//   clock/coord : the guest is a real process; coord = its pid, and step()
//                 lets it run (we never inject its work).
//
// The substrate couples in through the operator's live lattice[]: face_settle
// ticks phi_lattice.asm every frame, and this face reflects the current slot
// state into the guest's runtime dir (/lattice/slots), so the projection is of
// THIS run's substrate — the same binding conscious makes when it derives the
// OS from the lattice. The auditable seed in the MOTD is that fingerprint.
// ============================================================================
#define _DEFAULT_SOURCE   /* for chroot(2), forkpty(3) prototypes under -std=c99 */
#include "../../core/face_node.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>

// pty master/slave allocation (glibc). freestanding builds that lack these can
// define ALPINE_NO_PTY to fall back to a pipe (loses tty echo but still real).
#include <pty.h>

#define ALPINE_COLS 80
#define ALPINE_ROWS 25
#define ALPINE_CELLS (ALPINE_COLS*ALPINE_ROWS)

// Where the guest's real artifacts live, and where we stage the rootfs. These
// default to the repo layout; override with env for other locations.
#ifndef ALPINE_CONSCIOUS_DIR
#define ALPINE_CONSCIOUS_DIR "guest"
#endif
#ifndef ALPINE_ROOTFS_DIR
#define ALPINE_ROOTFS_DIR "/tmp/phi4096_rootfs"
#endif
#ifndef ALPINE_BUSYBOX
#define ALPINE_BUSYBOX "/usr/bin/busybox"
#endif

// The face's private machine: the running guest (real Linux process) + its
// console pty + a VT100-ish text grid we render its output into. Plus the live
// substrate lattice pointer, so the projection reflects THIS run's substrate.
struct AlpineSelf {
  int             ptym;        // pty master fd — the guest's console-out vector
  pid_t           pid;         // the guest process (its identity coordinate)
  unsigned char   grid[ALPINE_CELLS];
  int             cx, cy;      // cursor in the grid
  unsigned long   tick;
  const uint32_t* lattice;     // operator's live phi_lattice.asm slots (128)
  int             booted;
  int             in_esc;      // escape-parse state, persists across drains
  char            escbuf[16];
  int             escn;
};
static struct AlpineSelf g_alpine_self;

// ── rootfs staging: copy busybox + the guest's REAL artifacts in ────────────
// This is the analogue of the c64 dialect binding real KERNAL/BASIC ROMs: we
// place the real lattice_init.sh + entropy the resonance engine produced, not
// a synthesized console. Idempotent; safe to call once per realize.
static void alpine_stage_rootfs(struct AlpineSelf* s)
{
  (void)s;
  // Build the rootfs tree and busybox applet links, then drop in the guest's
  // real boot script + entropy. We shell out to keep this dialect small and to
  // use the system's own tools (the guest is defined by conscious's files, not
  // by C we write here). Failure is non-fatal: init() reports via the surface.
  const char* mk =
    "set -e; R=" ALPINE_ROOTFS_DIR "; C=" ALPINE_CONSCIOUS_DIR ";\n"
    "rm -rf \"$R\"; mkdir -p \"$R/bin\" \"$R/lattice\" \"$R/etc/apk\" "
      "\"$R/etc/sysctl.d\" \"$R/etc/profile.d\" \"$R/proc\" \"$R/dev\" "
      "\"$R/home\" \"$R/run\" \"$R/sbin\" \"$R/usr/share/zoneinfo/Etc\";\n"
    "cp " ALPINE_BUSYBOX " \"$R/bin/\" 2>/dev/null || cp \"$(command -v busybox)\" \"$R/bin/\";\n"
    "( cd \"$R/bin\"; for c in sh cat echo hostname mount mkdir chmod chown ln "
      "addgroup adduser renice wc dd printf id uname sysctl su ulimit head tail "
      "grep sleep date; do ln -sf busybox $c; done );\n"
    "cp \"$C/lattice_init.sh\"    \"$R/lattice/init.sh\";\n"
    "cp \"$C/lattice_entropy.bin\" \"$R/lattice/entropy.bin\" 2>/dev/null || true;\n"
    "chmod +x \"$R/lattice/init.sh\";\n";
  int rc = system(mk);
  (void)rc;
}

// ── reflect the live substrate lattice into the guest runtime dir ───────────
// The operator's phi_lattice.asm slots are the substrate. Expose them to the
// guest as /lattice/slots/* (the same path conscious's engine writes), so the
// projection is of the current substrate state, not a frozen snapshot.
static void alpine_reflect_lattice(struct AlpineSelf* s)
{
  if(!s->lattice) return;
  char dir[256];
  snprintf(dir, sizeof(dir), "%s/lattice/slots", ALPINE_ROOTFS_DIR);
  // best-effort mkdir via system (kept tiny; guest defines semantics)
  char cmd[320]; snprintf(cmd, sizeof(cmd), "mkdir -p '%s' 2>/dev/null", dir);
  int rc = system(cmd); (void)rc;
  // write the 128 live slots as readable files (normalized to [0,1) like the
  // engine's doubles); the guest/init already knows this contract.
  for(int i=0;i<PHI_LATTICE_SLOTS;i++){
    char p[300]; snprintf(p, sizeof(p), "%s/%d", dir, i+1);
    FILE* f = fopen(p, "w");
    if(f){ fprintf(f, "%.8f\n", (double)s->lattice[i] / 4294967296.0); fclose(f); }
  }
}

// ── init: bind the guest and run it (reset it for real) ─────────────────────
static void alpine_init(void* self)
{
  struct AlpineSelf* s = (struct AlpineSelf*)self;
  for(int i=0;i<ALPINE_CELLS;i++) s->grid[i] = ' ';
  s->cx = 0; s->cy = 0; s->tick = 0; s->booted = 0; s->pid = -1; s->ptym = -1;

  alpine_stage_rootfs(s);
  alpine_reflect_lattice(s);

  int master;
  pid_t pid = forkpty(&master, NULL, NULL, NULL);
  if(pid < 0){
    const char* msg = "[alpine] forkpty failed — cannot project guest";
    for(int i=0;msg[i] && i<ALPINE_COLS;i++) s->grid[i]=(unsigned char)msg[i];
    return;
  }
  if(pid == 0){
    // CHILD: become the guest. chroot into the staged rootfs and run the REAL
    // lattice_init.sh — the guest booting itself. This is the projection: the
    // substrate (parent) stays host; the child IS Linux, running its own boot.
    if(chroot(ALPINE_ROOTFS_DIR) != 0) _exit(127);
    if(chdir("/") != 0) _exit(127);
    // exec the guest's own boot sequence, then drop to an interactive shell so
    // the operator's face_key input reaches a live prompt (observed, echoed).
    execl("/bin/sh", "sh", "-c",
          "/bin/sh /lattice/init.sh; exec /bin/sh -i", (char*)NULL);
    _exit(127);
  }
  // PARENT (substrate side): hold the guest's console pty; observe, don't drive.
  s->pid  = pid;
  s->ptym = master;
  int fl = fcntl(master, F_GETFL, 0);
  if(fl != -1) fcntl(master, F_SETFL, fl | O_NONBLOCK);  // non-blocking observe
  s->booted = 1;
}

// ── VT: minimal terminal projection of the guest's console-out byte stream ──
// We render the guest's real output into the 80x25 grid. Handles LF, CR, BS,
// tab, and scroll — enough to project a boot log + shell. NOT a full VT100; we
// observe printable text and basic motion, and drop escape sequences we don't
// model (the point is faithful projection of the guest's console, not emulation
// of the guest — the guest is real, its bytes are real).
static void alpine_scroll(struct AlpineSelf* s)
{
  memmove(s->grid, s->grid+ALPINE_COLS, ALPINE_CELLS-ALPINE_COLS);
  memset(s->grid+ALPINE_CELLS-ALPINE_COLS, ' ', ALPINE_COLS);
  s->cy = ALPINE_ROWS-1;
}
static void alpine_vt_putc(struct AlpineSelf* s, unsigned char c)
{
  // ESC sequences are consumed by the drain loop; putc only sees text + basic
  // C0 control (LF/CR/BS/TAB). Keep the cursor in-bounds and scroll on overflow
  // so the surface always shows the guest console's most recent 25 lines.
  if(c == '\n'){ s->cx = 0; if(++s->cy >= ALPINE_ROWS) alpine_scroll(s); return; }
  if(c == '\r'){ s->cx = 0; return; }
  if(c == '\b'){ if(s->cx>0) s->cx--; return; }
  if(c == 0x07){ return; }                       // BEL — ignore
  if(c == '\t'){ s->cx = (s->cx + 8) & ~7; if(s->cx >= ALPINE_COLS){ s->cx = 0; if(++s->cy >= ALPINE_ROWS) alpine_scroll(s);} return; }
  if(c < 0x20 || c >= 0x7f) return;              // other control/non-print
  if(s->cx >= ALPINE_COLS){ s->cx = 0; if(++s->cy >= ALPINE_ROWS) alpine_scroll(s); }
  s->grid[s->cy*ALPINE_COLS + s->cx] = c;
  s->cx++;
}

// naive escape-sequence skipper state kept simple: consume ESC [ ... final
static int alpine_drain_console(struct AlpineSelf* s, int budget)
{
  if(s->ptym < 0) return 0;
  unsigned char buf[512];
  int total = 0;
  while(budget-- > 0){
    ssize_t n = read(s->ptym, buf, sizeof(buf));
    if(n <= 0) break;
    for(ssize_t i=0;i<n;i++){
      unsigned char c = buf[i];
      if(s->in_esc){
        if(s->escn < (int)sizeof(s->escbuf)-1) s->escbuf[s->escn++] = (char)c;
        // A CSI sequence ends on a final byte in '@'..'~'. The very first byte
        // after ESC may be '[' (CSI) or another intermediate; keep consuming
        // until a final byte so nothing leaks into the grid.
        if(s->escn > 1 && (c >= '@' && c <= '~')){
          s->escbuf[s->escn] = 0;
          // Answer Device Status Report cursor-position query (ESC[6n): the
          // guest's line editor asks where the cursor is and repeats until told.
          if(c == 'n' && s->escn >= 2 && s->escbuf[0]=='[' && s->escbuf[1]=='6'){
            char rep[24];
            int rlen = snprintf(rep,sizeof(rep),"\x1b[%d;%dR", s->cy+1, s->cx+1);
            ssize_t w = write(s->ptym, rep, (size_t)rlen); (void)w;
          }
          s->in_esc = 0; s->escn = 0;
        }
        continue;
      }
      if(c == 0x1b){ s->in_esc = 1; s->escn = 0; continue; }
      alpine_vt_putc(s, c);
    }
    total += (int)n;
  }
  return total;
}

// ── step: let the guest run itself; pump its console into the grid ──────────
static int alpine_step(void* self, unsigned int quanta)
{
  struct AlpineSelf* s = (struct AlpineSelf*)self;
  s->tick += quanta;
  if(!s->booted) return 0;
  // reap if the guest exited
  int status;
  pid_t r = waitpid(s->pid, &status, WNOHANG);
  if(r == s->pid){ s->booted = 0; }
  // observe available console-out (endogenous: the guest produced it, not us)
  alpine_drain_console(s, 8);
  return 0;
}

// ── observe: read-only copy of the guest's console into the surface ─────────
static void alpine_observe(void* self, unsigned char* surface, int cols, int rows)
{
  struct AlpineSelf* s = (struct AlpineSelf*)self;
  // pull any last bytes so the surface is current
  alpine_drain_console(s, 4);
  int n = cols*rows; if(n > FACE_SURFACE_SIZE) n = FACE_SURFACE_SIZE;
  for(int i=0;i<n;i++){
    unsigned char ch = (i < ALPINE_CELLS) ? s->grid[i] : ' ';
    surface[i] = (ch >= 0x20 && ch < 0x7f) ? ch : ' ';
  }
}

// ── input: deliver bytes into the guest's own stdin (authentic path) ────────
// The guest's shell reads and echoes — we never write the surface for input.
static void alpine_input(void* self, uint8_t key)
{
  struct AlpineSelf* s = (struct AlpineSelf*)self;
  if(!s->booted || s->ptym < 0) return;
  unsigned char b = key;
  if(b == '\r') b = '\n';               // Enter → newline into the tty
  ssize_t w = write(s->ptym, &b, 1);
  (void)w;
}

// ── coord: the guest's identity coordinate = its pid ────────────────────────
static uint32_t alpine_coord(void* self)
{
  struct AlpineSelf* s = (struct AlpineSelf*)self;
  return (uint32_t)(s->pid > 0 ? s->pid : 0);
}

// ── bind: one FaceBasis, wired to the REAL guest. Same shape as every dialect;
//    the operator (face_realize/tick/key/mouse) drives it UNCHANGED. mouse is
//    null: a console tty has no native pointer contract. The lattice pointer is
//    the substrate coupling — the projection reflects THIS run's phi-lattice.
void face_bind_alpine(struct FaceNode* f)
{
  g_alpine_self.tick = 0;
  g_alpine_self.lattice = f->lattice;   // live substrate slots (phi_lattice.asm)
  f->name = "alpine";
  f->self = &g_alpine_self;
  f->cols = ALPINE_COLS; f->rows = ALPINE_ROWS;
  f->basis.init    = alpine_init;
  f->basis.step    = alpine_step;
  f->basis.observe = alpine_observe;
  f->basis.input   = alpine_input;
  f->basis.mouse   = 0;
  f->basis.coord   = alpine_coord;
}
