import { assert } from "tsafe/assert";
import { is } from "tsafe/is";
import { deepClone } from "./deepClone";

//Warning: Be mindful that because of array this is not idempotent.
export function deepAssign(params: { target: Record<string, unknown>; source: Record<string, unknown> }) {
    const { target } = params;

    const source = deepClone(params.source);

    Object.keys(source).forEach(key => {
        var dereferencedSource = source[key];

        if (target[key] === undefined || dereferencedSource instanceof Function || !(dereferencedSource instanceof Object)) {
            Object.defineProperty(target, key, {
                "enumerable": true,
                "writable": true,
                "configurable": true,
                "value": dereferencedSource
            });

            return;
        }

        const dereferencedTarget = target[key];

        if (dereferencedSource instanceof Array) {
            assert(is<unknown[]>(dereferencedTarget));
            assert(is<unknown[]>(dereferencedSource));

            dereferencedSource.forEach(entry => dereferencedTarget.push(entry));

            return;
        }

        assert(is<Record<string, unknown>>(dereferencedTarget));
        assert(is<Record<string, unknown>>(dereferencedSource));

        deepAssign({
            "target": dereferencedTarget,
            "source": dereferencedSource
        });
    });
}
