// @flow

import {curry} from "flow-static-land/lib/Fun";
import {isLastIndex} from "@jumpn/utils-array";

import get from "./get";
import hasKey from "./hasKey";
import remove from "./remove";
import shallowCopy from "./shallowCopy";

import type {Composite, Path} from "./types";

const createReduceContext = composite => {
  const origin = shallowCopy(composite);

  return {origin, current: origin, previous: undefined};
};

const createSupporting = key => (typeof key === "number" ? [] : {});

const copyOrCreate = (key, nextKey, current) =>
  hasKey(key, current)
    ? shallowCopy(get(key, current))
    : createSupporting(nextKey);

const getNext = (path, updater, index, current) =>
  isLastIndex(path, index)
    ? updater(get(path[index], current))
    : copyOrCreate(path[index], path[index + 1], current);

const set = (key, value, composite) => {
  // eslint-disable-next-line no-param-reassign
  composite[(key: any)] = value;

  return get(key, composite);
};

const updateSet = (path, index, value, context) => ({
  ...context,
  current: set(path[index], value, context.current),
  previous: context.current
});

const updateRemove = (path, index, context) => {
  const removed = remove(path[index], context.current);

  return index === 0
    ? {...context, current: removed, origin: removed}
    : {
        ...context,
        previous: set(path[index - 1], removed, (context.previous: any))
      };
};

const removeAction = Symbol("composite.updateIn.removeAction");

const update = (path, index, value, context) =>
  value === removeAction
    ? updateRemove(path, index, context)
    : updateSet(path, index, value, context);

const getReducer = (path, updater) => (context, key, index) =>
  update(path, index, getNext(path, updater, index, context.current), context);

/**
 * Returns a new composite with the result of having updated the property value
 * at the given path with the result of the call to updater function.
 * 
 * Entry removal is supported by returning `updateIn.remove` symbol on updater
 * function.
 */
const updateIn = (
  path: Path,
  updater: (prev: any) => any,
  composite: Composite
): Composite =>
  path.length === 0
    ? composite
    : path.reduce(getReducer(path, updater), createReduceContext(composite))
        .origin;

// we are doing this way and not returning an Object.assign construction, as
// that is not well typed (returns any)
const updateInCurried = curry(updateIn);

updateInCurried.remove = removeAction;

export default updateInCurried;

@@ -426,399 +426,8 @@
 %7D;%0A%0A
-const createSupporting = key =%3E (typeof key === %22number%22 ? %5B%5D : %7B%7D);%0A%0Aconst copyOrCreate = (key, nextKey, current) =%3E%0A  hasKey(key, current)%0A    ? shallowCopy(get(key, current))%0A    : createSupporting(nextKey);%0A%0Aconst getNext = (path, updater, index, current) =%3E%0A  isLastIndex(path, index)%0A    ? updater(get(path%5Bindex%5D, current))%0A    : copyOrCreate(path%5Bindex%5D, path%5Bindex + 1%5D, current);%0A%0A
 cons
@@ -1250,24 +1250,415 @@
  context);%0A%0A
+const createSupporting = key =%3E (typeof key === %22number%22 ? %5B%5D : %7B%7D);%0A%0Aconst copyOrCreate = (key, nextKey, current) =%3E%0A  hasKey(key, current)%0A    ? shallowCopy(get(key, current))%0A    : createSupporting(nextKey);%0A%0Aconst getNext = (path, updater, index, current) =%3E%0A  isLastIndex(path, index)%0A    ? updater(get(path%5Bindex%5D, current))%0A    : copyOrCreate(path%5Bindex%5D, path%5Bindex + 1%5D, current);%0A%0A
 const getRed

Improve function ordering (by usage proximity)