import React, { useCallback, useEffect, useRef } from 'react';
import { createBEM } from '@zarm-design/bem';
import raf from 'raf';
import Events from '../utils/events';
import { ConfigContext } from '../config-provider';
import { getElementSize } from '../utils/dom';

var getTouches = function getTouches(touches) {
  return Array.from(touches).map(function (totuch) {
    return {
      x: totuch.pageX,
      y: totuch.pageY
    };
  });
};

var getDistance = function getDistance(a, b) {
  if (!b) return 0;
  return Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
};

var calculateScale = function calculateScale(startTouches, endTouches) {
  var startDistance = getDistance(startTouches[0], startTouches[1]);
  var endDistance = getDistance(endTouches[0], endTouches[1]);
  return endDistance / startDistance;
};

var range = function range(min, max, num) {
  return Math.min(Math.max(num, min), max);
};

var sum = function sum(a, b) {
  return a + b;
};

var getTouchCenter = function getTouchCenter(point) {
  return {
    x: point.map(function (_ref) {
      var x = _ref.x;
      return x;
    }).reduce(sum, 0) / point.length,
    y: point.map(function (_ref2) {
      var y = _ref2.y;
      return y;
    }).reduce(sum, 0) / point.length
  };
};

var cancelEvent = function cancelEvent(event) {
  event.stopPropagation();

  if (!Events.supportsPassiveEvents) {
    event.preventDefault();
  }
};

var PinchZoom = /*#__PURE__*/React.forwardRef(function (props, ref) {
  var container = ref || /*#__PURE__*/React.createRef();

  var _React$useContext = React.useContext(ConfigContext),
      prefixCls = _React$useContext.prefixCls;

  var bem = createBEM('pinch-zoom', {
    prefixCls: prefixCls
  });
  var children = props.children,
      className = props.className,
      maxScale = props.maxScale,
      minScale = props.minScale,
      onPinchZoom = props.onPinchZoom,
      style = props.style;
  var currentScale = 1;
  var lastScale = 1;
  var firstMove = false;
  var offset = useRef({
    x: 0,
    y: 0
  });
  var initOffset = useRef({
    x: 0,
    y: 0
  });
  var fingers = 0;
  var startTouches;
  var nthZoom = 0;
  var lastScaleCenter;
  var lastDragPosition;
  var wheelTimeOut;
  var rafId = useRef();
  var update = useCallback(function () {
    var updateFrame = function updateFrame() {
      var _container$current, _container$current2, _container$current3;

      var x = -offset.current.x / currentScale;
      var y = -offset.current.y / currentScale;
      container === null || container === void 0 ? void 0 : (_container$current = container.current) === null || _container$current === void 0 ? void 0 : _container$current.style.setProperty('--x', "".concat(x, "px"));
      container === null || container === void 0 ? void 0 : (_container$current2 = container.current) === null || _container$current2 === void 0 ? void 0 : _container$current2.style.setProperty('--y', "".concat(y, "px"));
      container === null || container === void 0 ? void 0 : (_container$current3 = container.current) === null || _container$current3 === void 0 ? void 0 : _container$current3.style.setProperty('--scale', currentScale);

      if (typeof onPinchZoom === 'function') {
        onPinchZoom(currentScale, x, y);
      }
    };

    if (rafId) {
      raf.cancel(rafId.current);
    }

    rafId.current = raf(updateFrame);
  }, [currentScale, container, onPinchZoom]);

  var alignCenter = function alignCenter() {
    var _container$current4, _container$current5;

    var node = (_container$current4 = container.current) === null || _container$current4 === void 0 ? void 0 : _container$current4.firstElementChild;
    initOffset.current = {
      x: 0,
      y: 0
    };
    var rect = (_container$current5 = container.current) === null || _container$current5 === void 0 ? void 0 : _container$current5.getBoundingClientRect();

    var _getElementSize = getElementSize(node),
        width = _getElementSize.width,
        height = _getElementSize.height;

    if (width <= (rect === null || rect === void 0 ? void 0 : rect.width)) {
      initOffset.current = {
        x: -((rect === null || rect === void 0 ? void 0 : rect.width) - width) / 2,
        y: 0
      };
    }

    if (height <= (rect === null || rect === void 0 ? void 0 : rect.height)) {
      initOffset.current = {
        x: initOffset.current.x,
        y: -((rect === null || rect === void 0 ? void 0 : rect.height) - height) / 2
      };
    }

    offset.current = initOffset === null || initOffset === void 0 ? void 0 : initOffset.current;
    update();
  };

  useEffect(function () {
    alignCenter();
  }, [initOffset, offset, container, update]);

  var calculateOffset = function calculateOffset(newOffset) {
    offset.current = {
      x: offset.current.x + newOffset.x,
      y: offset.current.y + newOffset.y
    };
  };

  var calculateOffsetBoundary = function calculateOffsetBoundary(currentOffset) {
    var _container$current6;

    var rect = getElementSize(container.current);

    var _getElementSize2 = getElementSize((_container$current6 = container.current) === null || _container$current6 === void 0 ? void 0 : _container$current6.firstElementChild),
        width = _getElementSize2.width,
        height = _getElementSize2.height;

    var elWidth = width * currentScale;
    var elHeight = height * currentScale;
    var maxX = elWidth - rect.width;
    var maxY = elHeight - rect.height;
    var maxOffsetX = Math.max(maxX, 0);
    var maxOffsetY = Math.max(maxY, 0);
    var minOffsetX = Math.min(maxX, 0);
    var minOffsetY = Math.min(maxY, 0);
    return {
      x: range(minOffsetX, maxOffsetX, currentOffset.x),
      y: range(minOffsetY, maxOffsetY, currentOffset.y)
    };
  };

  var doScale = function doScale(scale, touchCenter) {
    var originScale = currentScale;
    currentScale *= scale;
    currentScale = range(minScale / 2, maxScale, currentScale);
    var newScale = currentScale / originScale;
    calculateOffset({
      x: (newScale - 1) * (touchCenter.x + offset.current.x),
      y: (newScale - 1) * (touchCenter.y + offset.current.y)
    });
  };

  var getCurrentZoomCenter = function getCurrentZoomCenter() {
    var _offset$current = offset.current,
        x = _offset$current.x,
        y = _offset$current.y;
    var offsetLeft = x - initOffset.current.x;
    var offsetTop = y - initOffset.current.y;
    return {
      x: -1 * x - offsetLeft / (1 / currentScale - 1),
      y: -1 * y - offsetTop / (1 / currentScale - 1)
    };
  };

  var fixedScale = function fixedScale() {
    if (currentScale >= 1) {
      return false;
    }

    var center = getCurrentZoomCenter();
    doScale(1 / currentScale, center);
    offset.current = initOffset.current;
  };

  var end = function end() {
    fixedScale();
    update();
  };

  var handleZoomEnd = function handleZoomEnd() {
    end();
  };

  var handleDragEnd = function handleDragEnd() {
    end();
  };

  var handleZoomStart = function handleZoomStart() {
    lastScale = 1;
    lastScaleCenter = null;
    nthZoom = 0;
  };

  var doDrag = function doDrag(center, lastCenter) {
    if (lastCenter) {
      calculateOffset({
        x: -(center.x - lastCenter.x),
        y: -(center.y - lastCenter.y)
      });
    }
  };

  var getTouchesOffset = function getTouchesOffset(touches) {
    var rect = container.current.getBoundingClientRect();
    var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
    var posTop = rect.top + scrollTop;
    var posLeft = rect.left + scrollLeft;
    return touches.map(function (touch) {
      return {
        x: touch.x - posLeft,
        y: touch.y - posTop
      };
    });
  };

  var handleZoom = function handleZoom(event, newScale) {
    var touchCenter = getTouchCenter(getTouchesOffset(getTouches(event.touches)));
    var scale = newScale / lastScale;
    lastScale = newScale;
    nthZoom += 1;

    if (nthZoom > 3) {
      doScale(scale, touchCenter);
      doDrag(touchCenter, lastScaleCenter);
    }

    lastScaleCenter = touchCenter;
  };

  var handleDrag = function handleDrag(event) {
    var touch = getTouches(event.touches)[0];
    doDrag(touch, lastDragPosition);
    offset.current = calculateOffsetBoundary(offset.current);
    lastDragPosition = touch;
  };

  var handleDragStart = function handleDragStart(event) {
    lastDragPosition = null;
    handleDrag(event);
  };

  var touchStart = function touchStart(event) {
    firstMove = true;
    fingers = event.touches.length;
  };

  var interaction = '';

  var setInteraction = function setInteraction(newInteraction, event) {
    if (interaction !== newInteraction) {
      if (interaction && !newInteraction) {
        switch (interaction) {
          case 'zoom':
            handleZoomEnd();
            break;

          case 'drag':
            handleDragEnd();
            break;
        }
      }

      switch (newInteraction) {
        case 'zoom':
          handleZoomStart();
          break;

        case 'drag':
          handleDragStart(event);
          break;
      }
    }

    interaction = newInteraction;
  };

  var updateInteraction = function updateInteraction(event) {
    if (fingers === 2) {
      setInteraction('zoom', event);
    } else if (fingers === 1 && currentScale !== 1) {
      setInteraction('drag', event);
    } else {
      setInteraction(null, event);
    }
  };

  var touchMove = function touchMove(event) {
    if (firstMove) {
      updateInteraction(event);

      if (interaction) {
        cancelEvent(event);
      }

      startTouches = getTouches(event.touches);
    } else {
      switch (interaction) {
        case 'zoom':
          if (startTouches.length === 2 && event.touches.length === 2) {
            handleZoom(event, calculateScale(startTouches, getTouches(event.touches)));
          }

          break;

        case 'drag':
          handleDrag(event);
          break;
      }

      if (interaction) {
        cancelEvent(event);
        update();
      }
    }

    firstMove = false;
  };

  var touchEnd = function touchEnd(event) {
    fingers = event.touches.length;
    updateInteraction(event);
  };

  var wheel = function wheel(event) {
    var deltaY = event.deltaY;
    var ctrlKey = event.ctrlKey,
        deltaMode = event.deltaMode,
        pageX = event.pageX,
        pageY = event.pageY;

    if (deltaMode === 1) {
      deltaY *= 15;
    }

    var divisor = ctrlKey ? 100 : 300;
    doScale(1 - deltaY / divisor, getTouchesOffset([{
      x: pageX,
      y: pageY
    }])[0]);
    update();
    clearTimeout(wheelTimeOut);
    wheelTimeOut = setTimeout(function () {
      end();
    }, 100);
  };

  var cls = bem([className]);
  var child = React.Children.map(children, function (element, index) {
    return /*#__PURE__*/React.cloneElement(element, {
      key: +index,
      onLoad: function onLoad(e) {
        var _element$props, _element$props$onLoad;

        (_element$props = element.props) === null || _element$props === void 0 ? void 0 : (_element$props$onLoad = _element$props.onLoad) === null || _element$props$onLoad === void 0 ? void 0 : _element$props$onLoad.call(_element$props, e);
        alignCenter();
      }
    });
  });
  return /*#__PURE__*/React.createElement("div", {
    onTouchStart: touchStart,
    onTouchCancel: touchEnd,
    onTouchEnd: touchEnd,
    onTouchMove: touchMove,
    onWheel: wheel,
    ref: container,
    className: cls,
    style: style
  }, child);
});
PinchZoom.defaultProps = {
  minScale: 1,
  maxScale: 3
};
export default PinchZoom;