import _extends from "@babel/runtime/helpers/builtin/extends";
import _objectWithoutProperties from "@babel/runtime/helpers/builtin/objectWithoutProperties";
// @inheritedComponent Portal
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import warning from 'warning';
import keycode from 'keycode';
import ownerDocument from '../utils/ownerDocument';
import RootRef from '../RootRef';
import Portal from '../Portal';
import { createChainedFunction } from '../utils/helpers';
import withStyles from '../styles/withStyles';
import ModalManager from './ModalManager';
import Backdrop from '../Backdrop';

function getContainer(container, defaultContainer) {
  container = typeof container === 'function' ? container() : container;
  return ReactDOM.findDOMNode(container) || defaultContainer;
}

function getHasTransition(props) {
  return props.children ? props.children.props.hasOwnProperty('in') : false;
}

export const styles = theme => ({
  root: {
    position: 'fixed',
    zIndex: theme.zIndex.modal,
    right: 0,
    bottom: 0,
    top: 0,
    left: 0
  },
  hidden: {
    visibility: 'hidden'
  }
});
/* 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 Modal extends React.Component {
  constructor(props) {
    super(props);
    this.mountNode = null;
    this.modalNode = null;
    this.dialogNode = null;
    this.mounted = false;

    this.handleRendered = () => {
      this.autoFocus(); // Fix a bug on Chrome where the scroll isn't initially 0.

      this.modalNode.scrollTop = 0;

      if (this.props.onRendered) {
        this.props.onRendered();
      }
    };

    this.handleOpen = () => {
      const doc = ownerDocument(this.mountNode);
      const container = getContainer(this.props.container, doc.body);
      this.props.manager.add(this, container);
      doc.addEventListener('keydown', this.handleDocumentKeyDown);
      doc.addEventListener('focus', this.enforceFocus, true);
    };

    this.handleClose = () => {
      this.props.manager.remove(this);
      const doc = ownerDocument(this.mountNode);
      doc.removeEventListener('keydown', this.handleDocumentKeyDown);
      doc.removeEventListener('focus', this.enforceFocus, true);
      this.restoreLastFocus();
    };

    this.handleExited = () => {
      this.setState({
        exited: true
      });
      this.handleClose();
    };

    this.handleBackdropClick = event => {
      if (event.target !== event.currentTarget) {
        return;
      }

      if (this.props.onBackdropClick) {
        this.props.onBackdropClick(event);
      }

      if (!this.props.disableBackdropClick && this.props.onClose) {
        this.props.onClose(event, 'backdropClick');
      }
    };

    this.handleDocumentKeyDown = event => {
      if (!this.isTopModal() || keycode(event) !== 'esc') {
        return;
      }

      if (this.props.onEscapeKeyDown) {
        this.props.onEscapeKeyDown(event);
      }

      if (!this.props.disableEscapeKeyDown && this.props.onClose) {
        this.props.onClose(event, 'escapeKeyDown');
      }
    };

    this.checkForFocus = () => {
      this.lastFocus = ownerDocument(this.mountNode).activeElement;
    };

    this.enforceFocus = () => {
      if (this.props.disableEnforceFocus || !this.mounted || !this.isTopModal()) {
        return;
      }

      const currentActiveElement = ownerDocument(this.mountNode).activeElement;

      if (this.dialogNode && !this.dialogNode.contains(currentActiveElement)) {
        this.dialogNode.focus();
      }
    };

    this.state = {
      exited: !this.props.open
    };
  }

  componentDidMount() {
    this.mounted = true;

    if (this.props.open) {
      this.handleOpen();
    }
  }

  componentDidUpdate(prevProps) {
    if (!prevProps.open && this.props.open) {
      this.checkForFocus();
    }

    if (prevProps.open && !this.props.open && !getHasTransition(this.props)) {
      // Otherwise handleExited will call this.
      this.handleClose();
    } else if (!prevProps.open && this.props.open) {
      this.handleOpen();
    }
  }

  componentWillUnmount() {
    this.mounted = false;

    if (this.props.open || getHasTransition(this.props) && !this.state.exited) {
      this.handleClose();
    }
  }

  static getDerivedStateFromProps(nextProps) {
    if (nextProps.open) {
      return {
        exited: false
      };
    }

    if (!getHasTransition(nextProps)) {
      // Otherwise let handleExited take care of marking exited.
      return {
        exited: true
      };
    }

    return null;
  }

  autoFocus() {
    if (this.props.disableAutoFocus) {
      return;
    }

    const currentActiveElement = ownerDocument(this.mountNode).activeElement;

    if (this.dialogNode && !this.dialogNode.contains(currentActiveElement)) {
      this.lastFocus = currentActiveElement;

      if (!this.dialogNode.hasAttribute('tabIndex')) {
        process.env.NODE_ENV !== "production" ? warning(false, ['Material-UI: the modal content node does not accept focus.', 'For the benefit of assistive technologies, ' + 'the tabIndex of the node is being set to "-1".'].join('\n')) : void 0;
        this.dialogNode.setAttribute('tabIndex', -1);
      }

      this.dialogNode.focus();
    }
  }

  restoreLastFocus() {
    if (this.props.disableRestoreFocus) {
      return;
    }

    if (this.lastFocus) {
      // Not all elements in IE11 have a focus method.
      // Because IE11 market share is low, we accept the restore focus being broken
      // and we silent the issue.
      if (this.lastFocus.focus) {
        this.lastFocus.focus();
      }

      this.lastFocus = null;
    }
  }

  isTopModal() {
    return this.props.manager.isTopModal(this);
  }

  render() {
    const _props = this.props,
          {
      BackdropComponent,
      BackdropProps,
      children,
      classes,
      className,
      container,
      disableAutoFocus,
      disableBackdropClick,
      disableEnforceFocus,
      disableEscapeKeyDown,
      disableRestoreFocus,
      hideBackdrop,
      keepMounted,
      onBackdropClick,
      onClose,
      onEscapeKeyDown,
      onRendered,
      open,
      manager
    } = _props,
          other = _objectWithoutProperties(_props, ["BackdropComponent", "BackdropProps", "children", "classes", "className", "container", "disableAutoFocus", "disableBackdropClick", "disableEnforceFocus", "disableEscapeKeyDown", "disableRestoreFocus", "hideBackdrop", "keepMounted", "onBackdropClick", "onClose", "onEscapeKeyDown", "onRendered", "open", "manager"]);

    const {
      exited
    } = this.state;
    const hasTransition = getHasTransition(this.props);
    const childProps = {};

    if (!keepMounted && !open && (!hasTransition || exited)) {
      return null;
    } // It's a Transition like component


    if (hasTransition) {
      childProps.onExited = createChainedFunction(this.handleExited, children.props.onExited);
    }

    if (children.props.role === undefined) {
      childProps.role = children.props.role || 'document';
    }

    if (children.props.tabIndex === undefined) {
      childProps.tabIndex = children.props.tabIndex || '-1';
    }

    return React.createElement(Portal, {
      ref: node => {
        this.mountNode = node ? node.getMountNode() : node;
      },
      container: container,
      onRendered: this.handleRendered
    }, React.createElement("div", _extends({
      ref: node => {
        this.modalNode = node;
      },
      className: classNames(classes.root, className, {
        [classes.hidden]: exited
      })
    }, other), hideBackdrop ? null : React.createElement(BackdropComponent, _extends({
      open: open,
      onClick: this.handleBackdropClick
    }, BackdropProps)), React.createElement(RootRef, {
      rootRef: node => {
        this.dialogNode = node;
      }
    }, React.cloneElement(children, childProps))));
  }

}

Modal.propTypes = process.env.NODE_ENV !== "production" ? {
  /**
   * A backdrop component. This property enables custom backdrop rendering.
   */
  BackdropComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object]),

  /**
   * Properties applied to the `Backdrop` element.
   */
  BackdropProps: PropTypes.object,

  /**
   * A single child content element.
   */
  children: PropTypes.element,

  /**
   * Override or extend the styles applied to the component.
   * See [CSS API](#css-api) below for more details.
   */
  classes: PropTypes.object.isRequired,

  /**
   * @ignore
   */
  className: PropTypes.string,

  /**
   * A node, component instance, or function that returns either.
   * The `container` will have the portal children appended to it.
   */
  container: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),

  /**
   * If `true`, the modal will not automatically shift focus to itself when it opens, and
   * replace it to the last focused element when it closes.
   * This also works correctly with any modal children that have the `disableAutoFocus` prop.
   *
   * Generally this should never be set to `true` as it makes the modal less
   * accessible to assistive technologies, like screen readers.
   */
  disableAutoFocus: PropTypes.bool,

  /**
   * If `true`, clicking the backdrop will not fire any callback.
   */
  disableBackdropClick: PropTypes.bool,

  /**
   * If `true`, the modal will not prevent focus from leaving the modal while open.
   *
   * Generally this should never be set to `true` as it makes the modal less
   * accessible to assistive technologies, like screen readers.
   */
  disableEnforceFocus: PropTypes.bool,

  /**
   * If `true`, hitting escape will not fire any callback.
   */
  disableEscapeKeyDown: PropTypes.bool,

  /**
   * If `true`, the modal will not restore focus to previously focused element once
   * modal is hidden.
   */
  disableRestoreFocus: PropTypes.bool,

  /**
   * If `true`, the backdrop is not rendered.
   */
  hideBackdrop: PropTypes.bool,

  /**
   * Always keep the children in the DOM.
   * This property can be useful in SEO situation or
   * when you want to maximize the responsiveness of the Modal.
   */
  keepMounted: PropTypes.bool,

  /**
   * A modal manager used to track and manage the state of open
   * Modals. This enables customizing how modals interact within a container.
   */
  manager: PropTypes.object,

  /**
   * Callback fired when the backdrop is clicked.
   */
  onBackdropClick: PropTypes.func,

  /**
   * Callback fired when the component requests to be closed.
   * The `reason` parameter can optionally be used to control the response to `onClose`.
   *
   * @param {object} event The event source of the callback
   * @param {string} reason Can be:`"escapeKeyDown"`, `"backdropClick"`
   */
  onClose: PropTypes.func,

  /**
   * Callback fired when the escape key is pressed,
   * `disableEscapeKeyDown` is false and the modal is in focus.
   */
  onEscapeKeyDown: PropTypes.func,

  /**
   * Callback fired once the children has been mounted into the `container`.
   * It signals that the `open={true}` property took effect.
   */
  onRendered: PropTypes.func,

  /**
   * If `true`, the modal is open.
   */
  open: PropTypes.bool.isRequired
} : {};
Modal.defaultProps = {
  disableAutoFocus: false,
  disableBackdropClick: false,
  disableEnforceFocus: false,
  disableEscapeKeyDown: false,
  disableRestoreFocus: false,
  hideBackdrop: false,
  keepMounted: false,
  // Modals don't open on the server so this won't conflict with concurrent requests.
  manager: new ModalManager(),
  BackdropComponent: Backdrop
};
export default withStyles(styles, {
  flip: false,
  name: 'MuiModal'
})(Modal);