import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import { parsePropertyObject } from './propertyObject';
export function createCSSTransitionAnimator(classNamesRegistry) {
  var DEFAULT_PARSED_PROPS = parsePropertyObject({});
  /**
   * A tuple of parsed PropertyObjects in an applying
   * order that is required to track possible changes.
   *
   * @example
   * [prevState, currentState]
   */

  var history = [DEFAULT_PARSED_PROPS, DEFAULT_PARSED_PROPS];

  var pushState = function pushState(current) {
    history[0] = history[1];
    history[1] = current;
  };

  var hasLastChangedValue = function hasLastChangedValue(prop) {
    return history[0][prop].value !== history[1][prop].value;
  };

  var getWillChangeProps = function getWillChangeProps() {
    return {
      transform: hasLastChangedValue('transform'),
      width: hasLastChangedValue('width'),
      height: hasLastChangedValue('height'),
      opacity: hasLastChangedValue('opacity')
    };
  };
  /**
   * A single animation may include multiple CSS transitions
   * of different properties and the animator should invoke
   * finish callback only after all of them.
   *
   * @example
   * ```css
   * transition-property: transform, opacity;
   * ```
   */


  var remainingPropsToChange = 0;
  var finishCallbackRef;

  function addElementStyles(_ref) {
    var element = _ref.element,
        transitioned = _ref.transitioned,
        parsedProps = _ref.parsedProps,
        willChangeProps = _ref.willChangeProps,
        speed = _ref.speed;
    var className = parsedProps.className,
        transform = parsedProps.transform,
        width = parsedProps.width,
        height = parsedProps.height,
        opacity = parsedProps.opacity;

    var combine = function combine(a) {
      return a.join(', ');
    };

    var willChangePropsArray = Object.keys(willChangeProps).filter(function (prop) {
      return willChangeProps[prop];
    });
    classNamesRegistry.register('transition', className);
    element.className = classNamesRegistry.toString();
    element.style.willChange = combine(willChangePropsArray);

    if (transitioned) {
      var transitionProperty = [];
      var transitionDuration = [];
      var transitionTimingFunction = [];
      /**
       * The order of transitioned values should be the same
       * in each array, which is ensured by a loop.
       */

      willChangePropsArray.forEach(function (prop) {
        transitionProperty.push(prop);
        transitionDuration.push(applySpeed(parsedProps[prop].duration, speed));
        transitionTimingFunction.push(parsedProps[prop].easing);
      });
      element.style.transitionProperty = combine(transitionProperty);
      element.style.transitionDuration = combine(oneOrAll(transitionDuration));
      element.style.transitionTimingFunction = combine(oneOrAll(transitionTimingFunction));
    }

    if (willChangeProps.transform) {
      element.style.transform = transform.value;
      element.style.transformOrigin = transform.origin;
    }

    if (willChangeProps.width) {
      element.style.width = width.value === 'auto' ? '' : width.value;
    }

    if (willChangeProps.height) {
      element.style.height = height.value === 'auto' ? '' : height.value;
    }

    if (willChangeProps.opacity) {
      element.style.opacity = opacity.value;
    }
  }

  function removeElementStyles(element) {
    classNamesRegistry.register('transition', '');
    element.className = classNamesRegistry.toString();
    element.style.willChange = '';
    element.style.transitionProperty = '';
    element.style.transitionDuration = '';
    element.style.transitionTimingFunction = '';
    element.style.transform = '';
    element.style.transformOrigin = '';
    element.style.width = '';
    element.style.height = '';
    element.style.opacity = '';
  }
  /**
   * https://www.phpied.com/rendering-repaint-reflowrelayout-restyle/
   */


  function forceRepaint(element) {
    element.offsetHeight;
  }
  /**
   * We can reduce multiple transition values,
   * but only if they are all the same.
   */


  function oneOrAll(values) {
    var unique = _toConsumableArray(new Set(values));

    return unique.length === 1 ? unique : values;
  }

  function applySpeed(duration, speed) {
    if (!speed || speed === 1) {
      return duration;
    }

    var value = parseInt(duration, 10);
    var units = duration.slice(-2) === 'ms' ? 'ms' : 's';
    return value / speed + units;
  }
  /**
   * The instant transition is that defined
   * with a zero duration that won't trigger
   * a native transitionEnd event.
   */


  function isInstantTransition(parsedProps, willChangeProps) {
    if (parsedProps !== undefined) {
      return !Object.keys(willChangeProps).some(function (prop) {
        return parsedProps[prop].duration !== '0ms';
      });
    }

    return true;
  }

  return {
    animate: function animate(element, from, to, speed) {
      var fromParsedProps, toParsedProps;

      if (from !== undefined) {
        fromParsedProps = parsePropertyObject(from);
        pushState(fromParsedProps);
      }

      if (to !== undefined) {
        toParsedProps = parsePropertyObject(to);
        pushState(toParsedProps);
      }
      /**
       * We can determine which properties will actually
       * change after pushing them into the history.
       */


      var willChangeProps = getWillChangeProps();
      remainingPropsToChange = Object.keys(willChangeProps).reduce(function (sum, prop) {
        return sum + Number(willChangeProps[prop]);
      }, 0);

      if (fromParsedProps !== undefined) {
        addElementStyles({
          element: element,
          transitioned: false,
          parsedProps: fromParsedProps,
          willChangeProps: willChangeProps
        }); // repaint between synchronized styles change

        forceRepaint(element);
      }

      if (toParsedProps !== undefined) {
        addElementStyles({
          element: element,
          transitioned: true,
          parsedProps: toParsedProps,
          willChangeProps: willChangeProps,
          speed: speed
        });
      }
      /**
       * Only the optional "to" props are "transitioned" and
       * defined can try to execute an animation with a zero
       * duration that won't trigger a transitionEnd event.
       */


      if (isInstantTransition(toParsedProps, willChangeProps)) {
        remainingPropsToChange = 0;

        if (finishCallbackRef) {
          finishCallbackRef();
        }
      }
    },
    apply: function apply(element, props) {
      if (props !== undefined) {
        var parsedProps = parsePropertyObject(props);
        pushState(parsedProps);
        addElementStyles({
          element: element,
          transitioned: false,
          parsedProps: parsedProps,
          willChangeProps: getWillChangeProps()
        });
      }
    },
    cleanup: function cleanup(element) {
      pushState(DEFAULT_PARSED_PROPS);
      removeElementStyles(element);
    },
    propertyTransitionEnd: function propertyTransitionEnd() {
      if (--remainingPropsToChange === 0 && finishCallbackRef) {
        finishCallbackRef();
      }
    },
    onFinish: function onFinish(callback) {
      finishCallbackRef = callback;
    }
  };
}