Source: vsa/intersects.js

/**
 * @module {Object} intersects
 * @description Intersects for Typed Maps and Types
 */
const convertTypedMapToTypedJson = require('./convertTypedMapToTypedJson');
const isType = require('../util/isType');
const str = require('../util/str');

/**
 * Intersect two Typed Maps based on Keys and Types
 *
 * @param {Map} typedMap1 - Map to Intersect
 * @param {Map} typedMap2 - Map to Intersect
 * @returns {Map} Intersected Map
 * @throws Throws an error if error occurred when intersecting Typed Maps
 */
function intersectTypedMaps(typedMap1, typedMap2) {
    try {
        const keys = new Set([...typedMap1.keys(), ...typedMap2.keys()]);
        const intersectedTypeMap = new Map();
        for (const key of keys) {
            const inT1 = typedMap1.get(key);
            const inT2 = typedMap2.get(key);
            if (inT1 !== undefined && inT2 !== undefined) {
                const t1Prefix = inT1.prefix.join('.');
                const t2Prefix = inT2.prefix.join('.');
                if (t1Prefix !== t2Prefix) {
                    throw new Error(`Prefixes "${t1Prefix}" and "${t2Prefix}" for Key "${key}" do not match`);
                }
                const type = intersectTypes(inT1.type, inT2.type);
                if (!isType.isNull(type)) {
                    intersectedTypeMap.set(key, {
                        prefix: inT1.prefix,
                        type
                    });
                }
            }
        }
        return intersectedTypeMap;
    } catch (error) {
        throw new Error(
            `${error} when intersecting Typed Maps:\n` +
            `Typed Map 1:\n` +
            `${str(convertTypedMapToTypedJson(typedMap1))}\n` +
            `Typed Map 2:\n` +
            `${str(convertTypedMapToTypedJson(typedMap2))}`
        );
    }
}

/**
 * Intersect two Types based on Types
 *
 * @param {Object} type1 - Type to Intersect (array, object, types)
 * @param {Object} type2 - Type to Intersect (array, object, types)
 * @returns {(Object|null)} Intersected Type if not null
 */
function intersectTypes(type1, type2) {

    const intersectedType = {
        array: null,
        object: null,
        types: null
    };

    // array
    if (!isType.isNull(type1.array) && !isType.isNull(type2.array)) {
        intersectedType.array = intersectTypes(type1.array, type2.array);
    }

    // object
    if (!isType.isNull(type1.object) && !isType.isNull(type2.object)) {
        intersectedType.object = intersectTypedMaps(type1.object, type2.object);
        if (intersectedType.object.size === 0) {
            intersectedType.object = null;
        }
    }

    // types
    if (!isType.isNull(type1.types) && !isType.isNull(type2.types)) {
        intersectedType.types = new Set();
        for (const type of Array.from(new Set([...type1.types, ...type2.types]))) {
            if (type1.types.has(type) && type2.types.has(type)) {
                intersectedType.types.add(type);
            }
        }
        if (intersectedType.types.size === 0) {
            intersectedType.types = null;
        }
    }

    // mutually exclusive
    if (isType.isNull(intersectedType.array) &&
        isType.isNull(intersectedType.object) &&
        isType.isNull(intersectedType.types)) {
        return null;
    }

    return intersectedType;
}

module.exports = {
    intersectTypedMaps,
    intersectTypes
};