run(callback) {
		if (this.running) {
			return callback(new ConcurrentCompilationError());
		}

		/** @type {Logger | undefined} */
		let logger;

		/**
		 * @param {Error | null} err error
		 * @param {Stats=} stats stats
		 */
		const finalCallback = (err, stats) => {
			if (logger) logger.time("beginIdle");
			this.idle = true;
			this.cache.beginIdle();
			this.idle = true;
			if (logger) logger.timeEnd("beginIdle");
			this.running = false;
			if (err) {
				this.hooks.failed.call(err);
			}
			if (callback !== undefined) callback(err, stats);
			this.hooks.afterDone.call(/** @type {Stats} */ (stats));
		};

		const startTime = Date.now();

		this.running = true;

		/**
		 * @param {Error | null} err error
		 * @param {Compilation=} _compilation compilation
		 * @returns {void}
		 */
		const onCompiled = (err, _compilation) => {
			if (err) return finalCallback(err);

			const compilation = /** @type {Compilation} */ (_compilation);

			if (this.hooks.shouldEmit.call(compilation) === false) {
				compilation.startTime = startTime;
				compilation.endTime = Date.now();
				const stats = new Stats(compilation);
				this.hooks.done.callAsync(stats, err => {
					if (err) return finalCallback(err);
					return finalCallback(null, stats);
				});
				return;
			}

			process.nextTick(() => {
				logger = compilation.getLogger("webpack.Compiler");
				logger.time("emitAssets");
				this.emitAssets(compilation, err => {
					/** @type {Logger} */
					(logger).timeEnd("emitAssets");
					if (err) return finalCallback(err);

					if (compilation.hooks.needAdditionalPass.call()) {
						compilation.needAdditionalPass = true;

						compilation.startTime = startTime;
						compilation.endTime = Date.now();
						/** @type {Logger} */
						(logger).time("done hook");
						const stats = new Stats(compilation);
						this.hooks.done.callAsync(stats, err => {
							/** @type {Logger} */
							(logger).timeEnd("done hook");
							if (err) return finalCallback(err);

							this.hooks.additionalPass.callAsync(err => {
								if (err) return finalCallback(err);
								this.compile(onCompiled);
							});
						});
						return;
					}

					/** @type {Logger} */
					(logger).time("emitRecords");
					this.emitRecords(err => {
						/** @type {Logger} */
						(logger).timeEnd("emitRecords");
						if (err) return finalCallback(err);

						compilation.startTime = startTime;
						compilation.endTime = Date.now();
						/** @type {Logger} */
						(logger).time("done hook");
						const stats = new Stats(compilation);
						this.hooks.done.callAsync(stats, err => {
							/** @type {Logger} */
							(logger).timeEnd("done hook");
							if (err) return finalCallback(err);
							this.cache.storeBuildDependencies(
								compilation.buildDependencies,
								err => {
									if (err) return finalCallback(err);
									return finalCallback(null, stats);
								}
							);
						});
					});
				});
			});
		};

		const run = () => {
			this.hooks.beforeRun.callAsync(this, err => {
				if (err) return finalCallback(err);

				this.hooks.run.callAsync(this, err => {
					if (err) return finalCallback(err);

					this.readRecords(err => {
						if (err) return finalCallback(err);

						this.compile(onCompiled);
					});
				});
			});
		};

		if (this.idle) {
			this.cache.endIdle(err => {
				if (err) return finalCallback(err);

				this.idle = false;
				run();
			});
		} else {
			run();
		}
	}
@param {RunCallback<Stats>} callback signals when the call finishes
@returns {void}