/**
 * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
 * Node module: @schukai/monster
 *
 * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
 * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
 *
 * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
 * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
 * For more information about purchasing a commercial license, please contact Volker Schukai.
 *
 * SPDX-License-Identifier: AGPL-3.0
 */

import { isArray, isObject } from "../types/is.mjs";
import { typeOf } from "../types/typeof.mjs";

export { extend };

/**
 * Extend copies all enumerable own properties from one or
 * more source objects to a target object. It returns the modified target object.
 *
 * @return {object}
 * @license AGPLv3
 * @since 1.10.0
 * @copyright Volker Schukai
 * @throws {Error} unsupported argument
 * @throws {Error} type mismatch
 * @throws {Error} unsupported argument
 * @param args
 */
function extend(...args) {
	let o;
	let i;

	if (typeof args !== "object" || args[0] === null) {
		throw new Error(`unsupported argument ${JSON.stringify(args[0])}`);
	}

	for (i = 0; i < args.length; i++) {
		const a = args[i];

		if (!(isObject(a) || isArray(a))) {
			throw new Error(`unsupported argument ${JSON.stringify(a)}`);
		}

		if (o === undefined) {
			o = a;
			continue;
		}

		for (const k in a) {
			const v = a?.[k];

			if (k in o && v === o?.[k]) {
				// k in o is for the case of undefined
				continue;
			}

			if ((isObject(v) && typeOf(v) === "object") || isArray(v)) {
				if (o[k] === undefined) {
					if (isArray(v)) {
						o[k] = [];
					} else {
						o[k] = {};
					}
				} else {
					if (typeOf(o[k]) !== typeOf(v)) {
						throw new Error(
							`type mismatch: ${JSON.stringify(o[k])}(${typeOf(
								o[k],
							)}) != ${JSON.stringify(v)}(${typeOf(v)})`,
						);
					}
				}

				if (isArray(o[k])) {
					o[k] = [];
					o[k].push(...v);
					continue;
				}

				o[k] = extend(o[k], v);
			} else {
				if (isArray(o)) {
					o.push(v);
					continue;
				}
				o[k] = v;
			}
		}
	}

	return o;
}
