constructor(
		fs,
		{
			unmanagedPaths = [],
			managedPaths = [],
			immutablePaths = [],
			logger,
			hashFunction = DEFAULTS.HASH_FUNCTION
		} = {}
	) {
		this.fs = fs;
		this.logger = logger;
		this._remainingLogs = logger ? 40 : 0;
		/** @type {LoggedPaths | undefined} */
		this._loggedPaths = logger ? new Set() : undefined;
		this._hashFunction = hashFunction;
		/** @type {WeakMap<Snapshot, boolean | CheckSnapshotValidCallback[]>} */
		this._snapshotCache = new WeakMap();
		this._fileTimestampsOptimization = new SnapshotOptimization(
			s => s.hasFileTimestamps(),
			s => s.fileTimestamps,
			(s, v) => s.setFileTimestamps(v)
		);
		this._fileHashesOptimization = new SnapshotOptimization(
			s => s.hasFileHashes(),
			s => s.fileHashes,
			(s, v) => s.setFileHashes(v),
			false
		);
		this._fileTshsOptimization = new SnapshotOptimization(
			s => s.hasFileTshs(),
			s => s.fileTshs,
			(s, v) => s.setFileTshs(v)
		);
		this._contextTimestampsOptimization = new SnapshotOptimization(
			s => s.hasContextTimestamps(),
			s => s.contextTimestamps,
			(s, v) => s.setContextTimestamps(v)
		);
		this._contextHashesOptimization = new SnapshotOptimization(
			s => s.hasContextHashes(),
			s => s.contextHashes,
			(s, v) => s.setContextHashes(v),
			false
		);
		this._contextTshsOptimization = new SnapshotOptimization(
			s => s.hasContextTshs(),
			s => s.contextTshs,
			(s, v) => s.setContextTshs(v)
		);
		this._missingExistenceOptimization = new SnapshotOptimization(
			s => s.hasMissingExistence(),
			s => s.missingExistence,
			(s, v) => s.setMissingExistence(v),
			false
		);
		this._managedItemInfoOptimization = new SnapshotOptimization(
			s => s.hasManagedItemInfo(),
			s => s.managedItemInfo,
			(s, v) => s.setManagedItemInfo(v),
			false
		);
		this._managedFilesOptimization = new SnapshotOptimization(
			s => s.hasManagedFiles(),
			s => s.managedFiles,
			(s, v) => s.setManagedFiles(v),
			false,
			true
		);
		this._managedContextsOptimization = new SnapshotOptimization(
			s => s.hasManagedContexts(),
			s => s.managedContexts,
			(s, v) => s.setManagedContexts(v),
			false,
			true
		);
		this._managedMissingOptimization = new SnapshotOptimization(
			s => s.hasManagedMissing(),
			s => s.managedMissing,
			(s, v) => s.setManagedMissing(v),
			false,
			true
		);
		/** @type {StackedCacheMap<string, FileTimestamp>} */
		this._fileTimestamps = new StackedCacheMap();
		/** @type {Map<string, string | null>} */
		this._fileHashes = new Map();
		/** @type {Map<string, TimestampAndHash | string>} */
		this._fileTshs = new Map();
		/** @type {StackedCacheMap<string, ContextTimestamp>} */
		this._contextTimestamps = new StackedCacheMap();
		/** @type {Map<string, ContextHash>} */
		this._contextHashes = new Map();
		/** @type {Map<string, ContextTimestampAndHash>} */
		this._contextTshs = new Map();
		/** @type {Map<string, string>} */
		this._managedItems = new Map();
		/** @type {AsyncQueue<string, string, FileSystemInfoEntry>} */
		this.fileTimestampQueue = new AsyncQueue({
			name: "file timestamp",
			parallelism: 30,
			processor: this._readFileTimestamp.bind(this)
		});
		/** @type {AsyncQueue<string, string, string>} */
		this.fileHashQueue = new AsyncQueue({
			name: "file hash",
			parallelism: 10,
			processor: this._readFileHash.bind(this)
		});
		/** @type {AsyncQueue<string, string, ContextFileSystemInfoEntry>} */
		this.contextTimestampQueue = new AsyncQueue({
			name: "context timestamp",
			parallelism: 2,
			processor: this._readContextTimestamp.bind(this)
		});
		/** @type {AsyncQueue<string, string, ContextHash>} */
		this.contextHashQueue = new AsyncQueue({
			name: "context hash",
			parallelism: 2,
			processor: this._readContextHash.bind(this)
		});
		/** @type {AsyncQueue<string, string, ContextTimestampAndHash>} */
		this.contextTshQueue = new AsyncQueue({
			name: "context hash and timestamp",
			parallelism: 2,
			processor: this._readContextTimestampAndHash.bind(this)
		});
		/** @type {AsyncQueue<string, string, string>} */
		this.managedItemQueue = new AsyncQueue({
			name: "managed item info",
			parallelism: 10,
			processor: this._getManagedItemInfo.bind(this)
		});
		/** @type {AsyncQueue<string, string, Set<string>>} */
		this.managedItemDirectoryQueue = new AsyncQueue({
			name: "managed item directory info",
			parallelism: 10,
			processor: this._getManagedItemDirectoryInfo.bind(this)
		});
		const _unmanagedPaths = Array.from(unmanagedPaths);
		this.unmanagedPathsWithSlash =
			/** @type {string[]} */
			(_unmanagedPaths.filter(p => typeof p === "string")).map(p =>
				join(fs, p, "_").slice(0, -1)
			);
		this.unmanagedPathsRegExps =
			/** @type {RegExp[]} */
			(_unmanagedPaths.filter(p => typeof p !== "string"));

		this.managedPaths = Array.from(managedPaths);
		this.managedPathsWithSlash =
			/** @type {string[]} */
			(this.managedPaths.filter(p => typeof p === "string")).map(p =>
				join(fs, p, "_").slice(0, -1)
			);

		this.managedPathsRegExps =
			/** @type {RegExp[]} */
			(this.managedPaths.filter(p => typeof p !== "string"));
		this.immutablePaths = Array.from(immutablePaths);
		this.immutablePathsWithSlash =
			/** @type {string[]} */
			(this.immutablePaths.filter(p => typeof p === "string")).map(p =>
				join(fs, p, "_").slice(0, -1)
			);
		this.immutablePathsRegExps =
			/** @type {RegExp[]} */
			(this.immutablePaths.filter(p => typeof p !== "string"));

		this._cachedDeprecatedFileTimestamps = undefined;
		this._cachedDeprecatedContextTimestamps = undefined;

		this._warnAboutExperimentalEsmTracking = false;

		this._statCreatedSnapshots = 0;
		this._statTestedSnapshotsCached = 0;
		this._statTestedSnapshotsNotCached = 0;
		this._statTestedChildrenCached = 0;
		this._statTestedChildrenNotCached = 0;
		this._statTestedEntries = 0;
	}
@param {InputFileSystem} fs file system
@param {object} options options
@param {Iterable<string | RegExp>=} options.unmanagedPaths paths that are not managed by a package manager and the contents are subject to change
@param {Iterable<string | RegExp>=} options.managedPaths paths that are only managed by a package manager
@param {Iterable<string | RegExp>=} options.immutablePaths paths that are immutable
@param {Logger=} options.logger logger used to log invalid snapshots
@param {HashFunction=} options.hashFunction the hash function to use