getNormalizedWebpackOptions = config => ({
	amd: config.amd,
	bail: config.bail,
	cache:
		/** @type {NonNullable<CacheOptions>} */
		(
			optionalNestedConfig(config.cache, cache => {
				if (cache === false) return false;
				if (cache === true) {
					return {
						type: "memory",
						maxGenerations: undefined
					};
				}
				switch (cache.type) {
					case "filesystem":
						return {
							type: "filesystem",
							allowCollectingMemory: cache.allowCollectingMemory,
							maxMemoryGenerations: cache.maxMemoryGenerations,
							maxAge: cache.maxAge,
							profile: cache.profile,
							buildDependencies: cloneObject(cache.buildDependencies),
							cacheDirectory: cache.cacheDirectory,
							cacheLocation: cache.cacheLocation,
							hashAlgorithm: cache.hashAlgorithm,
							compression: cache.compression,
							idleTimeout: cache.idleTimeout,
							idleTimeoutForInitialStore: cache.idleTimeoutForInitialStore,
							idleTimeoutAfterLargeChanges: cache.idleTimeoutAfterLargeChanges,
							name: cache.name,
							store: cache.store,
							version: cache.version,
							readonly: cache.readonly
						};
					case undefined:
					case "memory":
						return {
							type: "memory",
							maxGenerations: cache.maxGenerations
						};
					default:
						// @ts-expect-error Property 'type' does not exist on type 'never'. ts(2339)
						throw new Error(`Not implemented cache.type ${cache.type}`);
				}
			})
		),
	context: config.context,
	dependencies: config.dependencies,
	devServer: optionalNestedConfig(config.devServer, devServer => {
		if (devServer === false) return false;
		return { ...devServer };
	}),
	devtool: config.devtool,
	entry:
		config.entry === undefined
			? { main: {} }
			: typeof config.entry === "function"
				? (
						fn => () =>
							Promise.resolve().then(fn).then(getNormalizedEntryStatic)
					)(config.entry)
				: getNormalizedEntryStatic(config.entry),
	experiments: nestedConfig(config.experiments, experiments => ({
		...experiments,
		buildHttp: optionalNestedConfig(experiments.buildHttp, options =>
			Array.isArray(options) ? { allowedUris: options } : options
		),
		lazyCompilation: optionalNestedConfig(
			experiments.lazyCompilation,
			options => (options === true ? {} : options)
		)
	})),
	externals: /** @type {NonNullable<Externals>} */ (config.externals),
	externalsPresets: cloneObject(config.externalsPresets),
	externalsType: config.externalsType,
	ignoreWarnings: config.ignoreWarnings
		? config.ignoreWarnings.map(ignore => {
				if (typeof ignore === "function") return ignore;
				const i = ignore instanceof RegExp ? { message: ignore } : ignore;
				return (warning, { requestShortener }) => {
					if (!i.message && !i.module && !i.file) return false;
					if (i.message && !i.message.test(warning.message)) {
						return false;
					}
					if (
						i.module &&
						(!(/** @type {WebpackError} */ (warning).module) ||
							!i.module.test(
								/** @type {WebpackError} */
								(warning).module.readableIdentifier(requestShortener)
							))
					) {
						return false;
					}
					if (
						i.file &&
						(!(/** @type {WebpackError} */ (warning).file) ||
							!i.file.test(/** @type {WebpackError} */ (warning).file))
					) {
						return false;
					}
					return true;
				};
			})
		: undefined,
	infrastructureLogging: cloneObject(config.infrastructureLogging),
	loader: cloneObject(config.loader),
	mode: config.mode,
	module:
		/** @type {ModuleOptionsNormalized} */
		(
			nestedConfig(config.module, module => ({
				noParse: module.noParse,
				unsafeCache: module.unsafeCache,
				parser: keyedNestedConfig(module.parser, cloneObject, {
					javascript: parserOptions => ({
						unknownContextRequest: module.unknownContextRequest,
						unknownContextRegExp: module.unknownContextRegExp,
						unknownContextRecursive: module.unknownContextRecursive,
						unknownContextCritical: module.unknownContextCritical,
						exprContextRequest: module.exprContextRequest,
						exprContextRegExp: module.exprContextRegExp,
						exprContextRecursive: module.exprContextRecursive,
						exprContextCritical: module.exprContextCritical,
						wrappedContextRegExp: module.wrappedContextRegExp,
						wrappedContextRecursive: module.wrappedContextRecursive,
						wrappedContextCritical: module.wrappedContextCritical,
						// TODO webpack 6 remove
						strictExportPresence: module.strictExportPresence,
						strictThisContextOnImports: module.strictThisContextOnImports,
						...parserOptions
					})
				}),
				generator: cloneObject(module.generator),
				defaultRules: optionalNestedArray(module.defaultRules, r => [...r]),
				rules: nestedArray(module.rules, r => [...r])
			}))
		),
	name: config.name,
	node: nestedConfig(
		config.node,
		node =>
			node && {
				...node
			}
	),
	optimization: nestedConfig(config.optimization, optimization => ({
		...optimization,
		runtimeChunk: getNormalizedOptimizationRuntimeChunk(
			optimization.runtimeChunk
		),
		splitChunks: nestedConfig(
			optimization.splitChunks,
			splitChunks =>
				splitChunks && {
					...splitChunks,
					defaultSizeTypes: splitChunks.defaultSizeTypes
						? [...splitChunks.defaultSizeTypes]
						: ["..."],
					cacheGroups: cloneObject(splitChunks.cacheGroups)
				}
		),
		emitOnErrors:
			optimization.noEmitOnErrors !== undefined
				? handledDeprecatedNoEmitOnErrors(
						optimization.noEmitOnErrors,
						optimization.emitOnErrors
					)
				: optimization.emitOnErrors
	})),
	output: nestedConfig(config.output, output => {
		const { library } = output;
		const libraryAsName = /** @type {LibraryName} */ (library);
		const libraryBase =
			typeof library === "object" &&
			library &&
			!Array.isArray(library) &&
			"type" in library
				? library
				: libraryAsName || output.libraryTarget
					? /** @type {LibraryOptions} */ ({
							name: libraryAsName
						})
					: undefined;
		/** @type {OutputNormalized} */
		const result = {
			assetModuleFilename: output.assetModuleFilename,
			asyncChunks: output.asyncChunks,
			charset: output.charset,
			chunkFilename: output.chunkFilename,
			chunkFormat: output.chunkFormat,
			chunkLoading: output.chunkLoading,
			chunkLoadingGlobal: output.chunkLoadingGlobal,
			chunkLoadTimeout: output.chunkLoadTimeout,
			cssFilename: output.cssFilename,
			cssChunkFilename: output.cssChunkFilename,
			clean: output.clean,
			compareBeforeEmit: output.compareBeforeEmit,
			crossOriginLoading: output.crossOriginLoading,
			devtoolFallbackModuleFilenameTemplate:
				output.devtoolFallbackModuleFilenameTemplate,
			devtoolModuleFilenameTemplate: output.devtoolModuleFilenameTemplate,
			devtoolNamespace: output.devtoolNamespace,
			environment: cloneObject(output.environment),
			enabledChunkLoadingTypes: output.enabledChunkLoadingTypes
				? [...output.enabledChunkLoadingTypes]
				: ["..."],
			enabledLibraryTypes: output.enabledLibraryTypes
				? [...output.enabledLibraryTypes]
				: ["..."],
			enabledWasmLoadingTypes: output.enabledWasmLoadingTypes
				? [...output.enabledWasmLoadingTypes]
				: ["..."],
			filename: output.filename,
			globalObject: output.globalObject,
			hashDigest: output.hashDigest,
			hashDigestLength: output.hashDigestLength,
			hashFunction: output.hashFunction,
			hashSalt: output.hashSalt,
			hotUpdateChunkFilename: output.hotUpdateChunkFilename,
			hotUpdateGlobal: output.hotUpdateGlobal,
			hotUpdateMainFilename: output.hotUpdateMainFilename,
			ignoreBrowserWarnings: output.ignoreBrowserWarnings,
			iife: output.iife,
			importFunctionName: output.importFunctionName,
			importMetaName: output.importMetaName,
			scriptType: output.scriptType,
			// TODO webpack6 remove `libraryTarget`/`auxiliaryComment`/`amdContainer`/etc in favor of the `library` option
			library: libraryBase && {
				type:
					output.libraryTarget !== undefined
						? output.libraryTarget
						: libraryBase.type,
				auxiliaryComment:
					output.auxiliaryComment !== undefined
						? output.auxiliaryComment
						: libraryBase.auxiliaryComment,
				amdContainer:
					output.amdContainer !== undefined
						? output.amdContainer
						: libraryBase.amdContainer,
				export:
					output.libraryExport !== undefined
						? output.libraryExport
						: libraryBase.export,
				name: libraryBase.name,
				umdNamedDefine:
					output.umdNamedDefine !== undefined
						? output.umdNamedDefine
						: libraryBase.umdNamedDefine
			},
			module: output.module,
			path: output.path,
			pathinfo: output.pathinfo,
			publicPath: output.publicPath,
			sourceMapFilename: output.sourceMapFilename,
			sourcePrefix: output.sourcePrefix,
			strictModuleErrorHandling: output.strictModuleErrorHandling,
			strictModuleExceptionHandling: output.strictModuleExceptionHandling,
			trustedTypes: optionalNestedConfig(output.trustedTypes, trustedTypes => {
				if (trustedTypes === true) return {};
				if (typeof trustedTypes === "string")
					return { policyName: trustedTypes };
				return { ...trustedTypes };
			}),
			uniqueName: output.uniqueName,
			wasmLoading: output.wasmLoading,
			webassemblyModuleFilename: output.webassemblyModuleFilename,
			workerPublicPath: output.workerPublicPath,
			workerChunkLoading: output.workerChunkLoading,
			workerWasmLoading: output.workerWasmLoading
		};
		return result;
	}),
	parallelism: config.parallelism,
	performance: optionalNestedConfig(config.performance, performance => {
		if (performance === false) return false;
		return {
			...performance
		};
	}),
	plugins: /** @type {Plugins} */ (nestedArray(config.plugins, p => [...p])),
	profile: config.profile,
	recordsInputPath:
		config.recordsInputPath !== undefined
			? config.recordsInputPath
			: config.recordsPath,
	recordsOutputPath:
		config.recordsOutputPath !== undefined
			? config.recordsOutputPath
			: config.recordsPath,
	resolve: nestedConfig(config.resolve, resolve => ({
		...resolve,
		byDependency: keyedNestedConfig(resolve.byDependency, cloneObject)
	})),
	resolveLoader: cloneObject(config.resolveLoader),
	snapshot: nestedConfig(config.snapshot, snapshot => ({
		resolveBuildDependencies: optionalNestedConfig(
			snapshot.resolveBuildDependencies,
			resolveBuildDependencies => ({
				timestamp: resolveBuildDependencies.timestamp,
				hash: resolveBuildDependencies.hash
			})
		),
		buildDependencies: optionalNestedConfig(
			snapshot.buildDependencies,
			buildDependencies => ({
				timestamp: buildDependencies.timestamp,
				hash: buildDependencies.hash
			})
		),
		resolve: optionalNestedConfig(snapshot.resolve, resolve => ({
			timestamp: resolve.timestamp,
			hash: resolve.hash
		})),
		module: optionalNestedConfig(snapshot.module, module => ({
			timestamp: module.timestamp,
			hash: module.hash
		})),
		immutablePaths: optionalNestedArray(snapshot.immutablePaths, p => [...p]),
		managedPaths: optionalNestedArray(snapshot.managedPaths, p => [...p]),
		unmanagedPaths: optionalNestedArray(snapshot.unmanagedPaths, p => [...p])
	})),
	stats: nestedConfig(config.stats, stats => {
		if (stats === false) {
			return {
				preset: "none"
			};
		}
		if (stats === true) {
			return {
				preset: "normal"
			};
		}
		if (typeof stats === "string") {
			return {
				preset: stats
			};
		}
		return {
			...stats
		};
	}),
	target: config.target,
	watch: config.watch,
	watchOptions: cloneObject(config.watchOptions)
})
@param {WebpackOptions} config input config
@returns {WebpackOptionsNormalized} normalized options