import d3Interpolate from "d3-interpolate";

export const isInterpolatable = function (obj) {
  // d3 turns null into 0 and undefined into NaN, which we don't want.
  if (obj !== null) {
    switch (typeof obj) {
    case "undefined":
      return false;
    case "number":
      // The standard `isNaN` is fine in this case since we already know the
      // type is number.
      return !isNaN(obj) && obj !== Number.POSITIVE_INFINITY && obj !== Number.NEGATIVE_INFINITY;
    case "string":
      // d3 might not *actually* be able to interpolate the string, but it
      // won't cause any issues to let it try.
      return true;
    case "boolean":
      // d3 turns Booleans into integers, which we don't want. Sure, we could
      // interpolate from 0 -> 1, but we'd be sending a non-Boolean to
      // something expecting a Boolean.
      return false;
    case "object":
      // Don't try to interpolate class instances (except Date or Array).
      const proto = Object.getPrototypeOf(obj);
      return proto === Object.prototype || proto === null;
    case "function":
      // Careful! There may be extra properties on function objects that the
      // component expects to access - for instance, it may be a `d3.scale()`
      // function, which has its own methods attached. We don't know if the
      // component is only going to call the function (in which case it's
      // safely interpolatable) or if it's going to access special properties
      // (in which case our function generated from `interpolateFunction` will
      // most likely cause an error. We could check for enumerable properties
      // on the function object here to see if it's a "plain" function, but
      // let's just require that components prevent such function props from
      // being animated in the first place.
      return true;
    }
  }
  return false;
};

/**
 * Interpolate immediately to the end value at the given step `when`.
 * Some nicer default behavior might be to jump at the halfway point or return
 * `a` if `t` is 0 (instead of always returning `b`). But d3's default
 * interpolator does not do these things:
 *
 *   d3.interpolate('aaa', 'bbb')(0) === 'bbb'
 *
 * ...and things might get wonky if we don't replicate that behavior.
 *
 * @param {any} a - Start value.
 * @param {any} b - End value.
 * @param {Number} when - Step value (0 to 1) at which to jump to `b`.
 * @returns {Function} An interpolation function.
 */
export const interpolateImmediate = function (a, b, when = 0) {
  return function (t) {
    return (t < when) ? a : b;
  };
};

/**
 * Interpolate to or from a function. The interpolated value will be a function
 * that calls `a` (if it's a function) and `b` (if it's a function) and calls
 * `d3.interpolate` on the resulting values. Note that our function won't
 * necessarily be called (that's up to the component this eventually gets
 * passed to) - but if it does get called, it will return an appropriately
 * interpolated value.
 *
 * @param {any} a - Start value.
 * @param {any} b - End value.
 * @returns {Function} An interpolation function.
 */
export const interpolateFunction = function (a, b) {
  return function (t) {
    if (t >= 1) {
      return b;
    }
    return function () {
      /* eslint-disable no-invalid-this */
      const aval = (typeof a === "function") ? a.apply(this, arguments) : a;
      const bval = (typeof b === "function") ? b.apply(this, arguments) : b;
      return d3Interpolate.value(aval, bval)(t);
    };
  };
};

/**
 * By default, the list of interpolators used by `d3.interpolate` has a few
 * downsides:
 *
 * - `null` values get turned into 0.
 * - `undefined`, `function`, and some other value types get turned into NaN.
 * - Boolean types get turned into numbers, which probably will be meaningless
 *   to whatever is consuming them.
 * - It tries to interpolate between identical start and end values, doing
 *   unnecessary calculations that sometimes result in floating point rounding
 *   errors.
 *
 * If only the default interpolators are used, `VictoryAnimation` will happily
 * pass down NaN (and other bad) values as props to the wrapped component.
 * The component will then either use the incorrect values or complain that it
 * was passed props of the incorrect type. This custom interpolator is added
 * using the `d3.interpolators` API, and prevents such cases from happening
 * for most values.
 *
 * @param {any} a - Start value.
 * @param {any} b - End value.
 * @returns {Function|undefined} An interpolation function, if necessary.
 */
export const victoryInterpolator = function (a, b) {
  // If the values are strictly equal, or either value is not interpolatable,
  // just use either the start value `a` or end value `b` at every step, as
  // there is no reasonable in-between value.
  if (a === b || !isInterpolatable(a) || !isInterpolatable(b)) {
    return interpolateImmediate(a, b);
  }
  if (typeof a === "function" || typeof b === "function") {
    return interpolateFunction(a, b);
  }
};

let interpolatorAdded = false;

export const addVictoryInterpolator = function () {
  if (!interpolatorAdded) {
    d3Interpolate.values.push(victoryInterpolator);
    interpolatorAdded = true;
  }
};

@@ -36,16 +36,72 @@
 polate%22;
+%0Aimport isPlainObject  from %22lodash/lang/isPlainObject%22;
 %0A%0Aexport
@@ -1015,107 +1015,78 @@
     
-const proto = Object.getPrototypeOf(obj);%0A      return proto === Object.prototype %7C%7C proto === null
+return obj instanceof Date %7C%7C Array.isArray(obj) %7C%7C isPlainObject(obj)
 ;%0A  

use getPlainObject