#include <compat.h> // IWYU pragma: keep

#include <uv.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <uthash.h>
#include <utarray.h>
#include <errno.h>

#include "ant.h"
#include "utf8.h"
#include "utils.h"
#include "base64.h"
#include "errors.h"
#include "internal.h"
#include "runtime.h"
#include "descriptors.h"

#include "gc/modules.h"

#include "modules/fs.h"
#include "modules/buffer.h"
#include "modules/symbol.h"

#define fs_err_code(js, code, op, path) ({ \
  ant_value_t _props = js_mkobj(js); \
  js_set(js, _props, "code", js_mkstr(js, code, strlen(code))); \
  js_mkerr_props(js, JS_ERR_TYPE, _props, "%s: %s, %s '%s'", code, strerror(errno), op, path); \
})

typedef enum {
  FS_ENC_NONE = 0,
  FS_ENC_UTF8,
  FS_ENC_UTF16LE,
  FS_ENC_LATIN1,
  FS_ENC_BASE64,
  FS_ENC_BASE64URL,
  FS_ENC_HEX,
  FS_ENC_ASCII,
} fs_encoding_t;

static ant_value_t fs_coerce_path(ant_t *js, ant_value_t arg) {
  if (vtype(arg) == T_STR) return arg;
  if (is_object_type(arg)) {
    ant_value_t pathname = js_get(js, arg, "pathname");
    if (vtype(pathname) == T_STR) return pathname;
  }
  return js_mkundef();
}

static fs_encoding_t parse_encoding(ant_t *js, ant_value_t arg) {
  size_t len;
  const char *str = NULL;

  if (vtype(arg) == T_STR) str = js_getstr(js, arg, &len); 
  else if (vtype(arg) == T_OBJ) {
    ant_value_t enc = js_get(js, arg, "encoding");
    if (vtype(enc) == T_STR) str = js_getstr(js, enc, &len);
  }

  if (!str) return FS_ENC_NONE;

  if (len == 4 && memcmp(str, "utf8", 4) == 0)      return FS_ENC_UTF8;
  if (len == 5 && memcmp(str, "utf-8", 5) == 0)     return FS_ENC_UTF8;
  if (len == 7 && memcmp(str, "utf16le", 7) == 0)   return FS_ENC_UTF16LE;
  if (len == 4 && memcmp(str, "ucs2", 4) == 0)      return FS_ENC_UTF16LE;
  if (len == 5 && memcmp(str, "ucs-2", 5) == 0)     return FS_ENC_UTF16LE;
  if (len == 6 && memcmp(str, "latin1", 6) == 0)    return FS_ENC_LATIN1;
  if (len == 6 && memcmp(str, "binary", 6) == 0)    return FS_ENC_LATIN1;
  if (len == 6 && memcmp(str, "base64", 6) == 0)    return FS_ENC_BASE64;
  if (len == 9 && memcmp(str, "base64url", 9) == 0) return FS_ENC_BASE64URL;
  if (len == 3 && memcmp(str, "hex", 3) == 0)       return FS_ENC_HEX;
  if (len == 5 && memcmp(str, "ascii", 5) == 0)     return FS_ENC_ASCII;

  return FS_ENC_NONE;
}

static ant_value_t encode_data(ant_t *js, const char *data, size_t len, fs_encoding_t enc) {
  switch (enc) {
    case FS_ENC_UTF8:
    case FS_ENC_LATIN1:
    case FS_ENC_ASCII: return js_mkstr(js, data, len);

    case FS_ENC_HEX: {
      char *out = malloc(len * 2);
      if (!out) return js_mkerr(js, "Out of memory");
      for (size_t i = 0; i < len; i++) {
        out[i * 2] = hex_char((unsigned char)data[i] >> 4);
        out[i * 2 + 1] = hex_char(data[i]);
      }
      ant_value_t result = js_mkstr(js, out, len * 2);
      free(out); return result;
    }

    case FS_ENC_BASE64: {
      size_t out_len;
      char *out = ant_base64_encode((const uint8_t *)data, len, &out_len);
      if (!out) return js_mkerr(js, "Out of memory");
      ant_value_t result = js_mkstr(js, out, out_len);
      free(out); return result;
    }

    case FS_ENC_BASE64URL: {
      size_t out_len;
      char *out = ant_base64_encode((const uint8_t *)data, len, &out_len);
      if (!out) return js_mkerr(js, "Out of memory");
      for (size_t i = 0; i < out_len; i++) {
        if (out[i] == '+') out[i] = '-';
        else if (out[i] == '/') out[i] = '_';
      }
      while (out_len > 0 && out[out_len - 1] == '=') out_len--;
      ant_value_t result = js_mkstr(js, out, out_len);
      free(out); return result;
    }

    case FS_ENC_UTF16LE: {
      const unsigned char *s = (const unsigned char *)data;
      char *out = malloc((len / 2) * 4);
      if (!out) return js_mkerr(js, "Out of memory");
      size_t j = 0;
      for (size_t i = 0; i + 1 < len; i += 2) {
        uint32_t cp = s[i] | (s[i + 1] << 8);
        bool is_hi = cp >= 0xD800 && cp <= 0xDBFF && i + 3 < len;
        uint32_t lo = is_hi ? s[i + 2] | (s[i + 3] << 8) : 0;
        bool is_pair = is_hi && lo >= 0xDC00 && lo <= 0xDFFF;
        if (is_pair) { cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); i += 2; }
        j += utf8_encode(cp, out + j);
      }
      ant_value_t result = js_mkstr(js, out, j);
      free(out); return result;
    }

    default: return js_mkstr(js, data, len);
  }
}

typedef enum {
  FS_OP_READ,
  FS_OP_WRITE,
  FS_OP_UNLINK,
  FS_OP_MKDIR,
  FS_OP_RMDIR,
  FS_OP_STAT,
  FS_OP_READ_BYTES,
  FS_OP_EXISTS,
  FS_OP_READDIR,
  FS_OP_ACCESS,
  FS_OP_WRITE_FD,
  FS_OP_OPEN,
  FS_OP_CLOSE
} fs_op_type_t;

typedef struct fs_request_s {
  uv_fs_t uv_req;
  ant_t *js;
  ant_value_t promise;
  char *path;
  char *data;
  char *error_msg;
  size_t data_len;
  fs_op_type_t op_type;
  fs_encoding_t encoding;
  uv_file fd;
  int completed;
  int failed;
  int recursive;
} fs_request_t;

static UT_array *pending_requests = NULL;

static void free_fs_request(fs_request_t *req) {
  if (!req) return;
  
  if (req->path) free(req->path);
  if (req->data) free(req->data);
  if (req->error_msg) free(req->error_msg);
  
  uv_fs_req_cleanup(&req->uv_req);
  free(req);
}

static void remove_pending_request(fs_request_t *req) {
  if (!req || !pending_requests) return;
  unsigned int len = utarray_len(pending_requests);
  
  for (unsigned int i = 0; i < len; i++) {
    if (*(fs_request_t**)utarray_eltptr(pending_requests, i) != req) continue;
    utarray_erase(pending_requests, i, 1); break;
  }
}

static ant_value_t fs_read_to_uint8array(ant_t *js, const char *data, size_t len) {
  ArrayBufferData *ab = create_array_buffer_data(len);
  if (!ab) return js_mkundef();
  memcpy(ab->data, data, len);
  ant_value_t result = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Buffer");
  if (vtype(result) == T_ERR) free_array_buffer_data(ab);
  return result;
}

static void complete_request(fs_request_t *req) {
  if (req->failed) {
    const char *err_msg = req->error_msg ? req->error_msg : "Unknown error";
    ant_value_t reject_value = js_mkerr(req->js, "%s", err_msg);
    if (is_err(reject_value)) {
      reject_value = req->js->thrown_value;
      if (vtype(reject_value) == T_UNDEF) {
        reject_value = js_mkstr(req->js, err_msg, strlen(err_msg));
      }
      req->js->thrown_exists = false;
      req->js->thrown_value = js_mkundef();
    }
    js_reject_promise(req->js, req->promise, reject_value);
  } else {
    ant_value_t result = js_mkundef();
    if (req->op_type == FS_OP_READ && req->data) {
      if (req->encoding != FS_ENC_NONE)
        result = encode_data(req->js, req->data, req->data_len, req->encoding);
      else result = fs_read_to_uint8array(req->js, req->data, req->data_len);
    } else if (req->op_type == FS_OP_READ_BYTES && req->data)
      result = js_mkstr(req->js, req->data, req->data_len);
    js_resolve_promise(req->js, req->promise, result);
  }
  
  remove_pending_request(req);
  free_fs_request(req);
}

static void on_read_complete(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
    req->completed = 1;
    complete_request(req);
    return;
  }
  
  req->data_len = uv_req->result;
  uv_fs_t close_req;
  
  uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
  uv_fs_req_cleanup(&close_req);
  
  req->completed = 1;
  complete_request(req);
}

static void on_open_for_read(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
    req->completed = 1;
    complete_request(req);
    return;
  }
  
  req->fd = (int)uv_req->result;
  uv_fs_req_cleanup(uv_req);
  
  uv_fs_t stat_req;
  int stat_result = uv_fs_fstat(uv_default_loop(), &stat_req, req->fd, NULL);
  
  if (stat_result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(stat_result));
    req->completed = 1;
    uv_fs_t close_req;
    uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
    uv_fs_req_cleanup(&close_req);
    complete_request(req);
    return;
  }
  
  size_t file_size = stat_req.statbuf.st_size;
  uv_fs_req_cleanup(&stat_req);
  
  size_t alloc_size = (req->op_type == FS_OP_READ) ? file_size + 1 : file_size;
  req->data = malloc(alloc_size);
  if (!req->data) {
    req->failed = 1;
    req->error_msg = strdup("Out of memory");
    req->completed = 1;
    uv_fs_t close_req;
    uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
    uv_fs_req_cleanup(&close_req);
    complete_request(req);
    return;
  }
  
  uv_buf_t buf = uv_buf_init(req->data, (unsigned int)file_size);
  int read_result = uv_fs_read(uv_default_loop(), uv_req, req->fd, &buf, 1, 0, on_read_complete);
  
  if (read_result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(read_result));
    req->completed = 1;
    uv_fs_t close_req;
    uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
    uv_fs_req_cleanup(&close_req);
    complete_request(req);
    return;
  }
}

static void on_write_complete(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
  }
  
  uv_fs_t close_req;
  uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
  uv_fs_req_cleanup(&close_req);
  
  req->completed = 1;
  complete_request(req);
}

static void on_open_for_write(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
    req->completed = 1;
    complete_request(req);
    return;
  }
  
  req->fd = (int)uv_req->result;
  uv_fs_req_cleanup(uv_req);
  
  uv_buf_t buf = uv_buf_init(req->data, (unsigned int)req->data_len);
  int write_result = uv_fs_write(uv_default_loop(), uv_req, req->fd, &buf, 1, 0, on_write_complete);
  
  if (write_result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(write_result));
    req->completed = 1;
    uv_fs_t close_req;
    uv_fs_close(uv_default_loop(), &close_req, req->fd, NULL);
    uv_fs_req_cleanup(&close_req);
    complete_request(req);
    return;
  }
}

static void on_unlink_complete(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
  }
  
  uv_fs_req_cleanup(uv_req);
  req->completed = 1;
  complete_request(req);
}

static void on_mkdir_complete(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
  if (!req->recursive || uv_req->result != UV_EEXIST) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
  }}
  
  uv_fs_req_cleanup(uv_req);
  req->completed = 1;
  complete_request(req);
}

static void on_rmdir_complete(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
  }
  
  uv_fs_req_cleanup(uv_req);
  req->completed = 1;
  complete_request(req);
}

static void on_stat_complete(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
    req->completed = 1;
    complete_request(req);
    return;
  }
  
  ant_value_t stat_obj = js_mkobj(req->js);
  ant_value_t proto = js_get_ctor_proto(req->js, "Stats", 5);
  if (is_object_type(proto)) js_set_proto_init(stat_obj, proto);
  
  uv_stat_t *st = &uv_req->statbuf;
  js_set_slot(stat_obj, SLOT_DATA, js_mknum((double)st->st_mode));
  js_set(req->js, stat_obj, "size", js_mknum((double)st->st_size));
  js_set(req->js, stat_obj, "mode", js_mknum((double)st->st_mode));
  js_set(req->js, stat_obj, "uid", js_mknum((double)st->st_uid));
  js_set(req->js, stat_obj, "gid", js_mknum((double)st->st_gid));
  
  req->completed = 1;
  js_resolve_promise(req->js, req->promise, stat_obj);
  remove_pending_request(req);
  free_fs_request(req);
}

static void on_exists_complete(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  ant_value_t result = js_bool(uv_req->result >= 0);
  
  req->completed = 1;
  js_resolve_promise(req->js, req->promise, result);
  remove_pending_request(req);
  free_fs_request(req);
}

static void on_access_complete(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
    req->completed = 1;
    complete_request(req);
    return;
  }
  
  req->completed = 1;
  js_resolve_promise(req->js, req->promise, js_mkundef());
  remove_pending_request(req);
  free_fs_request(req);
}

static void on_readdir_complete(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
    req->completed = 1;
    complete_request(req);
    return;
  }
  
  ant_value_t arr = js_mkarr(req->js);
  uv_dirent_t dirent;
  
  while (uv_fs_scandir_next(uv_req, &dirent) != UV_EOF) {
    ant_value_t name = js_mkstr(req->js, dirent.name, strlen(dirent.name));
    js_arr_push(req->js, arr, name);
  }
  
  req->completed = 1;
  js_resolve_promise(req->js, req->promise, arr);
  remove_pending_request(req);
  free_fs_request(req);
}

static ant_value_t builtin_fs_readFileSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "readFileSync() requires a path argument");
  
  ant_value_t path_val = fs_coerce_path(js, args[0]);
  if (vtype(path_val) != T_STR) return js_mkerr(js, "readFileSync() path must be a string or URL");
  
  size_t path_len;
  char *path = js_getstr(js, path_val, &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  char *path_cstr = strndup(path, path_len);
  if (!path_cstr) return js_mkerr(js, "Out of memory");
  
  FILE *file = fopen(path_cstr, "rb");
  if (!file) {
    char err_msg[256];
    snprintf(err_msg, sizeof(err_msg), "Failed to open file: %s", strerror(errno));
    free(path_cstr); return js_mkerr(js, "%s", err_msg);
  }
  
  fseek(file, 0, SEEK_END);
  long file_size = ftell(file);
  fseek(file, 0, SEEK_SET);
  
  if (file_size < 0) {
    fclose(file);
    free(path_cstr);
    return js_mkerr(js, "Failed to get file size");
  }
  
  char *data = malloc(file_size + 1);
  if (!data) {
    fclose(file);
    free(path_cstr);
    return js_mkerr(js, "Out of memory");
  }
  
  size_t bytes_read = fread(data, 1, file_size, file);
  fclose(file);
  free(path_cstr);
  
  if (bytes_read != (size_t)file_size) {
    free(data);
    return js_mkerr(js, "Failed to read entire file");
  }
  
  fs_encoding_t enc = (nargs > 1) ? parse_encoding(js, args[1]) : FS_ENC_NONE;
  ant_value_t result = (enc != FS_ENC_NONE)
    ? encode_data(js, data, file_size, enc)
    : fs_read_to_uint8array(js, data, file_size);
    
  free(data);
  return result;
}

static ant_value_t builtin_fs_readBytesSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "readBytesSync() requires a path argument");
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "readBytesSync() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  char *path_cstr = strndup(path, path_len);
  if (!path_cstr) return js_mkerr(js, "Out of memory");
  
  FILE *file = fopen(path_cstr, "rb");
  if (!file) {
    char err_msg[256];
    snprintf(err_msg, sizeof(err_msg), "Failed to open file: %s", strerror(errno));
    free(path_cstr); return js_mkerr(js, "%s", err_msg);
  }
  
  fseek(file, 0, SEEK_END);
  long file_size = ftell(file);
  fseek(file, 0, SEEK_SET);
  
  if (file_size < 0) {
    fclose(file);
    free(path_cstr);
    return js_mkerr(js, "Failed to get file size");
  }
  
  char *data = malloc(file_size);
  if (!data) {
    fclose(file);
    free(path_cstr);
    return js_mkerr(js, "Out of memory");
  }
  
  size_t bytes_read = fread(data, 1, file_size, file);
  fclose(file);
  free(path_cstr);
  
  if (bytes_read != (size_t)file_size) {
    free(data);
    return js_mkerr(js, "Failed to read entire file");
  }
  
  ant_value_t result = js_mkstr(js, data, file_size);
  free(data);
  
  return result;
}

static ant_value_t builtin_fs_readFile(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "readFile() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "readFile() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_READ;
  req->encoding = (nargs > 1) ? parse_encoding(js, args[1]) : FS_ENC_NONE;
  req->promise = js_mkpromise(js);
  req->path = strndup(path, path_len);
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  int result = uv_fs_open(uv_default_loop(), &req->uv_req, req->path, O_RDONLY, 0, on_open_for_read);
  
  if (result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(result));
    req->completed = 1;
    complete_request(req);
  }
  
  return req->promise;
}

static ant_value_t builtin_fs_readBytes(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "readBytes() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "readBytes() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_READ_BYTES;
  req->promise = js_mkpromise(js);
  req->path = strndup(path, path_len);
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  int result = uv_fs_open(uv_default_loop(), &req->uv_req, req->path, O_RDONLY, 0, on_open_for_read);
  
  if (result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(result));
    req->completed = 1;
    complete_request(req);
  }
  
  return req->promise;
}

static ant_value_t builtin_fs_writeFileSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 2) return js_mkerr(js, "writeFileSync() requires path and data arguments");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "writeFileSync() path must be a string");
  if (vtype(args[1]) != T_STR) return js_mkerr(js, "writeFileSync() data must be a string");
  
  size_t path_len, data_len;
  char *path = js_getstr(js, args[0], &path_len);
  char *data = js_getstr(js, args[1], &data_len);
  
  if (!path || !data) return js_mkerr(js, "Failed to get arguments");
  
  char *path_cstr = strndup(path, path_len);
  if (!path_cstr) return js_mkerr(js, "Out of memory");
  
  FILE *file = fopen(path_cstr, "wb");
  if (!file) {
    char err_msg[256];
    snprintf(err_msg, sizeof(err_msg), "Failed to open file: %s", strerror(errno));
    free(path_cstr); return js_mkerr(js, "%s", err_msg);
  }
  
  size_t bytes_written = fwrite(data, 1, data_len, file);
  fclose(file);
  free(path_cstr);
  
  if (bytes_written != data_len) {
    return js_mkerr(js, "Failed to write entire file");
  }
  
  return js_mkundef();
}

static ant_value_t builtin_fs_copyFileSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 2) return js_mkerr(js, "copyFileSync() requires src and dest arguments");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "copyFileSync() src must be a string");
  if (vtype(args[1]) != T_STR) return js_mkerr(js, "copyFileSync() dest must be a string");
  
  size_t src_len, dest_len;
  char *src = js_getstr(js, args[0], &src_len);
  char *dest = js_getstr(js, args[1], &dest_len);
  
  if (!src || !dest) return js_mkerr(js, "Failed to get arguments");
  
  char *src_cstr = strndup(src, src_len);
  char *dest_cstr = strndup(dest, dest_len);
  if (!src_cstr || !dest_cstr) {
    free(src_cstr);
    free(dest_cstr);
    return js_mkerr(js, "Out of memory");
  }
  
  FILE *in = fopen(src_cstr, "rb");
  if (!in) {
    char err_msg[256];
    snprintf(err_msg, sizeof(err_msg), "Failed to open source file: %s", strerror(errno));
    free(src_cstr); free(dest_cstr);
    return js_mkerr(js, "%s", err_msg);  
  }
  
  FILE *out = fopen(dest_cstr, "wb");
  if (!out) {
    char err_msg[256];
    snprintf(err_msg, sizeof(err_msg), "Failed to open dest file: %s", strerror(errno));
    fclose(in);
    free(src_cstr); free(dest_cstr);
    return js_mkerr(js, "%s", err_msg);
  }
  
  char buf[8192];
  size_t n;
  while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
    if (fwrite(buf, 1, n, out) != n) {
      fclose(in);
      fclose(out);
      free(src_cstr);
      free(dest_cstr);
      return js_mkerr(js, "Failed to write to dest file");
    }
  }
  
  fclose(in);
  fclose(out);
  free(src_cstr);
  free(dest_cstr);
  
  return js_mkundef();
}

static ant_value_t builtin_fs_renameSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 2) return js_mkerr(js, "renameSync() requires oldPath and newPath arguments");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "renameSync() oldPath must be a string");
  if (vtype(args[1]) != T_STR) return js_mkerr(js, "renameSync() newPath must be a string");
  
  size_t old_len, new_len;
  char *old_path = js_getstr(js, args[0], &old_len);
  char *new_path = js_getstr(js, args[1], &new_len);
  
  if (!old_path || !new_path) return js_mkerr(js, "Failed to get arguments");
  
  char *old_cstr = strndup(old_path, old_len);
  char *new_cstr = strndup(new_path, new_len);
  if (!old_cstr || !new_cstr) {
    free(old_cstr);
    free(new_cstr);
    return js_mkerr(js, "Out of memory");
  }
  
  int result = rename(old_cstr, new_cstr);
  free(old_cstr);
  free(new_cstr);
  
  if (result != 0) {
    char err_msg[256];
    snprintf(err_msg, sizeof(err_msg), "Failed to rename: %s", strerror(errno));
    return js_mkerr(js, "%s", err_msg);
  }
  
  return js_mkundef();
}

static ant_value_t builtin_fs_appendFileSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 2) return js_mkerr(js, "appendFileSync() requires path and data arguments");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "appendFileSync() path must be a string");
  if (vtype(args[1]) != T_STR) return js_mkerr(js, "appendFileSync() data must be a string");
  
  size_t path_len, data_len;
  char *path = js_getstr(js, args[0], &path_len);
  char *data = js_getstr(js, args[1], &data_len);
  
  if (!path || !data) return js_mkerr(js, "Failed to get arguments");
  
  char *path_cstr = strndup(path, path_len);
  if (!path_cstr) return js_mkerr(js, "Out of memory");
  
  FILE *file = fopen(path_cstr, "ab");
  if (!file) {
    char err_msg[256];
    snprintf(err_msg, sizeof(err_msg), "Failed to open file: %s", strerror(errno));
    free(path_cstr); return js_mkerr(js, "%s", err_msg);
  }
  
  size_t bytes_written = fwrite(data, 1, data_len, file);
  fclose(file);
  free(path_cstr);
  
  if (bytes_written != data_len) {
    return js_mkerr(js, "Failed to write entire file");
  }
  
  return js_mkundef();
}

static ant_value_t builtin_fs_writeFile(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 2) return js_mkerr(js, "writeFile() requires path and data arguments");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "writeFile() path must be a string");
  if (vtype(args[1]) != T_STR) return js_mkerr(js, "writeFile() data must be a string");
  
  size_t path_len, data_len;
  char *path = js_getstr(js, args[0], &path_len);
  char *data = js_getstr(js, args[1], &data_len);
  
  if (!path || !data) return js_mkerr(js, "Failed to get arguments");
  
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_WRITE;
  req->promise = js_mkpromise(js);
  req->path = strndup(path, path_len);
  req->data = malloc(data_len);
  if (!req->data) {
    free(req->path);
    free(req);
    return js_mkerr(js, "Out of memory");
  }
  
  memcpy(req->data, data, data_len);
  req->data_len = data_len;
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  int result = uv_fs_open(uv_default_loop(), &req->uv_req, req->path, O_WRONLY | O_CREAT | O_TRUNC, 0644, on_open_for_write);
  
  if (result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(result));
    req->completed = 1;
    complete_request(req);
  }
  
  return req->promise;
}

static ant_value_t builtin_fs_unlinkSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "unlinkSync() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "unlinkSync() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  char *path_cstr = strndup(path, path_len);
  if (!path_cstr) return js_mkerr(js, "Out of memory");
  
  int result = unlink(path_cstr);
  free(path_cstr);
  
  if (result != 0) {
    char err_msg[256];
    snprintf(err_msg, sizeof(err_msg), "Failed to unlink file: %s", strerror(errno));
    return js_mkerr(js, "%s", err_msg);
  }
  
  return js_mkundef();
}

static ant_value_t builtin_fs_unlink(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "unlink() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "unlink() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_UNLINK;
  req->promise = js_mkpromise(js);
  req->path = strndup(path, path_len);
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  int result = uv_fs_unlink(uv_default_loop(), &req->uv_req, req->path, on_unlink_complete);
  
  if (result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(result));
    req->completed = 1;
    complete_request(req);
  }
  
  return req->promise;
}

static ant_value_t builtin_fs_mkdirSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "mkdirSync() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "mkdirSync() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  int mode = 0755;
  int recursive = 0;
  
  if (nargs < 2) goto do_mkdir;
  
  switch (vtype(args[1])) {
    case T_NUM:
      mode = (int)js_getnum(args[1]);
      break;
    case T_OBJ: {
      ant_value_t opt = args[1];
      recursive = js_get(js, opt, "recursive") == js_true;
      ant_value_t mode_val = js_get(js, opt, "mode");
      if (vtype(mode_val) == T_NUM) mode = (int)js_getnum(mode_val);
      break;
    }
  }
  
do_mkdir:
  
  char *path_cstr = strndup(path, path_len);
  if (!path_cstr) return js_mkerr(js, "Out of memory");
  
#ifdef _WIN32
  (void)mode;
  int result = _mkdir(path_cstr);
#else
  int result = mkdir(path_cstr, (mode_t)mode);
#endif
  free(path_cstr);
  
  if (result != 0) {
    if (recursive && errno == EEXIST) {
      return js_mkundef();
    }
    char err_msg[256];
    snprintf(err_msg, sizeof(err_msg), "Failed to create directory: %s", strerror(errno));
    return js_mkerr(js, "%s", err_msg);
  }
  
  return js_mkundef();
}

static ant_value_t builtin_fs_mkdir(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "mkdir() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "mkdir() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  int mode = 0755;
  int recursive = 0;
  if (nargs >= 2) {
    switch (vtype(args[1])) {
      case T_NUM:
        mode = (int)js_getnum(args[1]);
        break;
      case T_OBJ: {
        ant_value_t opt = args[1];
        recursive = js_get(js, opt, "recursive") == js_true;
        ant_value_t mode_val = js_get(js, opt, "mode");
        if (vtype(mode_val) == T_NUM) mode = (int)js_getnum(mode_val);
        break;
      }
    }
  }

  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_MKDIR;
  req->promise = js_mkpromise(js);
  req->path = strndup(path, path_len);
  req->recursive = recursive;
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  int result = uv_fs_mkdir(uv_default_loop(), &req->uv_req, req->path, mode, on_mkdir_complete);
  
  if (result < 0) {
    if (recursive && result == UV_EEXIST) {
      req->completed = 1;
      complete_request(req);
    } else {
      req->failed = 1;
      req->error_msg = strdup(uv_strerror(result));
      req->completed = 1;
      complete_request(req);
    }
  }
  
  return req->promise;
}

static ant_value_t builtin_fs_rmdirSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "rmdirSync() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "rmdirSync() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  char *path_cstr = strndup(path, path_len);
  if (!path_cstr) return js_mkerr(js, "Out of memory");
  
#ifdef _WIN32
  int result = _rmdir(path_cstr);
#else
  int result = rmdir(path_cstr);
#endif
  free(path_cstr);
  
  if (result != 0) {
    char err_msg[256];
    snprintf(err_msg, sizeof(err_msg), "Failed to remove directory: %s", strerror(errno));
    return js_mkerr(js, "%s", err_msg);
  }
  
  return js_mkundef();
}

static ant_value_t builtin_fs_rmdir(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "rmdir() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "rmdir() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_RMDIR;
  req->promise = js_mkpromise(js);
  req->path = strndup(path, path_len);
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  int result = uv_fs_rmdir(uv_default_loop(), &req->uv_req, req->path, on_rmdir_complete);
  
  if (result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(result));
    req->completed = 1;
    complete_request(req);
  }
  
  return req->promise;
}

static ant_value_t stat_isFile(ant_t *js, ant_value_t *args, int nargs) {
  ant_value_t this = js_getthis(js);
  ant_value_t mode_val = js_get_slot(this, SLOT_DATA);
  
  if (vtype(mode_val) != T_NUM) return js_false;
  mode_t mode = (mode_t)js_getnum(mode_val);
  
  return js_bool(S_ISREG(mode));
}

static ant_value_t stat_isDirectory(ant_t *js, ant_value_t *args, int nargs) {
  ant_value_t this = js_getthis(js);
  ant_value_t mode_val = js_get_slot(this, SLOT_DATA);
  
  if (vtype(mode_val) != T_NUM) return js_false;
  mode_t mode = (mode_t)js_getnum(mode_val);
  
  return js_bool(S_ISDIR(mode));
}

static ant_value_t stat_isSymbolicLink(ant_t *js, ant_value_t *args, int nargs) {
  ant_value_t this = js_getthis(js);
  ant_value_t mode_val = js_get_slot(this, SLOT_DATA);
  
  if (vtype(mode_val) != T_NUM) return js_false;
  mode_t mode = (mode_t)js_getnum(mode_val);
  
  return js_bool(S_ISLNK(mode));
}

static ant_value_t create_stats_object(ant_t *js, struct stat *st) {
  ant_value_t stat_obj = js_mkobj(js);
  ant_value_t proto = js_get_ctor_proto(js, "Stats", 5);
  if (is_special_object(proto)) js_set_proto_init(stat_obj, proto);
  
  js_set_slot(stat_obj, SLOT_DATA, js_mknum((double)st->st_mode));
  js_set(js, stat_obj, "size", js_mknum((double)st->st_size));
  js_set(js, stat_obj, "mode", js_mknum((double)st->st_mode));
  js_set(js, stat_obj, "uid", js_mknum((double)st->st_uid));
  js_set(js, stat_obj, "gid", js_mknum((double)st->st_gid));
  
  return stat_obj;
}

static const char *errno_to_code(int err_num) {
  switch (err_num) {
    case ENOENT: return "ENOENT";
    case EACCES: return "EACCES";
    case ENOTDIR: return "ENOTDIR";
    case ELOOP: return "ELOOP";
    case ENAMETOOLONG: return "ENAMETOOLONG";
    case EOVERFLOW: return "EOVERFLOW";
    case EROFS: return "EROFS";
    case ETXTBSY: return "ETXTBSY";
    case EEXIST: return "EEXIST";
    case ENOTEMPTY: return "ENOTEMPTY";
    case EISDIR: return "EISDIR";
    case EBUSY: return "EBUSY";
    case EINVAL: return "EINVAL";
    case EPERM: return "EPERM";
    case EIO: return "EIO";
    default: return "UNKNOWN";
  }
}

static ant_value_t builtin_fs_statSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "statSync() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "statSync() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  char *path_cstr = strndup(path, path_len);
  if (!path_cstr) return js_mkerr(js, "Out of memory");
  
  struct stat st;
  int result = stat(path_cstr, &st);
  
  if (result != 0) {
    const char *code = errno_to_code(errno);
    ant_value_t err = fs_err_code(js, code, "stat", path_cstr);
    free(path_cstr); return err;
  }
  
  free(path_cstr);
  return create_stats_object(js, &st);
}

static ant_value_t builtin_fs_stat(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "stat() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "stat() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_STAT;
  req->promise = js_mkpromise(js);
  req->path = strndup(path, path_len);
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  int result = uv_fs_stat(uv_default_loop(), &req->uv_req, req->path, on_stat_complete);
  
  if (result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(result));
    req->completed = 1;
    complete_request(req);
  }
  
  return req->promise;
}

static ant_value_t builtin_fs_existsSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "existsSync() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "existsSync() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  char *path_cstr = strndup(path, path_len);
  if (!path_cstr) return js_mkerr(js, "Out of memory");
  
  struct stat st;
  int result = stat(path_cstr, &st);
  free(path_cstr);
  
  return js_bool(result == 0);
}

static ant_value_t builtin_fs_exists(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "exists() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "exists() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_EXISTS;
  req->promise = js_mkpromise(js);
  req->path = strndup(path, path_len);
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  int result = uv_fs_stat(uv_default_loop(), &req->uv_req, req->path, on_exists_complete);
  
  if (result < 0) {
    req->completed = 1;
    js_resolve_promise(req->js, req->promise, js_false);
    remove_pending_request(req);
    free_fs_request(req);
  }
  
  return req->promise;
}

static ant_value_t builtin_fs_accessSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "accessSync() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "accessSync() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  int mode = F_OK;
  if (nargs >= 2 && vtype(args[1]) == T_NUM) {
    mode = (int)js_getnum(args[1]);
  }
  
  char *path_cstr = strndup(path, path_len);
  if (!path_cstr) return js_mkerr(js, "Out of memory");
  int result = access(path_cstr, mode);
  
  if (result != 0) {
    const char *code = errno_to_code(errno);
    ant_value_t err = fs_err_code(js, code, "access", path_cstr);
    free(path_cstr); return err;
  }
  
  free(path_cstr);
  return js_mkundef();
}

static ant_value_t builtin_fs_access(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "access() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "access() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  int mode = F_OK;
  if (nargs >= 2 && vtype(args[1]) == T_NUM) {
    mode = (int)js_getnum(args[1]);
  }
  
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_ACCESS;
  req->promise = js_mkpromise(js);
  req->path = strndup(path, path_len);
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  int result = uv_fs_access(uv_default_loop(), &req->uv_req, req->path, mode, on_access_complete);
  
  if (result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(result));
    req->completed = 1;
    complete_request(req);
  }
  
  return req->promise;
}

static ant_value_t builtin_fs_readdirSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "readdirSync() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "readdirSync() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  char *path_cstr = strndup(path, path_len);
  if (!path_cstr) return js_mkerr(js, "Out of memory");
  
  uv_fs_t req;
  int result = uv_fs_scandir(NULL, &req, path_cstr, 0, NULL);
  free(path_cstr);
  
  if (result < 0) {
    char err_msg[256];
    snprintf(err_msg, sizeof(err_msg), "Failed to read directory: %s", uv_strerror(result));
    uv_fs_req_cleanup(&req); return js_mkerr(js, "%s", err_msg);
  }
  
  ant_value_t arr = js_mkarr(js);
  uv_dirent_t dirent;
  
  while (uv_fs_scandir_next(&req, &dirent) != UV_EOF) {
    ant_value_t name = js_mkstr(js, dirent.name, strlen(dirent.name));
    js_arr_push(js, arr, name);
  }
  
  uv_fs_req_cleanup(&req);
  return arr;
}

static ant_value_t builtin_fs_readdir(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "readdir() requires a path argument");
  
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "readdir() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_READDIR;
  req->promise = js_mkpromise(js);
  req->path = strndup(path, path_len);
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  int result = uv_fs_scandir(uv_default_loop(), &req->uv_req, req->path, 0, on_readdir_complete);
  
  if (result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(result));
    req->completed = 1;
    complete_request(req);
  }
  
  return req->promise;
}

static void on_write_fd_complete(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
    req->completed = 1;
    complete_request(req);
    return;
  }
  
  req->completed = 1;
  js_resolve_promise(req->js, req->promise, js_mknum((double)uv_req->result));
  remove_pending_request(req);
  free_fs_request(req);
}

static ant_value_t builtin_fs_readSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 2) return js_mkerr(js, "readSync() requires fd and buffer arguments");
  if (vtype(args[0]) != T_NUM) return js_mkerr(js, "readSync() fd must be a number");
  
  int fd = (int)js_getnum(args[0]);
  
  ant_value_t ta_data_val = js_get_slot(args[1], SLOT_BUFFER);
  TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
  if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
    return js_mkerr(js, "readSync() second argument must be a Buffer, TypedArray, or DataView");
  
  uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset;
  size_t buf_len = ta_data->byte_length;
  size_t offset = 0;
  size_t length = buf_len;
  int64_t position = -1;
  
  if (nargs >= 3) {
    if (vtype(args[2]) == T_OBJ) {
      ant_value_t off_val = js_get(js, args[2], "offset");
      ant_value_t len_val = js_get(js, args[2], "length");
      ant_value_t pos_val = js_get(js, args[2], "position");
      if (vtype(off_val) == T_NUM) offset = (size_t)js_getnum(off_val);
      if (vtype(len_val) == T_NUM) length = (size_t)js_getnum(len_val);
      else length = buf_len - offset;
      if (vtype(pos_val) == T_NUM) position = (int64_t)js_getnum(pos_val);
    } else if (vtype(args[2]) == T_NUM) {
      offset = (size_t)js_getnum(args[2]);
      length = buf_len - offset;
      if (nargs >= 4 && vtype(args[3]) == T_NUM) length = (size_t)js_getnum(args[3]);
      if (nargs >= 5 && vtype(args[4]) == T_NUM) position = (int64_t)js_getnum(args[4]);
    }
  }
  
  if (offset > buf_len) return js_mkerr(js, "offset is out of bounds");
  if (offset + length > buf_len) return js_mkerr(js, "length extends beyond buffer");
  
  uv_fs_t req;
  uv_buf_t buf = uv_buf_init((char *)(buf_data + offset), (unsigned int)length);
  int result = uv_fs_read(uv_default_loop(), &req, fd, &buf, 1, position, NULL);
  uv_fs_req_cleanup(&req);
  
  if (result < 0) return js_mkerr(js, "readSync failed: %s", uv_strerror(result));
  return js_mknum((double)result);
}

static ant_value_t builtin_fs_writeSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 2) return js_mkerr(js, "writeSync() requires fd and data arguments");
  if (vtype(args[0]) != T_NUM) return js_mkerr(js, "writeSync() fd must be a number");
  
  int fd = (int)js_getnum(args[0]);
  
  if (vtype(args[1]) == T_STR) {
    size_t str_len;
    const char *str = js_getstr(js, args[1], &str_len);
    if (!str) return js_mkerr(js, "Failed to get string");
    
    int64_t position = -1;
    if (nargs >= 3 && vtype(args[2]) == T_NUM)
      position = (int64_t)js_getnum(args[2]);
    
    uv_fs_t req;
    uv_buf_t buf = uv_buf_init((char *)str, (unsigned int)str_len);
    int result = uv_fs_write(uv_default_loop(), &req, fd, &buf, 1, position, NULL);
    uv_fs_req_cleanup(&req);
    
    if (result < 0) return js_mkerr(js, "writeSync failed: %s", uv_strerror(result));
    return js_mknum((double)result);
  }
  
  ant_value_t ta_data_val = js_get_slot(args[1], SLOT_BUFFER);
  TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
  if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
    return js_mkerr(js, "writeSync() second argument must be a Buffer, TypedArray, DataView, or string");
  
  uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset;
  size_t buf_len = ta_data->byte_length;
  size_t offset = 0;
  size_t length = buf_len;
  int64_t position = -1;
  
  if (nargs >= 3) {
    if (vtype(args[2]) == T_OBJ) {
      ant_value_t off_val = js_get(js, args[2], "offset");
      ant_value_t len_val = js_get(js, args[2], "length");
      ant_value_t pos_val = js_get(js, args[2], "position");
      if (vtype(off_val) == T_NUM) offset = (size_t)js_getnum(off_val);
      if (vtype(len_val) == T_NUM) length = (size_t)js_getnum(len_val);
      else length = buf_len - offset;
      if (vtype(pos_val) == T_NUM) position = (int64_t)js_getnum(pos_val);
    } else if (vtype(args[2]) == T_NUM) {
      offset = (size_t)js_getnum(args[2]);
      length = buf_len - offset;
      if (nargs >= 4 && vtype(args[3]) == T_NUM) length = (size_t)js_getnum(args[3]);
      if (nargs >= 5 && vtype(args[4]) == T_NUM) position = (int64_t)js_getnum(args[4]);
    }
  }
  
  if (offset > buf_len) return js_mkerr(js, "offset is out of bounds");
  if (offset + length > buf_len) return js_mkerr(js, "length extends beyond buffer");
  
  uv_fs_t req;
  uv_buf_t buf = uv_buf_init((char *)(buf_data + offset), (unsigned int)length);
  int result = uv_fs_write(uv_default_loop(), &req, fd, &buf, 1, position, NULL);
  uv_fs_req_cleanup(&req);
  
  if (result < 0) return js_mkerr(js, "writeSync failed: %s", uv_strerror(result));
  return js_mknum((double)result);
}

static ant_value_t builtin_fs_write_fd(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 2) return js_mkerr(js, "write() requires fd and data arguments");
  if (vtype(args[0]) != T_NUM) return js_mkerr(js, "write() fd must be a number");
  
  int fd = (int)js_getnum(args[0]);
  const char *write_data;
  size_t write_len;
  int64_t position = -1;
  
  if (vtype(args[1]) == T_STR) {
    size_t str_len;
    const char *str = js_getstr(js, args[1], &str_len);
    if (!str) return js_mkerr(js, "Failed to get string");
    
    if (nargs >= 3 && vtype(args[2]) == T_NUM)
      position = (int64_t)js_getnum(args[2]);
    
    write_data = str;
    write_len = str_len;
  } else {
    ant_value_t ta_data_val = js_get_slot(args[1], SLOT_BUFFER);
    TypedArrayData *ta_data = (TypedArrayData *)js_gettypedarray(ta_data_val);
    if (!ta_data || !ta_data->buffer || !ta_data->buffer->data)
      return js_mkerr(js, "write() second argument must be a Buffer, TypedArray, DataView, or string");
    
    uint8_t *buf_data = ta_data->buffer->data + ta_data->byte_offset;
    size_t buf_len = ta_data->byte_length;
    size_t offset = 0;
    size_t length = buf_len;
    
    if (nargs >= 3) {
      if (vtype(args[2]) == T_OBJ) {
        ant_value_t off_val = js_get(js, args[2], "offset");
        ant_value_t len_val = js_get(js, args[2], "length");
        ant_value_t pos_val = js_get(js, args[2], "position");
        if (vtype(off_val) == T_NUM) offset = (size_t)js_getnum(off_val);
        if (vtype(len_val) == T_NUM) length = (size_t)js_getnum(len_val);
        else length = buf_len - offset;
        if (vtype(pos_val) == T_NUM) position = (int64_t)js_getnum(pos_val);
      } else if (vtype(args[2]) == T_NUM) {
        offset = (size_t)js_getnum(args[2]);
        length = buf_len - offset;
        if (nargs >= 4 && vtype(args[3]) == T_NUM) length = (size_t)js_getnum(args[3]);
        if (nargs >= 5 && vtype(args[4]) == T_NUM) position = (int64_t)js_getnum(args[4]);
      }
    }
    
    if (offset > buf_len) return js_mkerr(js, "offset is out of bounds");
    if (offset + length > buf_len) return js_mkerr(js, "length extends beyond buffer");
    
    write_data = (const char *)(buf_data + offset);
    write_len = length;
  }
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_WRITE_FD;
  req->promise = js_mkpromise(js);
  req->fd = fd;
  req->data = malloc(write_len);
  if (!req->data) {
    free(req);
    return js_mkerr(js, "Out of memory");
  }
  memcpy(req->data, write_data, write_len);
  req->data_len = write_len;
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  
  uv_buf_t buf = uv_buf_init(req->data, (unsigned int)req->data_len);
  int result = uv_fs_write(uv_default_loop(), &req->uv_req, req->fd, &buf, 1, position, on_write_fd_complete);
  
  if (result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(result));
    req->completed = 1;
    complete_request(req);
  }
  
  return req->promise;
}

static ant_value_t builtin_fs_writevSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 2) return js_mkerr(js, "writevSync() requires fd and buffers arguments");
  if (vtype(args[0]) != T_NUM) return js_mkerr(js, "writevSync() fd must be a number");
  
  int fd = (int)js_getnum(args[0]);
  ant_offset_t arr_len = js_arr_len(js, args[1]);
  if (arr_len == 0) return js_mknum(0);
  
  int64_t position = -1;
  if (nargs >= 3 && vtype(args[2]) == T_NUM)
    position = (int64_t)js_getnum(args[2]);
  
  uv_buf_t *bufs = calloc((size_t)arr_len, sizeof(uv_buf_t));
  if (!bufs) return js_mkerr(js, "Out of memory");
  
  for (ant_offset_t i = 0; i < arr_len; i++) {
    ant_value_t item = js_arr_get(js, args[1], i);
    ant_value_t ta_val = js_get_slot(item, SLOT_BUFFER);
    TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(ta_val);
    if (!ta || !ta->buffer || !ta->buffer->data) {
      free(bufs);
      return js_mkerr(js, "writevSync() buffers must contain ArrayBufferViews");
    }
    bufs[i] = uv_buf_init((char *)(ta->buffer->data + ta->byte_offset), (unsigned int)ta->byte_length);
  }
  
  uv_fs_t req;
  int result = uv_fs_write(uv_default_loop(), &req, fd, bufs, (unsigned int)arr_len, position, NULL);
  uv_fs_req_cleanup(&req);
  free(bufs);
  
  if (result < 0) return js_mkerr(js, "writevSync failed: %s", uv_strerror(result));
  return js_mknum((double)result);
}

static ant_value_t builtin_fs_writev_fd(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 2) return js_mkerr(js, "writev() requires fd and buffers arguments");
  if (vtype(args[0]) != T_NUM) return js_mkerr(js, "writev() fd must be a number");
  
  int fd = (int)js_getnum(args[0]);
  ant_offset_t arr_len = js_arr_len(js, args[1]);
  
  if (arr_len == 0) {
    ant_value_t promise = js_mkpromise(js);
    js_resolve_promise(js, promise, js_mknum(0));
    return promise;
  }
  
  int64_t position = -1;
  if (nargs >= 3 && vtype(args[2]) == T_NUM)
    position = (int64_t)js_getnum(args[2]);
  
  size_t total_len = 0;
  for (ant_offset_t i = 0; i < arr_len; i++) {
    ant_value_t item = js_arr_get(js, args[1], i);
    ant_value_t ta_val = js_get_slot(item, SLOT_BUFFER);
    TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(ta_val);
    if (!ta || !ta->buffer || !ta->buffer->data)
      return js_mkerr(js, "writev() buffers must contain ArrayBufferViews");
    total_len += ta->byte_length;
  }
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->data = malloc(total_len);
  if (!req->data) {
    free(req);
    return js_mkerr(js, "Out of memory");
  }
  
  size_t off = 0;
  for (ant_offset_t i = 0; i < arr_len; i++) {
    ant_value_t item = js_arr_get(js, args[1], i);
    ant_value_t ta_val = js_get_slot(item, SLOT_BUFFER);
    TypedArrayData *ta = (TypedArrayData *)js_gettypedarray(ta_val);
    memcpy(req->data + off, ta->buffer->data + ta->byte_offset, ta->byte_length);
    off += ta->byte_length;
  }
  
  req->js = js;
  req->op_type = FS_OP_WRITE_FD;
  req->promise = js_mkpromise(js);
  req->fd = fd;
  req->data_len = total_len;
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  
  uv_buf_t buf = uv_buf_init(req->data, (unsigned int)total_len);
  int result = uv_fs_write(uv_default_loop(), &req->uv_req, req->fd, &buf, 1, position, on_write_fd_complete);
  
  if (result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(result));
    req->completed = 1;
    complete_request(req);
  }
  
  return req->promise;
}

static int parse_open_flags(ant_t *js, ant_value_t arg) {
  if (vtype(arg) == T_NUM) return (int)js_getnum(arg);
  if (vtype(arg) != T_STR) return O_RDONLY;
  
  size_t len;
  const char *str = js_getstr(js, arg, &len);
  if (!str) return O_RDONLY;
  
  if (len == 1 && str[0] == 'r')                      return O_RDONLY;
  if (len == 2 && memcmp(str, "r+", 2) == 0)          return O_RDWR;
  if (len == 1 && str[0] == 'w')                      return O_WRONLY | O_CREAT | O_TRUNC;
  if (len == 2 && memcmp(str, "wx", 2) == 0)          return O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
  if (len == 2 && memcmp(str, "w+", 2) == 0)          return O_RDWR | O_CREAT | O_TRUNC;
  if (len == 3 && memcmp(str, "wx+", 3) == 0)         return O_RDWR | O_CREAT | O_TRUNC | O_EXCL;
  if (len == 1 && str[0] == 'a')                      return O_WRONLY | O_CREAT | O_APPEND;
  if (len == 2 && memcmp(str, "ax", 2) == 0)          return O_WRONLY | O_CREAT | O_APPEND | O_EXCL;
  if (len == 2 && memcmp(str, "a+", 2) == 0)          return O_RDWR | O_CREAT | O_APPEND;
  if (len == 3 && memcmp(str, "ax+", 3) == 0)         return O_RDWR | O_CREAT | O_APPEND | O_EXCL;
  
  return O_RDONLY;
}

static ant_value_t builtin_fs_openSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "openSync() requires a path argument");
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "openSync() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  int flags = (nargs >= 2) ? parse_open_flags(js, args[1]) : O_RDONLY;
  int mode = (nargs >= 3 && vtype(args[2]) == T_NUM) ? (int)js_getnum(args[2]) : 0666;
  
  char *path_cstr = strndup(path, path_len);
  if (!path_cstr) return js_mkerr(js, "Out of memory");
  
  uv_fs_t req;
  int result = uv_fs_open(uv_default_loop(), &req, path_cstr, flags, mode, NULL);
  uv_fs_req_cleanup(&req);
  free(path_cstr);
  
  if (result < 0) return js_mkerr(js, "openSync failed: %s", uv_strerror(result));
  return js_mknum((double)result);
}

static ant_value_t builtin_fs_closeSync(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "closeSync() requires a fd argument");
  if (vtype(args[0]) != T_NUM) return js_mkerr(js, "closeSync() fd must be a number");
  
  int fd = (int)js_getnum(args[0]);
  
  uv_fs_t req;
  int result = uv_fs_close(uv_default_loop(), &req, fd, NULL);
  uv_fs_req_cleanup(&req);
  
  if (result < 0) return js_mkerr(js, "closeSync failed: %s", uv_strerror(result));
  return js_mkundef();
}

static void on_open_fd_complete(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
    req->completed = 1;
    complete_request(req);
    return;
  }
  
  req->completed = 1;
  js_resolve_promise(req->js, req->promise, js_mknum((double)uv_req->result));
  remove_pending_request(req);
  free_fs_request(req);
}

static ant_value_t builtin_fs_open_fd(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "open() requires a path argument");
  if (vtype(args[0]) != T_STR) return js_mkerr(js, "open() path must be a string");
  
  size_t path_len;
  char *path = js_getstr(js, args[0], &path_len);
  if (!path) return js_mkerr(js, "Failed to get path string");
  
  int flags = (nargs >= 2) ? parse_open_flags(js, args[1]) : O_RDONLY;
  int mode = (nargs >= 3 && vtype(args[2]) == T_NUM) ? (int)js_getnum(args[2]) : 0666;
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_OPEN;
  req->promise = js_mkpromise(js);
  req->path = strndup(path, path_len);
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  int result = uv_fs_open(uv_default_loop(), &req->uv_req, req->path, flags, mode, on_open_fd_complete);
  
  if (result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(result));
    req->completed = 1;
    complete_request(req);
  }
  
  return req->promise;
}

static void on_close_fd_complete(uv_fs_t *uv_req) {
  fs_request_t *req = (fs_request_t *)uv_req->data;
  
  if (uv_req->result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror((int)uv_req->result));
    req->completed = 1;
    complete_request(req);
    return;
  }
  
  req->completed = 1;
  js_resolve_promise(req->js, req->promise, js_mkundef());
  remove_pending_request(req);
  free_fs_request(req);
}

static ant_value_t builtin_fs_close_fd(ant_t *js, ant_value_t *args, int nargs) {
  if (nargs < 1) return js_mkerr(js, "close() requires a fd argument");
  if (vtype(args[0]) != T_NUM) return js_mkerr(js, "close() fd must be a number");
  
  int fd = (int)js_getnum(args[0]);
  
  fs_request_t *req = calloc(1, sizeof(fs_request_t));
  if (!req) return js_mkerr(js, "Out of memory");
  
  req->js = js;
  req->op_type = FS_OP_CLOSE;
  req->promise = js_mkpromise(js);
  req->fd = fd;
  req->uv_req.data = req;
  
  utarray_push_back(pending_requests, &req);
  int result = uv_fs_close(uv_default_loop(), &req->uv_req, req->fd, on_close_fd_complete);
  
  if (result < 0) {
    req->failed = 1;
    req->error_msg = strdup(uv_strerror(result));
    req->completed = 1;
    complete_request(req);
  }
  
  return req->promise;
}

void init_fs_module(void) {
  utarray_new(pending_requests, &ut_ptr_icd);
  
  ant_t *js = rt->js;
  ant_value_t glob = js->global;
  
  ant_value_t stats_ctor = js_mkobj(js);
  ant_value_t stats_proto = js_mkobj(js);
  
  js_set(js, stats_proto, "isFile", js_mkfun(stat_isFile));
  js_set(js, stats_proto, "isDirectory", js_mkfun(stat_isDirectory));
  js_set(js, stats_proto, "isSymbolicLink", js_mkfun(stat_isSymbolicLink));
  js_set_sym(js, stats_proto, get_toStringTag_sym(), js_mkstr(js, "Stats", 5));
  
  js_mkprop_fast(js, stats_ctor, "prototype", 9, stats_proto);
  js_mkprop_fast(js, stats_ctor, "name", 4, js_mkstr(js, "Stats", 5));
  js_set_descriptor(js, stats_ctor, "name", 4, 0);
  
  js_set(js, glob, "Stats", js_obj_to_func(stats_ctor));
}

static void fs_set_promise_methods(ant_t *js, ant_value_t lib) {
  js_set(js, lib, "readFile", js_mkfun(builtin_fs_readFile));
  js_set(js, lib, "open", js_mkfun(builtin_fs_open_fd));
  js_set(js, lib, "close", js_mkfun(builtin_fs_close_fd));
  js_set(js, lib, "writeFile", js_mkfun(builtin_fs_writeFile));
  js_set(js, lib, "write", js_mkfun(builtin_fs_write_fd));
  js_set(js, lib, "writev", js_mkfun(builtin_fs_writev_fd));
  js_set(js, lib, "unlink", js_mkfun(builtin_fs_unlink));
  js_set(js, lib, "mkdir", js_mkfun(builtin_fs_mkdir));
  js_set(js, lib, "rmdir", js_mkfun(builtin_fs_rmdir));
  js_set(js, lib, "stat", js_mkfun(builtin_fs_stat));
  js_set(js, lib, "exists", js_mkfun(builtin_fs_exists));
  js_set(js, lib, "access", js_mkfun(builtin_fs_access));
  js_set(js, lib, "readdir", js_mkfun(builtin_fs_readdir));
}

static ant_value_t fs_make_constants(ant_t *js) {
  ant_value_t constants = js_mkobj(js);
  js_set(js, constants, "F_OK", js_mknum(F_OK));
  js_set(js, constants, "R_OK", js_mknum(R_OK));
  js_set(js, constants, "W_OK", js_mknum(W_OK));
  js_set(js, constants, "X_OK", js_mknum(X_OK));
  js_set(js, constants, "O_RDONLY", js_mknum(O_RDONLY));
  js_set(js, constants, "O_WRONLY", js_mknum(O_WRONLY));
  js_set(js, constants, "O_RDWR", js_mknum(O_RDWR));
  js_set(js, constants, "O_CREAT", js_mknum(O_CREAT));
  js_set(js, constants, "O_EXCL", js_mknum(O_EXCL));
  js_set(js, constants, "O_TRUNC", js_mknum(O_TRUNC));
  js_set(js, constants, "O_APPEND", js_mknum(O_APPEND));
  return constants;
}

static ant_value_t builtin_fs_promises_getter(ant_t *js, ant_value_t *args, int nargs) {
  ant_value_t getter_fn = js_getcurrentfunc(js);
  ant_value_t cached = js_get_slot(getter_fn, SLOT_DATA);
  if (is_object_type(cached)) return cached;

  ant_value_t promises = fs_promises_library(js);
  js_set_slot(getter_fn, SLOT_DATA, promises);
  return promises;
}

ant_value_t fs_library(ant_t *js) {
  ant_value_t lib = js_mkobj(js);
  fs_set_promise_methods(js, lib);

  js_set(js, lib, "readFileSync", js_mkfun(builtin_fs_readFileSync));
  js_set(js, lib, "readSync", js_mkfun(builtin_fs_readSync));
  js_set(js, lib, "stream", js_mkfun(builtin_fs_readBytes));
  js_set(js, lib, "openSync", js_mkfun(builtin_fs_openSync));
  js_set(js, lib, "closeSync", js_mkfun(builtin_fs_closeSync));
  js_set(js, lib, "writeFileSync", js_mkfun(builtin_fs_writeFileSync));
  js_set(js, lib, "writeSync", js_mkfun(builtin_fs_writeSync));
  js_set(js, lib, "writevSync", js_mkfun(builtin_fs_writevSync));
  js_set(js, lib, "appendFileSync", js_mkfun(builtin_fs_appendFileSync));
  js_set(js, lib, "copyFileSync", js_mkfun(builtin_fs_copyFileSync));
  js_set(js, lib, "renameSync", js_mkfun(builtin_fs_renameSync));
  js_set(js, lib, "unlinkSync", js_mkfun(builtin_fs_unlinkSync));
  js_set(js, lib, "mkdirSync", js_mkfun(builtin_fs_mkdirSync));
  js_set(js, lib, "rmdirSync", js_mkfun(builtin_fs_rmdirSync));
  js_set(js, lib, "statSync", js_mkfun(builtin_fs_statSync));
  js_set(js, lib, "existsSync", js_mkfun(builtin_fs_existsSync));
  js_set(js, lib, "accessSync", js_mkfun(builtin_fs_accessSync));
  js_set(js, lib, "readdirSync", js_mkfun(builtin_fs_readdirSync));
  
  js_set_getter_desc(
    js, lib,
    "promises", 8,
    js_heavy_mkfun(js, builtin_fs_promises_getter, js_mkundef()),
    JS_DESC_E | JS_DESC_C
  );
  
  js_set(js, lib, "constants", fs_make_constants(js));
  js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "fs", 2));
  
  return lib;
}

ant_value_t fs_promises_library(ant_t *js) {
  ant_value_t lib = js_mkobj(js);

  fs_set_promise_methods(js, lib);
  js_set(js, lib, "constants", fs_make_constants(js));

  js_set_sym(js, lib, get_toStringTag_sym(), js_mkstr(js, "fs/promises", 11));
  return lib;
}

int has_pending_fs_ops(void) {
  return pending_requests && utarray_len(pending_requests) > 0;
}

void gc_mark_fs(ant_t *js, gc_mark_fn mark) {
  if (!pending_requests) return;
  unsigned int len = utarray_len(pending_requests);
  for (unsigned int i = 0; i < len; i++) {
    fs_request_t **reqp = (fs_request_t **)utarray_eltptr(pending_requests, i);
    if (reqp && *reqp) { mark(js, (*reqp)->promise); }
  }
}

