type ConfigMergeType = 'merge' | 'overwrite';
type JsonObject = { [key: string]: any };

export function mergeJson(
	first: JsonObject,
	second: JsonObject,
	mergeBehavior?: ConfigMergeType,
	mergeKeys?: { [key: string]: (a: any, b: any) => boolean },
): any {
	const copyOfFirst = JSON.parse(JSON.stringify(first));

	try {
		for (var key in second) {
			let secondValue = second[key];

			if (!(key in copyOfFirst) || mergeBehavior === 'overwrite') {
				// New value
				copyOfFirst[key] = secondValue;
				continue;
			}

			const firstValue = copyOfFirst[key];
			if (Array.isArray(secondValue) && Array.isArray(firstValue)) {
				// Array
				if (mergeKeys?.[key]) {
					// Merge keys are used to determine whether an item form the second object should override one from the first
					let keptFromFirst: any[] = [];
					firstValue.forEach((item: any) => {
						if (!secondValue.some((item2: any) => mergeKeys[key](item, item2))) {
							keptFromFirst.push(item);
						}
					});
					copyOfFirst[key] = [...keptFromFirst, ...secondValue];
				} else {
					copyOfFirst[key] = [...firstValue, ...secondValue];
				}
			} else if (typeof secondValue === 'object' && typeof firstValue === 'object') {
				// Object
				copyOfFirst[key] = mergeJson(firstValue, secondValue, mergeBehavior);
			} else {
				// Other (boolean, number, string)
				copyOfFirst[key] = secondValue;
			}
		}
		return copyOfFirst;
	} catch (e) {
		console.error('Error merging JSON', e, copyOfFirst, second);
		return {
			...copyOfFirst,
			...second,
		};
	}
}

export default mergeJson;
