var WasmFSLibrary = {
  $wasmFS$preloadedFiles: [],
  $wasmFS$preloadedDirs: [],
#if USE_CLOSURE_COMPILER
  // Declare variable for Closure, FS.createPreloadedFile() below calls Browser.handledByPreloadPlugin()
  $FS__postset: '/**@suppress {duplicate, undefinedVars}*/var Browser;',
#endif
  $FS__deps: [
    '$wasmFS$preloadedFiles',
    '$wasmFS$preloadedDirs',
    '$asyncLoad',
  ],
  $FS : {
    // TODO: Clean up the following functions - currently copied from library_fs.js directly.
    createPreloadedFile: (parent, name, url, canRead, canWrite, onload, onerror, dontCreateFile, canOwn, preFinish) => {
      // TODO: use WasmFS code to resolve and join the path here?
      var fullname = name ? parent + '/' + name : parent;
      var dep = getUniqueRunDependency('cp ' + fullname); // might have several active requests for the same fullname
      function processData(byteArray) {
        function finish(byteArray) {
          if (preFinish) preFinish();
          if (!dontCreateFile) {
            FS.createDataFile(parent, name, byteArray, canRead, canWrite, canOwn);
          }
          if (onload) onload();
          removeRunDependency(dep);
        }
#if !MINIMAL_RUNTIME
        if (Browser.handledByPreloadPlugin(byteArray, fullname, finish, () => {
          if (onerror) onerror();
          removeRunDependency(dep);
        })) {
          return;
        }
#endif
        finish(byteArray);
      }
      addRunDependency(dep);
      if (typeof url == 'string') {
        asyncLoad(url, (byteArray) => {
          processData(byteArray);
        }, onerror);
      } else {
        processData(url);
      }
    },
    getMode: (canRead, canWrite) => {
      var mode = 0;
      if (canRead) mode |= {{{ cDefine('S_IRUGO') }}} | {{{ cDefine('S_IXUGO') }}};
      if (canWrite) mode |= {{{ cDefine('S_IWUGO') }}};
      return mode;
    },
    createDataFile: (parent, name, data, canRead, canWrite, canOwn) => {
      // Data files must be cached until the file system itself has been initialized.
      var mode = FS.getMode(canRead, canWrite);
      var pathName = name ? parent + '/' + name : parent;
      wasmFS$preloadedFiles.push({pathName: pathName, fileData: data, mode: mode});
    },
    createPath: (parent, path, canRead, canWrite) => {
      // Cache file path directory names.
      wasmFS$preloadedDirs.push({parentPath: parent, childName: path});
    },
    readFile: (path, opts) => {
      opts = opts || {};
      opts.encoding = opts.encoding || 'binary';
      if (opts.encoding !== 'utf8' && opts.encoding !== 'binary') {
        throw new Error('Invalid encoding type "' + opts.encoding + '"');
      }

      var pathName = allocateUTF8(path);

      // Copy the file into a JS buffer on the heap.
      var buf = __wasmfs_read_file(pathName);
      // The integer length is returned in the first 8 bytes of the buffer.
      var length = {{{ makeGetValue('buf', '0', 'i64') }}};

      // Default return type is binary.
      // The buffer contents exist 8 bytes after the returned pointer.
      var ret = new Uint8Array(HEAPU8.subarray(buf + 8, buf + 8 + length));
      if (opts.encoding === 'utf8') {
        ret = UTF8ArrayToString(ret, 0);
      }

      _free(pathName);
      _free(buf);
      return ret;
    },
    cwd: () => {
      // TODO: Remove dependency on FS.cwd().
      // User code should not be using FS.cwd().
      // For file preloading, cwd should be '/' to begin with.
      return '/';
    },

#if FORCE_FILESYSTEM
    // Full JS API support
    mkdir: (path, mode) => {
      mode = mode !== undefined ? mode : 511 /* 0777 */;
      var buffer = allocateUTF8OnStack(path);
      __wasmfs_mkdir(buffer, mode);
    },
    chdir: (path) => {
      var buffer = allocateUTF8OnStack(path);
      return __wasmfs_chdir(buffer);
    },
    writeFile: (path, data) => {
      var pathBuffer = allocateUTF8OnStack(path);
      var dataBuffer = _malloc(data);
      __wasmfs_write_file(pathBuffer, dataBuffer, data.length);
      _free(dataBuffer);
    },
    symlink: (target, linkpath) => {
      var targetBuffer = allocateUTF8OnStack(target);
      var linkpathBuffer = allocateUTF8OnStack(linkpath);
      __wasmfs_symlink(targetBuffer, linkpathBuffer);
    },
    chmod: (path, mode) => {
      var buffer = allocateUTF8OnStack(path);
      return __wasmfs_chmod(buffer, mode);
    },
#endif
  },
  _wasmfs_get_num_preloaded_files__deps: ['$wasmFS$preloadedFiles'],
  _wasmfs_get_num_preloaded_files: function() {
    return wasmFS$preloadedFiles.length;
  },
  _wasmfs_get_num_preloaded_dirs__deps: ['$wasmFS$preloadedDirs'],
  _wasmfs_get_num_preloaded_dirs: function() {
    return wasmFS$preloadedDirs.length;
  },
  _wasmfs_get_preloaded_file_mode: function(index) {
    return wasmFS$preloadedFiles[index].mode;
  },
  _wasmfs_get_preloaded_parent_path: function(index, parentPathBuffer) {
    var s = wasmFS$preloadedDirs[index].parentPath;
    var len = lengthBytesUTF8(s) + 1;
    stringToUTF8(s, parentPathBuffer, len);
  },
  _wasmfs_get_preloaded_child_path: function(index, childNameBuffer) {
    var s = wasmFS$preloadedDirs[index].childName;
    var len = lengthBytesUTF8(s) + 1;
    stringToUTF8(s, childNameBuffer, len);
  },
  _wasmfs_get_preloaded_path_name: function(index, fileNameBuffer) {
    var s = wasmFS$preloadedFiles[index].pathName;
    var len = lengthBytesUTF8(s) + 1;
    stringToUTF8(s, fileNameBuffer, len);
  },
  _wasmfs_get_preloaded_file_size: function(index) {
    return wasmFS$preloadedFiles[index].fileData.length;
  },
  _wasmfs_copy_preloaded_file_data: function(index, buffer) {
    HEAPU8.set(wasmFS$preloadedFiles[index].fileData, buffer);
  },

  // Backend support. wasmFS$backends will contain a mapping of backend IDs to
  // the JS code that implements them. This is the JS side of the JSImpl* class
  // in C++, together with the js_impl calls defined right after it.
  $wasmFS$backends: {},

  // JSImpl

  _wasmfs_jsimpl_alloc_file: function(backend, file) {
#if ASSERTIONS
    assert(wasmFS$backends[backend]);
#endif
    return wasmFS$backends[backend].allocFile(file);
  },

  _wasmfs_jsimpl_free_file: function(backend, file) {
#if ASSERTIONS
    assert(wasmFS$backends[backend]);
#endif
    return wasmFS$backends[backend].freeFile(file);
  },

  _wasmfs_jsimpl_write: function(backend, file, buffer, length, {{{ defineI64Param('offset') }}}) {
    {{{ receiveI64ParamAsDouble('offset') }}}
#if ASSERTIONS
    assert(wasmFS$backends[backend]);
#endif
    return wasmFS$backends[backend].write(file, buffer, length, offset);
  },

  _wasmfs_jsimpl_read: function(backend, file, buffer, length, {{{ defineI64Param('offset') }}}) {
    {{{ receiveI64ParamAsDouble('offset') }}}
#if ASSERTIONS
    assert(wasmFS$backends[backend]);
#endif
    return wasmFS$backends[backend].read(file, buffer, length, offset);
  },

  _wasmfs_jsimpl_get_size: function(backend, file) {
#if ASSERTIONS
    assert(wasmFS$backends[backend]);
#endif
    return wasmFS$backends[backend].getSize(file);
  },

  // ProxiedAsyncJSImpl. Each function receives a function pointer and a
  // parameter. We convert those into a convenient Promise API for the
  // implementors of backends: the hooks we call should return Promises, which
  // we then connect to the calling C++.

  // TODO: arg is void*, which for MEMORY64 will be 64-bit. we need a way to
  //       declare arg in the function signature here (like defineI64Param,
  //       but that varies for wasm32/wasm64), and a way to do makeDynCall that
  //       adds a 'p' signature type for pointer, or something like that
  //       (however, dyncalls might also just work, given in MEMORY64 we assume
  //       WASM_BIGINT so the pointer is just a single argument, just like in
  //       wasm32).
  _wasmfs_jsimpl_async_alloc_file: async function(backend, file, fptr, arg) {
#if ASSERTIONS
    assert(wasmFS$backends[backend]);
#endif
    {{{ runtimeKeepalivePush() }}}
    await wasmFS$backends[backend].allocFile(file);
    {{{ runtimeKeepalivePop() }}}
    {{{ makeDynCall('vi', 'fptr') }}}(arg);
  },

  _wasmfs_jsimpl_async_free_file: async function(backend, file, fptr, arg) {
#if ASSERTIONS
    assert(wasmFS$backends[backend]);
#endif
    {{{ runtimeKeepalivePush() }}}
    await wasmFS$backends[backend].freeFile(file);
    {{{ runtimeKeepalivePop() }}}
    {{{ makeDynCall('vi', 'fptr') }}}(arg);
  },

  _wasmfs_jsimpl_async_write: async function(backend, file, buffer, length, {{{ defineI64Param('offset') }}}, fptr, arg) {
    {{{ receiveI64ParamAsDouble('offset') }}}
#if ASSERTIONS
    assert(wasmFS$backends[backend]);
#endif
    {{{ runtimeKeepalivePush() }}}
    var size = await wasmFS$backends[backend].write(file, buffer, length, offset);
    {{{ runtimeKeepalivePop() }}}
    {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}};
    {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}};
    {{{ makeDynCall('vi', 'fptr') }}}(arg);
  },

  _wasmfs_jsimpl_async_read: async function(backend, file, buffer, length, {{{ defineI64Param('offset') }}}, fptr, arg) {
    {{{ receiveI64ParamAsDouble('offset') }}}
#if ASSERTIONS
    assert(wasmFS$backends[backend]);
#endif
    {{{ runtimeKeepalivePush() }}}
    var size = await wasmFS$backends[backend].read(file, buffer, length, offset);
    {{{ runtimeKeepalivePop() }}}
    {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}};
    {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}};
    {{{ makeDynCall('vi', 'fptr') }}}(arg);
  },

  _wasmfs_jsimpl_async_get_size: async function(backend, file, fptr, arg) {
#if ASSERTIONS
    assert(wasmFS$backends[backend]);
#endif
    {{{ runtimeKeepalivePush() }}}
    var size = await wasmFS$backends[backend].getSize(file);
    {{{ runtimeKeepalivePop() }}}
    {{{ makeSetValue('arg', C_STRUCTS.CallbackState.result, '0', 'i32') }}};
    {{{ makeSetValue('arg', C_STRUCTS.CallbackState.offset, 'size', 'i64') }}};
    {{{ makeDynCall('vi', 'fptr') }}}(arg);
  },
}

mergeInto(LibraryManager.library, WasmFSLibrary);

DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.push('$FS');
