createSnapshot(startTime, files, directories, missing, options, callback) {
		/** @type {FileTimestamps} */
		const fileTimestamps = new Map();
		/** @type {FileHashes} */
		const fileHashes = new Map();
		/** @type {FileTshs} */
		const fileTshs = new Map();
		/** @type {ContextTimestamps} */
		const contextTimestamps = new Map();
		/** @type {ContextHashes} */
		const contextHashes = new Map();
		/** @type {ContextTshs} */
		const contextTshs = new Map();
		/** @type {MissingExistence} */
		const missingExistence = new Map();
		/** @type {ManagedItemInfo} */
		const managedItemInfo = new Map();
		/** @type {ManagedFiles} */
		const managedFiles = new Set();
		/** @type {ManagedContexts} */
		const managedContexts = new Set();
		/** @type {ManagedMissing} */
		const managedMissing = new Set();
		/** @type {Children} */
		const children = new Set();

		const snapshot = new Snapshot();
		if (startTime) snapshot.setStartTime(startTime);

		/** @type {Set<string>} */
		const managedItems = new Set();

		/** 1 = timestamp, 2 = hash, 3 = timestamp + hash */
		const mode = options && options.hash ? (options.timestamp ? 3 : 2) : 1;

		let jobs = 1;
		const jobDone = () => {
			if (--jobs === 0) {
				if (fileTimestamps.size !== 0) {
					snapshot.setFileTimestamps(fileTimestamps);
				}
				if (fileHashes.size !== 0) {
					snapshot.setFileHashes(fileHashes);
				}
				if (fileTshs.size !== 0) {
					snapshot.setFileTshs(fileTshs);
				}
				if (contextTimestamps.size !== 0) {
					snapshot.setContextTimestamps(contextTimestamps);
				}
				if (contextHashes.size !== 0) {
					snapshot.setContextHashes(contextHashes);
				}
				if (contextTshs.size !== 0) {
					snapshot.setContextTshs(contextTshs);
				}
				if (missingExistence.size !== 0) {
					snapshot.setMissingExistence(missingExistence);
				}
				if (managedItemInfo.size !== 0) {
					snapshot.setManagedItemInfo(managedItemInfo);
				}
				this._managedFilesOptimization.optimize(snapshot, managedFiles);
				if (managedFiles.size !== 0) {
					snapshot.setManagedFiles(managedFiles);
				}
				this._managedContextsOptimization.optimize(snapshot, managedContexts);
				if (managedContexts.size !== 0) {
					snapshot.setManagedContexts(managedContexts);
				}
				this._managedMissingOptimization.optimize(snapshot, managedMissing);
				if (managedMissing.size !== 0) {
					snapshot.setManagedMissing(managedMissing);
				}
				if (children.size !== 0) {
					snapshot.setChildren(children);
				}
				this._snapshotCache.set(snapshot, true);
				this._statCreatedSnapshots++;

				callback(null, snapshot);
			}
		};
		const jobError = () => {
			if (jobs > 0) {
				// large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
				jobs = -100000000;
				callback(null, null);
			}
		};
		/**
		 * @param {string} path path
		 * @param {ManagedFiles} managedSet managed set
		 * @returns {boolean} true when managed
		 */
		const checkManaged = (path, managedSet) => {
			for (const unmanagedPath of this.unmanagedPathsRegExps) {
				if (unmanagedPath.test(path)) return false;
			}
			for (const unmanagedPath of this.unmanagedPathsWithSlash) {
				if (path.startsWith(unmanagedPath)) return false;
			}
			for (const immutablePath of this.immutablePathsRegExps) {
				if (immutablePath.test(path)) {
					managedSet.add(path);
					return true;
				}
			}
			for (const immutablePath of this.immutablePathsWithSlash) {
				if (path.startsWith(immutablePath)) {
					managedSet.add(path);
					return true;
				}
			}
			for (const managedPath of this.managedPathsRegExps) {
				const match = managedPath.exec(path);
				if (match) {
					const managedItem = getManagedItem(match[1], path);
					if (managedItem) {
						managedItems.add(managedItem);
						managedSet.add(path);
						return true;
					}
				}
			}
			for (const managedPath of this.managedPathsWithSlash) {
				if (path.startsWith(managedPath)) {
					const managedItem = getManagedItem(managedPath, path);
					if (managedItem) {
						managedItems.add(managedItem);
						managedSet.add(path);
						return true;
					}
				}
			}
			return false;
		};
		/**
		 * @param {Iterable<string>} items items
		 * @param {Set<string>} managedSet managed set
		 * @returns {Set<string>} result
		 */
		const captureNonManaged = (items, managedSet) => {
			/** @type {Set<string>} */
			const capturedItems = new Set();
			for (const path of items) {
				if (!checkManaged(path, managedSet)) capturedItems.add(path);
			}
			return capturedItems;
		};
		/**
		 * @param {ManagedFiles} capturedFiles captured files
		 */
		const processCapturedFiles = capturedFiles => {
			switch (mode) {
				case 3:
					this._fileTshsOptimization.optimize(snapshot, capturedFiles);
					for (const path of capturedFiles) {
						const cache = this._fileTshs.get(path);
						if (cache !== undefined) {
							fileTshs.set(path, cache);
						} else {
							jobs++;
							this._getFileTimestampAndHash(path, (err, entry) => {
								if (err) {
									if (this.logger) {
										this.logger.debug(
											`Error snapshotting file timestamp hash combination of ${path}: ${err.stack}`
										);
									}
									jobError();
								} else {
									fileTshs.set(path, /** @type {TimestampAndHash} */ (entry));
									jobDone();
								}
							});
						}
					}
					break;
				case 2:
					this._fileHashesOptimization.optimize(snapshot, capturedFiles);
					for (const path of capturedFiles) {
						const cache = this._fileHashes.get(path);
						if (cache !== undefined) {
							fileHashes.set(path, cache);
						} else {
							jobs++;
							this.fileHashQueue.add(path, (err, entry) => {
								if (err) {
									if (this.logger) {
										this.logger.debug(
											`Error snapshotting file hash of ${path}: ${err.stack}`
										);
									}
									jobError();
								} else {
									fileHashes.set(path, /** @type {string} */ (entry));
									jobDone();
								}
							});
						}
					}
					break;
				case 1:
					this._fileTimestampsOptimization.optimize(snapshot, capturedFiles);
					for (const path of capturedFiles) {
						const cache = this._fileTimestamps.get(path);
						if (cache !== undefined) {
							if (cache !== "ignore") {
								fileTimestamps.set(path, cache);
							}
						} else {
							jobs++;
							this.fileTimestampQueue.add(path, (err, entry) => {
								if (err) {
									if (this.logger) {
										this.logger.debug(
											`Error snapshotting file timestamp of ${path}: ${err.stack}`
										);
									}
									jobError();
								} else {
									fileTimestamps.set(
										path,
										/** @type {FileSystemInfoEntry} */
										(entry)
									);
									jobDone();
								}
							});
						}
					}
					break;
			}
		};
		if (files) {
			processCapturedFiles(captureNonManaged(files, managedFiles));
		}
		/**
		 * @param {ManagedContexts} capturedDirectories captured directories
		 */
		const processCapturedDirectories = capturedDirectories => {
			switch (mode) {
				case 3:
					this._contextTshsOptimization.optimize(snapshot, capturedDirectories);
					for (const path of capturedDirectories) {
						const cache = this._contextTshs.get(path);
						/** @type {ResolvedContextTimestampAndHash | null | undefined} */
						let resolved;
						if (
							cache !== undefined &&
							(resolved = getResolvedTimestamp(cache)) !== undefined
						) {
							contextTshs.set(path, resolved);
						} else {
							jobs++;
							/**
							 * @param {(WebpackError | null)=} err error
							 * @param {(ResolvedContextTimestampAndHash | null)=} entry entry
							 * @returns {void}
							 */
							const callback = (err, entry) => {
								if (err) {
									if (this.logger) {
										this.logger.debug(
											`Error snapshotting context timestamp hash combination of ${path}: ${err.stack}`
										);
									}
									jobError();
								} else {
									contextTshs.set(
										path,
										/** @type {ResolvedContextTimestampAndHash | null} */
										(entry)
									);
									jobDone();
								}
							};
							if (cache !== undefined) {
								this._resolveContextTsh(cache, callback);
							} else {
								this.getContextTsh(path, callback);
							}
						}
					}
					break;
				case 2:
					this._contextHashesOptimization.optimize(
						snapshot,
						capturedDirectories
					);
					for (const path of capturedDirectories) {
						const cache = this._contextHashes.get(path);
						let resolved;
						if (
							cache !== undefined &&
							(resolved = getResolvedHash(cache)) !== undefined
						) {
							contextHashes.set(path, resolved);
						} else {
							jobs++;
							/**
							 * @param {(WebpackError | null)=} err err
							 * @param {string=} entry entry
							 */
							const callback = (err, entry) => {
								if (err) {
									if (this.logger) {
										this.logger.debug(
											`Error snapshotting context hash of ${path}: ${err.stack}`
										);
									}
									jobError();
								} else {
									contextHashes.set(path, /** @type {string} */ (entry));
									jobDone();
								}
							};
							if (cache !== undefined) {
								this._resolveContextHash(cache, callback);
							} else {
								this.getContextHash(path, callback);
							}
						}
					}
					break;
				case 1:
					this._contextTimestampsOptimization.optimize(
						snapshot,
						capturedDirectories
					);
					for (const path of capturedDirectories) {
						const cache = this._contextTimestamps.get(path);
						if (cache === "ignore") continue;
						let resolved;
						if (
							cache !== undefined &&
							(resolved = getResolvedTimestamp(cache)) !== undefined
						) {
							contextTimestamps.set(path, resolved);
						} else {
							jobs++;
							/**
							 * @param {(Error | null)=} err error
							 * @param {FileTimestamp=} entry entry
							 * @returns {void}
							 */
							const callback = (err, entry) => {
								if (err) {
									if (this.logger) {
										this.logger.debug(
											`Error snapshotting context timestamp of ${path}: ${err.stack}`
										);
									}
									jobError();
								} else {
									contextTimestamps.set(
										path,
										/** @type {FileSystemInfoEntry | null} */
										(entry)
									);
									jobDone();
								}
							};
							if (cache !== undefined) {
								this._resolveContextTimestamp(
									/** @type {ContextFileSystemInfoEntry} */
									(cache),
									callback
								);
							} else {
								this.getContextTimestamp(path, callback);
							}
						}
					}
					break;
			}
		};
		if (directories) {
			processCapturedDirectories(
				captureNonManaged(directories, managedContexts)
			);
		}
		/**
		 * @param {ManagedMissing} capturedMissing captured missing
		 */
		const processCapturedMissing = capturedMissing => {
			this._missingExistenceOptimization.optimize(snapshot, capturedMissing);
			for (const path of capturedMissing) {
				const cache = this._fileTimestamps.get(path);
				if (cache !== undefined) {
					if (cache !== "ignore") {
						missingExistence.set(path, Boolean(cache));
					}
				} else {
					jobs++;
					this.fileTimestampQueue.add(path, (err, entry) => {
						if (err) {
							if (this.logger) {
								this.logger.debug(
									`Error snapshotting missing timestamp of ${path}: ${err.stack}`
								);
							}
							jobError();
						} else {
							missingExistence.set(path, Boolean(entry));
							jobDone();
						}
					});
				}
			}
		};
		if (missing) {
			processCapturedMissing(captureNonManaged(missing, managedMissing));
		}
		this._managedItemInfoOptimization.optimize(snapshot, managedItems);
		for (const path of managedItems) {
			const cache = this._managedItems.get(path);
			if (cache !== undefined) {
				if (!cache.startsWith("*")) {
					managedFiles.add(join(this.fs, path, "package.json"));
				} else if (cache === "*nested") {
					managedMissing.add(join(this.fs, path, "package.json"));
				}
				managedItemInfo.set(path, cache);
			} else {
				jobs++;
				this.managedItemQueue.add(path, (err, entry) => {
					if (err) {
						if (this.logger) {
							this.logger.debug(
								`Error snapshotting managed item ${path}: ${err.stack}`
							);
						}
						jobError();
					} else if (entry) {
						if (!entry.startsWith("*")) {
							managedFiles.add(join(this.fs, path, "package.json"));
						} else if (cache === "*nested") {
							managedMissing.add(join(this.fs, path, "package.json"));
						}
						managedItemInfo.set(path, entry);
						jobDone();
					} else {
						// Fallback to normal snapshotting
						/**
						 * @param {Set<string>} set set
						 * @param {(set: Set<string>) => void} fn fn
						 */
						const process = (set, fn) => {
							if (set.size === 0) return;
							const captured = new Set();
							for (const file of set) {
								if (file.startsWith(path)) captured.add(file);
							}
							if (captured.size > 0) fn(captured);
						};
						process(managedFiles, processCapturedFiles);
						process(managedContexts, processCapturedDirectories);
						process(managedMissing, processCapturedMissing);
						jobDone();
					}
				});
			}
		}
		jobDone();
	}
@param {number | null | undefined} startTime when processing the files has started
@param {Iterable<string> | null} files all files
@param {Iterable<string> | null} directories all directories
@param {Iterable<string> | null} missing all missing files or directories
@param {SnapshotOptions | null | undefined} options options object (for future extensions)
@param {(err: WebpackError | null, snapshot: Snapshot | null) => void} callback callback function
@returns {void}