import _extends from "@babel/runtime/helpers/builtin/extends";
import _objectSpread from "@babel/runtime/helpers/builtin/objectSpread";
import _objectWithoutProperties from "@babel/runtime/helpers/builtin/objectWithoutProperties";

/* eslint-disable consistent-this */
// @inheritedComponent Drawer
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import Drawer, { getAnchor, isHorizontal } from '../Drawer/Drawer';
import { duration } from '../styles/transitions';
import withTheme from '../styles/withTheme';
import { getTransitionProps } from '../transitions/utils';
import SwipeArea from './SwipeArea'; // This value is closed to what browsers are using internally to
// trigger a native scroll.

const UNCERTAINTY_THRESHOLD = 3; // px
// We can only have one node at the time claiming ownership for handling the swipe.
// Otherwise, the UX would be confusing.
// That's why we use a singleton here.

let nodeThatClaimedTheSwipe = null; // Exported for test purposes.

export function reset() {
  nodeThatClaimedTheSwipe = null;
}
/* istanbul ignore if */

if (process.env.NODE_ENV !== 'production' && !React.createContext) {
  throw new Error('Material-UI: react@16.3.0 or greater is required.');
}

class SwipeableDrawer extends React.Component {
  constructor(...args) {
    var _temp;

    return _temp = super(...args), this.backdrop = null, this.paper = null, this.isSwiping = null, this.startX = null, this.startY = null, this.state = {}, this.handleBodyTouchStart = event => {
      // We are not supposed to hanlde this touch move.
      if (nodeThatClaimedTheSwipe !== null && nodeThatClaimedTheSwipe !== this) {
        return;
      }

      const {
        disableDiscovery,
        disableSwipeToOpen,
        open,
        swipeAreaWidth
      } = this.props;
      const anchor = getAnchor(this.props);
      const currentX = anchor === 'right' ? document.body.offsetWidth - event.touches[0].pageX : event.touches[0].pageX;
      const currentY = anchor === 'bottom' ? window.innerHeight - event.touches[0].clientY : event.touches[0].clientY;

      if (!open) {
        if (disableSwipeToOpen) {
          return;
        }

        if (isHorizontal(this.props)) {
          if (currentX > swipeAreaWidth) {
            return;
          }
        } else if (currentY > swipeAreaWidth) {
          return;
        }
      }

      nodeThatClaimedTheSwipe = this;
      this.startX = currentX;
      this.startY = currentY;
      this.setState({
        maybeSwiping: true
      });

      if (!open && this.paper) {
        // the ref may be null when a parent component updates while swiping
        this.setPosition(this.getMaxTranslate() + (disableDiscovery ? 20 : -swipeAreaWidth), {
          changeTransition: false
        });
      }

      document.body.addEventListener('touchmove', this.handleBodyTouchMove, {
        passive: false
      });
      document.body.addEventListener('touchend', this.handleBodyTouchEnd); // https://plus.google.com/+PaulIrish/posts/KTwfn1Y2238

      document.body.addEventListener('touchcancel', this.handleBodyTouchEnd);
    }, this.handleBodyTouchMove = event => {
      // the ref may be null when a parent component updates while swiping
      if (!this.paper) return;
      const anchor = getAnchor(this.props);
      const horizontalSwipe = isHorizontal(this.props);
      const currentX = anchor === 'right' ? document.body.offsetWidth - event.touches[0].pageX : event.touches[0].pageX;
      const currentY = anchor === 'bottom' ? window.innerHeight - event.touches[0].clientY : event.touches[0].clientY; // We don't know yet.

      if (this.isSwiping == null) {
        const dx = Math.abs(currentX - this.startX);
        const dy = Math.abs(currentY - this.startY); // We are likely to be swiping, let's prevent the scroll event on iOS.

        if (dx > dy) {
          event.preventDefault();
        }

        const isSwiping = horizontalSwipe ? dx > dy && dx > UNCERTAINTY_THRESHOLD : dy > dx && dy > UNCERTAINTY_THRESHOLD;

        if (isSwiping === true || (horizontalSwipe ? dy > UNCERTAINTY_THRESHOLD : dx > UNCERTAINTY_THRESHOLD)) {
          this.isSwiping = isSwiping;

          if (!isSwiping) {
            this.handleBodyTouchEnd(event);
            return;
          } // Shift the starting point.


          this.startX = currentX;
          this.startY = currentY; // Compensate for the part of the drawer displayed on touch start.

          if (!this.props.disableDiscovery && !this.props.open) {
            if (horizontalSwipe) {
              this.startX -= this.props.swipeAreaWidth;
            } else {
              this.startY -= this.props.swipeAreaWidth;
            }
          }
        }
      }

      if (!this.isSwiping) {
        return;
      } // We are swiping, let's prevent the scroll event on iOS.


      event.preventDefault();
      this.setPosition(this.getTranslate(horizontalSwipe ? currentX : currentY));
    }, this.handleBodyTouchEnd = event => {
      nodeThatClaimedTheSwipe = null;
      this.removeBodyTouchListeners();
      this.setState({
        maybeSwiping: false
      }); // The swipe wasn't started.

      if (!this.isSwiping) {
        this.isSwiping = null;
        return;
      }

      const anchor = getAnchor(this.props);
      let current;

      if (isHorizontal(this.props)) {
        current = anchor === 'right' ? document.body.offsetWidth - event.changedTouches[0].pageX : event.changedTouches[0].pageX;
      } else {
        current = anchor === 'bottom' ? window.innerHeight - event.changedTouches[0].clientY : event.changedTouches[0].clientY;
      }

      const translateRatio = this.getTranslate(current) / this.getMaxTranslate(); // We have to open or close after setting swiping to null,
      // because only then CSS transition is enabled.

      if (translateRatio > 0.5) {
        if (this.isSwiping && !this.props.open) {
          // Reset the position, the swipe was aborted.
          this.setPosition(this.getMaxTranslate(), {
            mode: 'enter'
          });
        } else {
          this.props.onClose();
        }
      } else if (this.isSwiping && !this.props.open) {
        this.props.onOpen();
      } else {
        // Reset the position, the swipe was aborted.
        this.setPosition(0, {
          mode: 'exit'
        });
      }

      this.isSwiping = null;
    }, this.handleBackdropRef = node => {
      this.backdrop = node ? ReactDOM.findDOMNode(node) : null;
    }, this.handlePaperRef = node => {
      this.paper = node ? ReactDOM.findDOMNode(node) : null;
    }, _temp;
  }

  componentDidMount() {
    if (this.props.variant === 'temporary') {
      this.listenTouchStart();
    }
  }

  componentDidUpdate(prevProps) {
    const variant = this.props.variant;
    const prevVariant = prevProps.variant;

    if (variant !== prevVariant) {
      if (variant === 'temporary') {
        this.listenTouchStart();
      } else if (prevVariant === 'temporary') {
        this.removeTouchStart();
      }
    }
  }

  componentWillUnmount() {
    this.removeTouchStart();
    this.removeBodyTouchListeners(); // We need to release the lock.

    if (nodeThatClaimedTheSwipe === this) {
      nodeThatClaimedTheSwipe = null;
    }
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (typeof prevState.maybeSwiping === 'undefined') {
      return {
        maybeSwiping: false,
        open: nextProps.open
      };
    }

    if (!nextProps.open && prevState.open) {
      return {
        maybeSwiping: false,
        open: nextProps.open
      };
    }

    return {
      open: nextProps.open
    };
  }

  getMaxTranslate() {
    return isHorizontal(this.props) ? this.paper.clientWidth : this.paper.clientHeight;
  }

  getTranslate(current) {
    const start = isHorizontal(this.props) ? this.startX : this.startY;
    return Math.min(Math.max(this.props.open ? start - current : this.getMaxTranslate() + start - current, 0), this.getMaxTranslate());
  }

  setPosition(translate, options = {}) {
    const {
      mode = null,
      changeTransition = true
    } = options;
    const anchor = getAnchor(this.props);
    const rtlTranslateMultiplier = ['right', 'bottom'].indexOf(anchor) !== -1 ? 1 : -1;
    const transform = isHorizontal(this.props) ? `translate(${rtlTranslateMultiplier * translate}px, 0)` : `translate(0, ${rtlTranslateMultiplier * translate}px)`;
    const drawerStyle = this.paper.style;
    drawerStyle.webkitTransform = transform;
    drawerStyle.transform = transform;
    let transition = '';

    if (mode) {
      transition = this.props.theme.transitions.create('all', getTransitionProps({
        timeout: this.props.transitionDuration
      }, {
        mode
      }));
    }

    if (changeTransition) {
      drawerStyle.webkitTransition = transition;
      drawerStyle.transition = transition;
    }

    if (!this.props.disableBackdropTransition) {
      const backdropStyle = this.backdrop.style;
      backdropStyle.opacity = 1 - translate / this.getMaxTranslate();

      if (changeTransition) {
        backdropStyle.webkitTransition = transition;
        backdropStyle.transition = transition;
      }
    }
  }

  listenTouchStart() {
    document.body.addEventListener('touchstart', this.handleBodyTouchStart);
  }

  removeTouchStart() {
    document.body.removeEventListener('touchstart', this.handleBodyTouchStart);
  }

  removeBodyTouchListeners() {
    document.body.removeEventListener('touchmove', this.handleBodyTouchMove, {
      passive: false
    });
    document.body.removeEventListener('touchend', this.handleBodyTouchEnd);
    document.body.removeEventListener('touchcancel', this.handleBodyTouchEnd);
  }

  render() {
    const _props = this.props,
          {
      disableBackdropTransition,
      disableDiscovery,
      disableSwipeToOpen,
      ModalProps: {
        BackdropProps
      } = {},
      onOpen,
      open,
      PaperProps = {},
      swipeAreaWidth,
      variant
    } = _props,
          ModalPropsProp = _objectWithoutProperties(_props.ModalProps, ["BackdropProps"]),
          other = _objectWithoutProperties(_props, ["disableBackdropTransition", "disableDiscovery", "disableSwipeToOpen", "ModalProps", "onOpen", "open", "PaperProps", "swipeAreaWidth", "variant"]);

    const {
      maybeSwiping
    } = this.state;
    return React.createElement(React.Fragment, null, React.createElement(Drawer, _extends({
      open: variant === 'temporary' && maybeSwiping ? true : open,
      variant: variant,
      ModalProps: _objectSpread({
        BackdropProps: _objectSpread({}, BackdropProps, {
          ref: this.handleBackdropRef
        })
      }, ModalPropsProp),
      PaperProps: _objectSpread({}, PaperProps, {
        style: _objectSpread({
          pointerEvents: variant === 'temporary' && !open ? 'none' : ''
        }, PaperProps.style),
        ref: this.handlePaperRef
      })
    }, other)), !disableDiscovery && !disableSwipeToOpen && variant === 'temporary' && React.createElement(SwipeArea, {
      anchor: other.anchor,
      swipeAreaWidth: swipeAreaWidth
    }));
  }

}

SwipeableDrawer.propTypes = process.env.NODE_ENV !== "production" ? {
  /**
   * @ignore
   */
  anchor: PropTypes.oneOf(['left', 'top', 'right', 'bottom']),

  /**
   * Disable the backdrop transition.
   * This can improve the FPS on low-end devices.
   */
  disableBackdropTransition: PropTypes.bool,

  /**
   * If `true`, touching the screen near the edge of the drawer will not slide in the drawer a bit
   * to promote accidental discovery of the swipe gesture.
   */
  disableDiscovery: PropTypes.bool,

  /**
   * If `true`, swipe to open is disabled. This is useful in browsers where swiping triggers
   * navigation actions. Swipe to open is disabled on iOS browsers by default.
   */
  disableSwipeToOpen: PropTypes.bool,

  /**
   * @ignore
   */
  ModalProps: PropTypes.object,

  /**
   * Callback fired when the component requests to be closed.
   *
   * @param {object} event The event source of the callback
   */
  onClose: PropTypes.func.isRequired,

  /**
   * Callback fired when the component requests to be opened.
   *
   * @param {object} event The event source of the callback
   */
  onOpen: PropTypes.func.isRequired,

  /**
   * If `true`, the drawer is open.
   */
  open: PropTypes.bool.isRequired,

  /**
   * @ignore
   */
  PaperProps: PropTypes.object,

  /**
   * The width of the left most (or right most) area in pixels where the
   * drawer can be swiped open from.
   */
  swipeAreaWidth: PropTypes.number,

  /**
   * @ignore
   */
  theme: PropTypes.object.isRequired,

  /**
   * The duration for the transition, in milliseconds.
   * You may specify a single timeout for all transitions, or individually with an object.
   */
  transitionDuration: PropTypes.oneOfType([PropTypes.number, PropTypes.shape({
    enter: PropTypes.number,
    exit: PropTypes.number
  })]),

  /**
   * @ignore
   */
  variant: PropTypes.oneOf(['permanent', 'persistent', 'temporary'])
} : {};
SwipeableDrawer.defaultProps = {
  anchor: 'left',
  disableBackdropTransition: false,
  disableDiscovery: false,
  disableSwipeToOpen: typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent),
  swipeAreaWidth: 20,
  transitionDuration: {
    enter: duration.enteringScreen,
    exit: duration.leavingScreen
  },
  variant: 'temporary' // Mobile first.

};
export default withTheme()(SwipeableDrawer);