// JS-target atomic-write primitive. Opens a .mem_*.tmp next to the
// target, writes, fsyncs, closes, and renames onto the target. On any
// failure the tmp is unlinked before rethrowing so memories/ doesn't
// accumulate `.mem_*.tmp` droppings.

///|
extern "js" fn ffi_atomic_write_impl(path : String, content : String) -> Unit =
  #| (path, content) => {
  #|   const fs = require("node:fs");
  #|   const crypto = require("node:crypto");
  #|   const nodePath = require("node:path");
  #|   const dir = nodePath.dirname(path);
  #|   const tmp = nodePath.join(dir, ".mem_" + crypto.randomBytes(8).toString("hex") + ".tmp");
  #|   const cleanup = () => { try { fs.unlinkSync(tmp); } catch {} };
  #|   const fd = fs.openSync(tmp, "w");
  #|   try {
  #|     try {
  #|       fs.writeSync(fd, content);
  #|       fs.fsyncSync(fd);
  #|     } finally { fs.closeSync(fd); }
  #|   } catch (e) { cleanup(); throw e; }
  #|   try { fs.renameSync(tmp, path); }
  #|   catch (e) { cleanup(); throw e; }
  #| }

///| Test-only helper: mirror of ffi_atomic_write_impl that returns Bool
///| instead of throwing, so the inline failure-path test can assert
///| without propagating a panic through the async test driver.
extern "js" fn ffi_ms_try_write(path : String, content : String) -> Bool =
  #| (path, content) => {
  #|   try {
  #|     const fs = require("node:fs");
  #|     const crypto = require("node:crypto");
  #|     const nodePath = require("node:path");
  #|     const dir = nodePath.dirname(path);
  #|     const tmp = nodePath.join(dir, ".mem_" + crypto.randomBytes(8).toString("hex") + ".tmp");
  #|     const cleanup = () => { try { fs.unlinkSync(tmp); } catch {} };
  #|     const fd = fs.openSync(tmp, "w");
  #|     try {
  #|       try { fs.writeSync(fd, content); fs.fsyncSync(fd); }
  #|       finally { fs.closeSync(fd); }
  #|     } catch (e) { cleanup(); return false; }
  #|     try { fs.renameSync(tmp, path); return true; }
  #|     catch (e) { cleanup(); return false; }
  #|   } catch (e) { return false; }
  #| }