mergeSingleValue = (a, b, internalCaching) => {
	const bType = getValueType(b);
	const aType = getValueType(a);
	switch (bType) {
		case VALUE_TYPE_DELETE:
		case VALUE_TYPE_ATOM:
			return b;
		case VALUE_TYPE_OBJECT: {
			return aType !== VALUE_TYPE_OBJECT
				? b
				: internalCaching
					? cachedCleverMerge(a, b)
					: cleverMerge(a, b);
		}
		case VALUE_TYPE_UNDEFINED:
			return a;
		case VALUE_TYPE_ARRAY_EXTEND:
			switch (
				aType !== VALUE_TYPE_ATOM
					? aType
					: Array.isArray(a)
						? VALUE_TYPE_ARRAY_EXTEND
						: VALUE_TYPE_OBJECT
			) {
				case VALUE_TYPE_UNDEFINED:
					return b;
				case VALUE_TYPE_DELETE:
					return /** @type {B[]} */ (b).filter(item => item !== "...");
				case VALUE_TYPE_ARRAY_EXTEND: {
					/** @type {(A | B)[]} */
					const newArray = [];
					for (const item of /** @type {B[]} */ (b)) {
						if (item === "...") {
							for (const item of /** @type {A[]} */ (a)) {
								newArray.push(item);
							}
						} else {
							newArray.push(item);
						}
					}
					return newArray;
				}
				case VALUE_TYPE_OBJECT:
					return /** @type {(A | B)[]} */ (b).map(item =>
						item === "..." ? /** @type {A} */ (a) : item
					);
				default:
					throw new Error("Not implemented");
			}
		default:
			throw new Error("Not implemented");
	}
}
@template A
@template B
@param {A | A[]} a value
@param {B | B[]} b value
@param {boolean} internalCaching should parsing of objects and nested merges be cached
@returns {A & B | (A | B)[] | A | A[] | B | B[]} value