import cx from 'classnames'
import _ from 'lodash'
import PropTypes from 'prop-types'
import React, { Component } from 'react'

import Ref from '../../addons/Ref'
import {
  childrenUtils,
  customPropTypes,
  doesNodeContainClick,
  eventStack,
  getUnhandledProps,
  getElementType,
  useKeyOnly,
} from '../../lib'
import SidebarPushable from './SidebarPushable'
import SidebarPusher from './SidebarPusher'

/**
 * A sidebar hides additional content beside a page.
 */
class Sidebar extends Component {
  static propTypes = {
    /** An element type to render as (string or function). */
    as: customPropTypes.as,

    /** Animation style. */
    animation: PropTypes.oneOf([
      'overlay',
      'push',
      'scale down',
      'uncover',
      'slide out',
      'slide along',
    ]),

    /** Primary content. */
    children: PropTypes.node,

    /** Additional classes. */
    className: PropTypes.string,

    /** Shorthand for primary content. */
    content: customPropTypes.contentShorthand,

    /** Direction the sidebar should appear on. */
    direction: PropTypes.oneOf(['top', 'right', 'bottom', 'left']),

    /** Duration of sidebar animation. */
    duration: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),

    /**
     * Called before a sidebar begins to animate out.
     *
     * @param {SyntheticEvent} event - React's original SyntheticEvent.
     * @param {object} data - All props.
     */
    onHide: PropTypes.func,

    /**
     * Called after a sidebar has finished animating out.
     *
     * @param {null}
     * @param {object} data - All props.
     */
    onHidden: PropTypes.func,

    /**
     * Called when a sidebar has finished animating in.
     *
     * @param {null}
     * @param {object} data - All props.
     */
    onShow: PropTypes.func,

    /**
     * Called when a sidebar begins animating in.
     *
     * @param {null}
     * @param {object} data - All props.
     */
    onVisible: PropTypes.func,

    /** Controls whether or not the sidebar is visible on the page. */
    visible: PropTypes.bool,

    /** Sidebar width. */
    width: PropTypes.oneOf(['very thin', 'thin', 'wide', 'very wide']),
  }

  static defaultProps = {
    direction: 'left',
    duration: 500,
  }

  static autoControlledProps = ['visible']

  static Pushable = SidebarPushable
  static Pusher = SidebarPusher

  state = {}

  componentDidMount() {
    const { visible } = this.props

    if (visible) this.addListener()
  }

  componentWillUnmount() {
    const { visible } = this.props

    if (visible) this.removeListener()
    clearTimeout(this.animationTimer)
  }

  componentDidUpdate(prevProps) {
    const { visible: prevVisible } = prevProps
    const { visible: currentVisible } = this.props

    if (prevVisible === currentVisible) return

    this.handleAnimationStart()

    if (currentVisible) {
      this.addListener()
      return
    }

    this.removeListener()
  }

  addListener() {
    eventStack.sub('click', this.handleDocumentClick)
  }

  removeListener() {
    eventStack.unsub('click', this.handleDocumentClick)
  }

  handleAnimationStart = () => {
    const { duration, visible } = this.props
    const callback = visible ? 'onVisible' : 'onHide'

    this.setState({ animating: true }, () => {
      clearTimeout(this.animationTimer)
      this.animationTimer = setTimeout(this.handleAnimationEnd, duration)

      if (this.skipNextCallback) {
        this.skipNextCallback = false
        return
      }

      _.invoke(this.props, callback, null, this.props)
    })
  }

  handleAnimationEnd = () => {
    const { visible } = this.props
    const callback = visible ? 'onShow' : 'onHidden'

    this.setState({ animating: false })
    _.invoke(this.props, callback, null, this.props)
  }

  handleDocumentClick = (e) => {
    if (!doesNodeContainClick(this.ref, e)) {
      this.skipNextCallback = true
      _.invoke(this.props, 'onHide', e, { ...this.props, visible: false })
    }
  }

  handleRef = c => (this.ref = c)

  render() {
    const { animation, className, children, content, direction, visible, width } = this.props
    const { animating } = this.state

    const classes = cx(
      'ui',
      animation,
      direction,
      width,
      useKeyOnly(animating, 'animating'),
      useKeyOnly(visible, 'visible'),
      'sidebar',
      className,
    )
    const rest = getUnhandledProps(Sidebar, this.props)
    const ElementType = getElementType(Sidebar, this.props)

    return (
      <Ref innerRef={this.handleRef}>
        <ElementType {...rest} className={classes}>
          {childrenUtils.isNil(children) ? content : children}
        </ElementType>
      </Ref>
    )
  }
}

export default Sidebar

@@ -1,12 +1,68 @@
+import EventStack from '@semantic-ui-react/event-stack'%0A
 import cx fr
@@ -285,22 +285,8 @@
 ck,%0A
-  eventStack,%0A
   ge
@@ -2421,254 +2421,8 @@
 %7B%7D%0A%0A
-  componentDidMount() %7B%0A    const %7B visible %7D = this.props%0A%0A    if (visible) this.addListener()%0A  %7D%0A%0A  componentWillUnmount() %7B%0A    const %7B visible %7D = this.props%0A%0A    if (visible) this.removeListener()%0A    clearTimeout(this.animationTimer)%0A  %7D%0A%0A
   co
@@ -2570,17 +2570,17 @@
 Visible 
-=
+!
 == curre
@@ -2593,20 +2593,8 @@
 ble)
- return%0A%0A   
  thi
@@ -2622,263 +2622,76 @@
 t()%0A
-%0A
   
-  if (currentVisible) %7B%0A      this.addListener()%0A      return%0A    %7D%0A%0A    this.removeListener()%0A  %7D%0A%0A  addListener() %7B%0A    eventStack.sub('click', 
+%7D%0A%0A  componentWillUnmount() %7B%0A    clearTimeout(
 this.
-h
 an
-dleDocumentClick)%0A  %7D%0A%0A  removeListener() %7B%0A    eventStack.unsub('click', this.handleDocumentClick
+imationTimer
 )%0A  
@@ -4236,16 +4236,97 @@
 ildren%7D%0A
+          %7Bvisible && %3CEventStack name='click' on=%7Bthis.handleDocumentClick%7D /%3E%7D%0A
         

use EventStack component (#2990)