/**
 *
 * @param {Object} source
 * @returns {Object}
 */
function structural_clone(source) {
    const result = Object.create(Object.getPrototypeOf(source));

    const ownKeys = Reflect.ownKeys(source);

    for (let i = 0; i < ownKeys.length; i++) {
        const ownKey = ownKeys[i];

        result[ownKey] = object_deep_clone(source[ownKey]);
    }

    return result;
}

/**
 * Will invoke any "clone" methods and inherit prototypes where possible creating as close to identical copy as possible
 * @template T
 * @param {T} source
 * @returns {T}
 */
export function object_deep_clone(source) {

    const source_type = typeof source;
    if (source_type === "object") {
        if (source !== null) {
            if (typeof source.clone === "function") {
                // has clone method
                return source.clone();
            } else {
                // do structural cloning
                return structural_clone(source);
            }
        } else {
            // value is null
            return source;
        }
    } else {
        // not an object, assume primitive type and just return that
        return source;
    }

}