import React from 'react'
import PropTypes from 'prop-types'
import debounce from 'lodash.debounce'

import { convertXYtoLatLong } from 'Map'
import { connectToContext } from 'Provider'
import ContextMenuCoordinateGroup from './ContextMenuCoords'

import { Container } from './styled'

/** A context menu component useful for contextual geospatial actions
 * @component
 * @category ContextMenu
 * @since 0.16.0
 */
class ContextMenu extends React.Component {
  constructor (props) {
    super(props)

    this.state = {
      show: false,
      showSnackbar: false,
      pixel: { x: 0, y: 0 }
    }

    // debounce time was changed from 400 to 50 due to inaccurate pointer/coords location
    this.pointerMoveHandler = debounce(this.pointerMoveHandler, 50)
  }

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

    // bind to right click events
    document.addEventListener('contextmenu', this.rightClickHandler)

    // bind to any old click events
    document.addEventListener('click', this.clickHandler)

    // bind to pointer move to get accurate lat/long off of mouse location
    map.on('pointermove', this.pointerMoveHandler)
  }

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

    document.removeEventListener('contextmenu', this.rightClickHandler)
    document.removeEventListener('click', this.clickHandler)
    map.un('pointermove', this.pointerMoveHandler)
  }

  pointerMoveHandler = e => {
    if (!e.dragging) this.setState({ pointerX: e.pixel[0], pointerY: e.pixel[1] })
  }

  clickHandler = e => {
    // if not within the map, another menu handles the event
    if (!this.isWithinMap(e)) return

    this.setState({ show: false })
  }

  isWithinMap = e => {
    const { map } = this.props
    const rect = map.getTargetElement().getBoundingClientRect()

    return e.x >= rect.left && e.x <= rect.right &&
      e.y >= rect.top && e.y <= rect.bottom
  }

  rightClickHandler = e => {
    const { map } = this.props
    const { pointerX, pointerY } = this.state

    // if we're not within the map, another context menu will handle or if we're not on a canvas we ignore event
    if (!this.isWithinMap(e) || e.target.tagName !== 'CANVAS') return

    // prevent the default browser context menu from showing
    e.preventDefault()

    // get the latitude/longitude
    const { latitude: lat, longitude: long } = convertXYtoLatLong(map, pointerX, pointerY)

    // get any features at the event location (in OL v4 no features returns null which is bad; switch to empty array)
    const features = map.getFeaturesAtPixel([pointerX, pointerY])

    this.setState({ show: true, features, pixel: { x: e.x, y: e.y }, coords: { lat, long } })
  }

  closeContextMenu = msg => {
    this.setState({
      show: false,
      showSnackbar: !!msg, // only show snackbar if there's a message passed
      snackbarMessage: msg
    })
  }

  onCopy = copiedFeatures => {
    this.setState({ copiedFeatures })
  }

  render () {
    const { children, map, keepDefaults } = this.props
    const { show, pixel, features, coords } = this.state

    // we render children if passed; otherwise, we default to a helpful context menu
    const getChildren = () => {
      const props = { map, pixel, coords, features, closeContextMenu: this.closeContextMenu }
      const defaults = [
        <ContextMenuCoordinateGroup key={'coordgroup'} {...props} />
      ]
      // this logic allows defaults, custom or a mix (defaults render on top & custom below)
      const contents = children ? [...(keepDefaults ? defaults : []), ...React.Children.toArray(children)] : defaults

      return React.Children.map(contents, c => React.cloneElement(c, props))
    }

    return (
      <React.Fragment>
        <Container
          show={show}
          top={pixel.y}
          left={pixel.x}
          innerRef={node => { this.contextMenuRef = node }}>
          {show && getChildren()}
        </Container>
      </React.Fragment>
    )
  }
}

ContextMenu.propTypes = {
  /** A map which is used to compute the click location */
  map: PropTypes.object.isRequired,

  /** A set of components (like `ContextMenuCoords` or custom items made with `ContextMenuListItem`) which perform actions when clicked */
  children: PropTypes.node,

  /** A boolean to indicate if the default context menu items should be left and custom items appended only */
  keepDefaults: PropTypes.bool
}

ContextMenu.defaultProps = {
  keepDefaults: false
}

export default connectToContext(ContextMenu)