import React, { useState, useEffect, useRef, Fragment } from 'react';
import PropTypes from 'prop-types';
import { TransitionGroup, CSSTransition } from 'react-transition-group';

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }

  return obj;
}

function _extends() {
  _extends = Object.assign || function (target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i];

      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      }
    }

    return target;
  };

  return _extends.apply(this, arguments);
}

function ownKeys(object, enumerableOnly) {
  var keys = Object.keys(object);

  if (Object.getOwnPropertySymbols) {
    var symbols = Object.getOwnPropertySymbols(object);
    if (enumerableOnly) symbols = symbols.filter(function (sym) {
      return Object.getOwnPropertyDescriptor(object, sym).enumerable;
    });
    keys.push.apply(keys, symbols);
  }

  return keys;
}

function _objectSpread2(target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i] != null ? arguments[i] : {};

    if (i % 2) {
      ownKeys(Object(source), true).forEach(function (key) {
        _defineProperty(target, key, source[key]);
      });
    } else if (Object.getOwnPropertyDescriptors) {
      Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
    } else {
      ownKeys(Object(source)).forEach(function (key) {
        Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
      });
    }
  }

  return target;
}

function _objectWithoutPropertiesLoose(source, excluded) {
  if (source == null) return {};
  var target = {};
  var sourceKeys = Object.keys(source);
  var key, i;

  for (i = 0; i < sourceKeys.length; i++) {
    key = sourceKeys[i];
    if (excluded.indexOf(key) >= 0) continue;
    target[key] = source[key];
  }

  return target;
}

function _objectWithoutProperties(source, excluded) {
  if (source == null) return {};

  var target = _objectWithoutPropertiesLoose(source, excluded);

  var key, i;

  if (Object.getOwnPropertySymbols) {
    var sourceSymbolKeys = Object.getOwnPropertySymbols(source);

    for (i = 0; i < sourceSymbolKeys.length; i++) {
      key = sourceSymbolKeys[i];
      if (excluded.indexOf(key) >= 0) continue;
      if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
      target[key] = source[key];
    }
  }

  return target;
}

/* https://stackoverflow.com/a/2117523/214017 */
const uuidv4 = () => {
  if (window.crypto) {
    return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16));
  } else {
    return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
      const r = Math.random() * 16 | 0,
            v = c == "x" ? r : r & 0x3 | 0x8;
      return v.toString(16);
    });
  }
};
const getPercentInt = (num, total) => {
  // Check for division by zero.
  if (total <= 0) {
    return 0;
  }

  return Math.floor(num / total * 100);
};
/**
 * Determines a zeroth index based on progress 0-100.
 * Example:
 * getIndexFromProgress(10, 2); // 0
 */

const getIndexFromProgress = (progress, total) => {
  const index = Math.ceil(total * (progress / 100));
  const zeroIndex = index !== 0 ? index - 1 : index;
  return zeroIndex;
};

const supportedEventTypes = ["mousemove", "touchmove", "touchstart"];

const getCursorProgress = (syntheticEvent, axis, boundingClientRect) => {
  const isTouchEvent = syntheticEvent.touches && syntheticEvent.touches[0];
  const clientXYContainer = isTouchEvent ? syntheticEvent.touches[0] : syntheticEvent;
  const {
    clientX,
    clientY
  } = clientXYContainer;
  return axis === "y" ? getPercentInt(clientY - boundingClientRect.y, boundingClientRect.height) : getPercentInt(clientX - boundingClientRect.x, boundingClientRect.width);
};
/**
 * React hook to determine a user cursor's x or y "progress" while passing over an element.
 * Top left corner: x: 0, y: 0
 * Bottom right corner: x: 100, y: 100
 */


function useCursorProgress() {
  const initialState = {
    xProgress: 0,
    yProgress: 0,
    boundingClientRect: null
  };
  let [{
    xProgress,
    yProgress,
    boundingClientRect
  }, setProgress] = useState(initialState);

  function setCursorProgress(syntheticEvent) {
    // Prevent page scroll for touch events
    const hasTouch = syntheticEvent.touches;

    if (hasTouch && syntheticEvent.preventDefault) {
      syntheticEvent.preventDefault();
    }

    if (!syntheticEvent || !supportedEventTypes.includes(syntheticEvent.type)) {
      return;
    } // Note: this hits the DOM a lot, but seems performant enough.  If all images are
    // equally sized, this can possibly be cached in the future.


    const maybeCachedRect = syntheticEvent.target.getBoundingClientRect();
    setProgress({
      xProgress: getCursorProgress(syntheticEvent, "x", maybeCachedRect),
      yProgress: getCursorProgress(syntheticEvent, "y", maybeCachedRect),
      boundingClientRect: maybeCachedRect
    });
  }

  return [[xProgress, yProgress], setCursorProgress];
}

/**
 * React Hook that cycles through an image slideshow when the user hovers over a DOM node.
 * Example with a 2 image array:
 * - User's cursor is on the left half of the element, so currentImage is the first array item.
 * - User's cursor is on the right half of the element, so currentImage is the last array item.
 */

function useHoverSlideshow(images, axis = "horizontal") {
  const initialState = {
    currentImage: images[0],
    previousImage: null,
    currentImageIndex: 0,
    previousImageIndex: null
  };
  const [{
    currentImage,
    previousImage,
    currentImageIndex,
    previousImageIndex,
    currentImageEventId
  }, setImageState] = useState(initialState);
  const [progress, setCursorProgress] = useCursorProgress();
  let [xProgress, yProgress] = progress;

  function setImage(shouldReset = false) {
    let imageIndex = 0;
    /**
     * If shouldReset is false (default), then the user is actively interacting with the DOM
     * element, so we need to recompute currentImage based on cursor/touch position.
     */

    if (!shouldReset) {
      imageIndex = axis === "horizontal" ? getIndexFromProgress(xProgress, images.length) : getIndexFromProgress(yProgress, images.length);
    }
    /**
     * No need to update if the image index is unchanged.
     */


    const indexChanged = imageIndex !== currentImageIndex;

    if (indexChanged) {
      setImageState({
        currentImageIndex: imageIndex,
        currentImage: images[imageIndex],

        /**
         * "Image Events".
         * In order to properly animate between images, we need to create unique IDs
         * every time the user mouses into a new image zone.  UUID may be overkill,
         * but should be fine as long as it's performant enough.
         */
        currentImageEventId: uuidv4(),
        previousImageIndex: currentImageIndex,
        previousImage: previousImageIndex !== null ? images[currentImageIndex] : null,
        previousImageEventId: currentImageEventId
      });
    }
  } // Responds to x,y cursor progress side effects.


  useEffect(() => {
    setImage();
  }, [xProgress, yProgress]);
  return [{
    currentImage,
    currentImageIndex,
    previousImage,
    previousImageIndex,
    currentImageEventId
  }, {
    updateHoverSlideshow: setCursorProgress,
    resetHoverSlideshow: () => setImage(true)
  }];
}

function styleInject(css, ref) {
  if ( ref === void 0 ) ref = {};
  var insertAt = ref.insertAt;

  if (!css || typeof document === 'undefined') { return; }

  var head = document.head || document.getElementsByTagName('head')[0];
  var style = document.createElement('style');
  style.type = 'text/css';

  if (insertAt === 'top') {
    if (head.firstChild) {
      head.insertBefore(style, head.firstChild);
    } else {
      head.appendChild(style);
    }
  } else {
    head.appendChild(style);
  }

  if (style.styleSheet) {
    style.styleSheet.cssText = css;
  } else {
    style.appendChild(document.createTextNode(css));
  }
}

var css_248z = ".HoverSlideshow_container__3k7xc {\n\ttouch-action: none;\n\tdisplay: inline-flex;\n\tbackground-repeat: no-repeat;\n\t-webkit-touch-callout: none;\n\t-webkit-user-select: none;\n\toverflow: hidden;\n\tposition: relative;\n}\n\n.HoverSlideshow_innerContainer__3-jH3 {\n\tposition: relative;\n\twhite-space: pre;\n\twill-change: transform;\n\ttransition: 0.001s ease-in-out; /* HACK: fix for flickering seen on FF 70 */\n}\n\n.HoverSlideshow_img__3cXoo {\n\twidth: 100%;\n\tvertical-align: top;\n\ttransition: 0.2s opacity;\n\ttransition-delay: 1s;\n}\n";
var styles = {"container":"HoverSlideshow_container__3k7xc","innerContainer":"HoverSlideshow_innerContainer__3-jH3","img":"HoverSlideshow_img__3cXoo"};
styleInject(css_248z);

/**
 * Helper hook to keep track of total image loading progress.
 */

function useImgLoadProgress(totalImages) {
  const [loadProgress, setLoadProgress] = useState({
    percent: 0,
    totalLoaded: 0,
    imagesLoaded: []
  });

  function handleImgLoad(image) {
    setLoadProgress({
      percent: parseInt((loadProgress.totalLoaded + 1) / totalImages * 100),
      totalLoaded: loadProgress.totalLoaded + 1,
      imagesLoaded: [...loadProgress.imagesLoaded, image],
      isLoading: loadProgress.totalLoaded + 1 < totalImages
    });
  }

  return [loadProgress, handleImgLoad];
}

/**
 * TODO: support images array of objects, to support alt titles for each image.
 */

/**
 * Cycles through an image slideshow on cursor/touch movement across an image.
 */

function HoverSlideshow(props) {
  const {
    axis,
    images,
    width,
    height,
    style,
    className,
    LoadingPlaceholder
  } = props,
        otherProps = _objectWithoutProperties(props, ["axis", "images", "width", "height", "style", "className", "LoadingPlaceholder"]);

  let [// Current image href, which will update on mousemove/touchmove
  {
    currentImageIndex
  }, // Update will recompute currentImage based on the user's cursor
  // Reset assumes the user is no longer interacting, so it will return to the first (default) image
  {
    updateHoverSlideshow,
    resetHoverSlideshow
  }] = useHoverSlideshow(images, axis);
  const [imgLoadProgress, handleImgLoad] = useImgLoadProgress(images.length);
  const showPlaceholder = imgLoadProgress.isLoading && LoadingPlaceholder;
  return /*#__PURE__*/React.createElement("div", _extends({
    onMouseLeave: resetHoverSlideshow,
    onMouseMove: updateHoverSlideshow,
    onTouchStart: updateHoverSlideshow,
    onTouchMove: updateHoverSlideshow,
    onTouchEnd: resetHoverSlideshow,
    className: `${styles.container} ${className}`,
    style: _objectSpread2({
      width,
      height
    }, style)
  }, otherProps), showPlaceholder && /*#__PURE__*/React.createElement(LoadingPlaceholder, {
    progressPercent: imgLoadProgress.percent
  }), /*#__PURE__*/React.createElement("div", {
    className: styles.innerContainer,
    style: {
      transform: `translateX(-${parseInt(width) * currentImageIndex}px)`
    }
  }, images.map(src => {
    const style = LoadingPlaceholder ? {
      visibility: showPlaceholder ? "hidden" : "visible",
      opacity: showPlaceholder ? 0 : 1
    } : {};
    return /*#__PURE__*/React.createElement("img", {
      src: src,
      key: src,
      className: styles.img,
      onLoad: handleImgLoad.bind(null, src),
      style: style
    });
  })));
}
HoverSlideshow.propTypes = {
  /** ARIA label to add to container. */
  "aria-label": PropTypes.string,

  /** Axis to monitor cursor/touch "progress" on. */
  axis: PropTypes.oneOf(["horizontal", "vertical"]),

  /** Child elements.  Useful to display absolutely-positioned content over images. */
  children: PropTypes.any,

  /** Additional CSS classnames to add to the root container. */
  className: PropTypes.string,

  /** Height of the container, e.g. "100px" */
  height: PropTypes.string,

  /** Array of image hrefs. */
  images: PropTypes.arrayOf(PropTypes.string),

  /** Optional placeholder element to display while images load. */
  LoadingPlaceholder: PropTypes.elementType,

  /** ARIA role to add to image container. */
  role: PropTypes.string,

  /** Custom CSS style overrides. */
  style: PropTypes.object,

  /** Width of the container, e.g. "100px" */
  width: PropTypes.string
};
HoverSlideshow.defaultProps = {
  "aria-label": "Image slideshow",
  axis: "horizontal",
  className: "",
  images: [],
  role: "img",
  style: {}
};

var css_248z$1 = ".HoverSlideshowAnimated_container__3-pZk {\n\tposition: relative;\n\tdisplay: inline-block;\n}\n\n.HoverSlideshowAnimated_imageContainer__DI5bz {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tdisplay: flex;\n\tmax-width: 100%;\n}\n\n.HoverSlideshowAnimated_crossfade-appear__38xZV {\n\topacity: 0;\n}\n\n.HoverSlideshowAnimated_crossfade-appear-active__16FGs {\n\topacity: 1;\n\ttransition: opacity 300ms;\n}\n\n.HoverSlideshowAnimated_crossfade-enter-active__2bRYX {\n\topacity: 1;\n}\n\n.HoverSlideshowAnimated_crossfade-enter-done__2dmOf {\n\topacity: 1;\n}\n\n.HoverSlideshowAnimated_crossfade-exit__3pAzZ {\n\topacity: 1;\n}\n\n.HoverSlideshowAnimated_crossfade-exit-active__10DD0 {\n\topacity: 0;\n\ttransition: opacity 300ms;\n}\n\n/*.crossfade-exit-done {\n\topacity: 0;\n}*/\n\n.HoverSlideshowAnimated_img__3KgGz {\n\twidth: 100%;\n\tvertical-align: top;\n}\n\n.HoverSlideshowAnimated_hiddenPreloadedImages__3tauT {\n\tvisibility: hidden;\n\twidth: 0;\n\theight: 0;\n\toverflow: hidden;\n}\n";
var styles$1 = {"container":"HoverSlideshowAnimated_container__3-pZk","imageContainer":"HoverSlideshowAnimated_imageContainer__DI5bz","crossfade-appear":"HoverSlideshowAnimated_crossfade-appear__38xZV","crossfade-appear-active":"HoverSlideshowAnimated_crossfade-appear-active__16FGs","crossfade-enter-active":"HoverSlideshowAnimated_crossfade-enter-active__2bRYX","crossfade-enter-done":"HoverSlideshowAnimated_crossfade-enter-done__2dmOf","crossfade-exit":"HoverSlideshowAnimated_crossfade-exit__3pAzZ","crossfade-exit-active":"HoverSlideshowAnimated_crossfade-exit-active__10DD0","img":"HoverSlideshowAnimated_img__3KgGz","hiddenPreloadedImages":"HoverSlideshowAnimated_hiddenPreloadedImages__3tauT"};
styleInject(css_248z$1);

/**
 * Cycles through an image slideshow on cursor/touch movement across an image.  Uses CSS transitions to achieve a crossfade effect.
 *
 * Note: this component depends on the optional peer dependency `react-transition-group`, which can be installed with this command:
 *
 * ```npm i --save react-transition-group```
 */

function HoverSlideshowAnimated(props) {
  const {
    images,
    style,
    className,
    width,
    height,
    children,
    LoadingPlaceholder
  } = props,
        otherProps = _objectWithoutProperties(props, ["images", "style", "className", "width", "height", "children", "LoadingPlaceholder"]);

  let [{
    currentImage,
    currentImageEventId
  }, {
    updateHoverSlideshow,
    resetHoverSlideshow
  }] = useHoverSlideshow(images);
  const nodeRef = useRef(null);
  const [inProp, setInProp] = useState(false);
  const [imgLoadProgress, handleImgLoad] = useImgLoadProgress(images.length);
  const showPlaceholder = imgLoadProgress.isLoading && LoadingPlaceholder;
  return /*#__PURE__*/React.createElement("div", _extends({
    onMouseLeave: resetHoverSlideshow,
    onMouseMove: updateHoverSlideshow,
    style: _objectSpread2({
      width,
      height
    }, style),
    className: `${styles$1.container} ${className}`
  }, otherProps), /*#__PURE__*/React.createElement("div", {
    className: styles$1.hiddenPreloadedImages
  }, images.map(src => {
    return /*#__PURE__*/React.createElement("img", {
      key: src,
      src: src,
      onLoad: handleImgLoad.bind(null, src)
    });
  })), showPlaceholder && /*#__PURE__*/React.createElement(LoadingPlaceholder, {
    progressPercent: imgLoadProgress.percent
  }), !showPlaceholder && /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement("picture", null, /*#__PURE__*/React.createElement(React.StrictMode, null, /*#__PURE__*/React.createElement(TransitionGroup, null, /*#__PURE__*/React.createElement(CSSTransition, {
    timeout: 300,
    in: inProp,
    appear: true,
    classNames: {
      appear: styles$1["crossfade-appear"],
      appearActive: styles$1["crossfade-appear-active"],
      enter: styles$1["crossfade-enter"],
      enterActive: styles$1["crossfade-enter-active"],
      enterDone: styles$1["crossfade-enter-done"],
      exit: styles$1["crossfade-exit"],
      exitActive: styles$1["crossfade-exit-active"],
      exitDone: styles$1["crossfade-exit-done"]
    },
    key: currentImageEventId,
    nodeRef: nodeRef
  }, /*#__PURE__*/React.createElement("div", {
    className: styles$1.imageContainer,
    ref: nodeRef
  }, /*#__PURE__*/React.createElement("img", {
    src: currentImage,
    className: styles$1.img
  })))))), children));
}
HoverSlideshowAnimated.propTypes = {
  /** Child elements.  Useful to display absolutely-positioned content over images. */
  children: PropTypes.any,

  /** Additional CSS classnames to add to the root container. */
  className: PropTypes.string,

  /** Height of the container, e.g. "100px" */
  height: PropTypes.string,

  /** Optional href to add to anchor wrapper. */
  href: PropTypes.string,

  /** Array of image hrefs. */
  images: PropTypes.arrayOf(PropTypes.string),

  /** Optional placeholder element to display while images load. */
  LoadingPlaceholder: PropTypes.elementType,

  /** Custom CSS style overrides. */
  style: PropTypes.object,

  /** Width of the container, e.g. "100px" */
  width: PropTypes.string
};
HoverSlideshowAnimated.defaultProps = {
  className: "",
  images: [],
  style: {}
};

export { HoverSlideshow, HoverSlideshowAnimated, useCursorProgress, useHoverSlideshow };
