# Source: data/undercomplicate/joins.js

    /**
     * Very simple client-side data joins. Useful for aligning records from two datasets based on a common key.
     */
    import { clone } from './util';

    /**
     * Simple grouping function, used to identify sets of records for joining.
     *
     * Used internally by join helpers, exported mainly for unit testing
     * @memberOf module:undercomplicate
     * @param {object[]} records
     * @param {string} group_key
     * @returns {Map<any, any>}
     */
    function groupBy(records, group_key) {
        const result = new Map();
        for (let item of records) {
            const item_group = item[group_key];

            if (typeof item_group === 'undefined') {
                throw new Error(`All records must specify a value for the field "${group_key}"`);
            }
            if (typeof item_group === 'object') {
                // If we can't group this item, then don't (exclude object, array, map, null, etc from grouping keys)
                throw new Error('Attempted to group on a field with non-primitive values');
            }

            let group = result.get(item_group);
            if (!group) {
                group = [];
                result.set(item_group, group);
            }
            group.push(item);
        }
        return result;
    }

    function _any_match(type, left, right, left_key, right_key) {
        // Helper that consolidates logic for all three join types
        const right_index = groupBy(right, right_key);
        const results = [];
        for (let item of left) {
            const left_match_value = item[left_key];
            const right_matches = right_index.get(left_match_value) || [];
            if (right_matches.length) {
                // Record appears on both left and right; equiv to an inner join
                results.push(...right_matches.map((right_item) => Object.assign({}, clone(right_item), clone(item))));
            } else if (type !== 'inner') {
                // Record appears on left but not right
                results.push(clone(item));
            }
        }

        if (type === 'outer') {
            // Outer join part! We've already added all left-only and left-right matches; all that's left is the items that only appear on right side
            const left_index = groupBy(left, left_key);
            for (let item of right) {
                const right_match_value = item[right_key];
                const left_matches = left_index.get(right_match_value) || [];
                if (!left_matches.length) {
                    results.push(clone(item));
                }
            }
        }
        return results;
    }

    /**
     * Equivalent to LEFT OUTER JOIN in SQL. Return all left records, joined to matching right data where appropriate.
     * @memberOf module:undercomplicate
     * @param {Object[]} left The left side recordset
     * @param {Object[]} right The right side recordset
     * @param {string} left_key The join field in left records
     * @param {string} right_key The join field in right records
     * @returns {Object[]}
     */
    function left_match(left, right, left_key, right_key) {
        return _any_match('left', ...arguments);
    }

    /**
     * Equivalent to INNER JOIN in SQL. Only return record joins if the key field has a match on both left and right.
     * @memberOf module:undercomplicate
     * @param {object[]} left The left side recordset
     * @param {object[]} right The right side recordset
     * @param {string} left_key The join field in left records
     * @param {string} right_key The join field in right records
     * @returns {Object[]}
     */
    function inner_match(left, right, left_key, right_key) {
        return _any_match('inner', ...arguments);
    }

    /**
     * Equivalent to FULL OUTER JOIN in SQL. Return records in either recordset, joined where appropriate.
     * @memberOf module:undercomplicate
     * @param {object[]} left The left side recordset
     * @param  {object[]} right The right side recordset
     * @param {string} left_key The join field in left records
     * @param {string} right_key The join field in right records
     * @returns {Object[]}
     */
    function full_outer_match(left, right, left_key, right_key) {
        return _any_match('outer', ...arguments);
    }

    export {left_match, inner_match, full_outer_match, groupBy};