import React from 'react'
import ReactDOM from 'react-dom'
import { createPortal } from 'react-dom'
import { Map } from 'immutable'

const TOOLTIP_ROOT = document.getElementById('tooltip-root')

interface Props {
  position?: TooltipPosition
}

export class Controller extends React.PureComponent<Props> {
  static defaultProps = {
    position: 'top-right'
  }

  tooltip = React.createRef<HTMLSpanElement>()
  trigger = React.createRef<React.ReactInstance>()

  state = {
    isVisible: false,
    tooltipDimensions: Map({
      width: 0,
      height: 0
    }),
    style: Map({
      position: 'absolute' as 'absolute',
      left: 0,
      top: 0,
    }),
    triggerRect: Map({
      left: 0,
      top: 0,
      width: 0,
      height: 0
    })
  }

  handleMouseEnter = () => {
    this.setState({ 
      isVisible: true,
      style: this.state.style.merge(this.getPosition())
    })
    
  }

  handleMouseLeave = () => {
    this.setState({ isVisible: false })
  }

  componentDidUpdate(prevProps, prevState) {
    if (this.state.isVisible) {
      const { width, height } = (ReactDOM.findDOMNode(this.tooltip.current) as HTMLElement).getBoundingClientRect()
      const { style, tooltipDimensions } = this.state

      this.setState({
        tooltipDimensions: tooltipDimensions.merge({ width, height }),
        style: style.merge(this.getPosition())
      })
    }
  }

  getPosition = (): { top: number, left: number } => {
    const { tooltipDimensions, triggerRect } = this.state
    return calculatePosition(this.props.position, triggerRect, tooltipDimensions)
  }

  updateTriggerRect = (triggerRect: { left: number, top: number, width: number, height: number }) => {
    this.setState({
      triggerRect: this.state.triggerRect.merge(triggerRect)
    })
  }

  render() {
    const { children } = this.props
    const { isVisible, style } = this.state

    return React.Children.map(children, (child: React.ReactElement) => {
      const name = (child.type as any).name
      if (name === 'Trigger') {
        return React.cloneElement(child, {
          ref: this.trigger,
          resize: this.updateTriggerRect,
          onMouseEnter: this.handleMouseEnter,
          onMouseLeave: this.handleMouseLeave,
        })
      } else {
        // Tooltip
        return isVisible && createPortal(
          <span style={style.toJS()} ref={this.tooltip}>
            {React.cloneElement(child)}
          </span>, 
          TOOLTIP_ROOT
        )
      }
    })
  }
}

const TOOLTIP_BOUNDING_PADDING = 10

const calculatePosition = (pos: TooltipPosition, trigger: Map<string, number>, tooltip: Map<string, number>) => {
  const { innerWidth: windowWidth } = window
  let top = 0
  let left = 0

  const triggerTop = trigger.get('top') + window.pageYOffset

  // Top
  switch (pos) {
    case 'top-left': 
    case 'top-right': {
      top = triggerTop - (tooltip.get('height') + 5)
      break
    }

    case 'bottom-left':
    case 'bottom-right': {
      top = triggerTop + trigger.get('height') + 5
      break
    }
  }

  // Left
  switch (pos) {
    case 'left':
    case 'bottom-left':
    case 'top-left': {
      left = (trigger.get('left')) - tooltip.get('width')
      break
    }

    case 'right': 
    case 'bottom-right':
    case 'top-right': {
      left = (trigger.get('left')) + trigger.get('width')
      break
    }
  }

  const overflowsRight = left + tooltip.get('width') + TOOLTIP_BOUNDING_PADDING > windowWidth
  const overflowsTop = top - (window.scrollY + TOOLTIP_BOUNDING_PADDING) < 0

  // Validate
  switch (pos) {
    case 'top-right': {
      if (overflowsRight && !overflowsTop) return calculatePosition('top-left', trigger, tooltip)
      if (!overflowsRight && overflowsTop) return calculatePosition('bottom-right', trigger, tooltip)
      if (overflowsRight && overflowsTop) return calculatePosition('bottom-left', trigger, tooltip)
      break
    }

    case 'top-left': {
      break
    }
  }

  return { top, left }
}


