import { groupBy, mapObjIndexed } from "ramda";

export class IsoObjectUtils {
  static fullOuterJoin<K extends string, V>(
    left: Record<K, V | undefined>,
    right: Record<K, V | undefined>,
    reduce: (left: V, right: V) => V
  ): Record<K, V> {
    type VOpt = V | undefined;
    type Entry = [K, V];
    const isDefined = (entry: [string, VOpt]): entry is Entry => entry[1] !== undefined;
    const getEntries = (record: Record<K, VOpt>): Entry[] => Object.entries<VOpt>(record).filter(isDefined);
    const entries: Entry[] = [...getEntries(left), ...getEntries(right)];
    const entriesByKey: Record<K, Entry[]> = groupBy<Entry, K>(([k, _]) => k, entries);

    return mapObjIndexed(x => {
      if (x.length === 1) {
        return x[0][1];
      }
      if (x.length === 2) {
        return reduce(x[0][1], x[1][1]);
      }
      throw new Error(`Unexpected error: full outer join produced ${x.length} values, but expected 1 or 2.`);
    }, entriesByKey);
  }
}
