import CLASS from './class'
import { ChartInternal } from './core'

ChartInternal.prototype.initEventRect = function() {
  var $$ = this,
    config = $$.config

  $$.main
    .select('.' + CLASS.chart)
    .append('g')
    .attr('class', CLASS.eventRects)
    .style('fill-opacity', 0)
  $$.eventRect = $$.main
    .select('.' + CLASS.eventRects)
    .append('rect')
    .attr('class', CLASS.eventRect)

  // event rect handle zoom event as well
  if (config.zoom_enabled && $$.zoom) {
    $$.eventRect.call($$.zoom).on('dblclick.zoom', null)
    if (config.zoom_initialRange) {
      // WORKAROUND: Add transition to apply transform immediately when no subchart
      $$.eventRect
        .transition()
        .duration(0)
        .call($$.zoom.transform, $$.zoomTransform(config.zoom_initialRange))
    }
  }
}
ChartInternal.prototype.redrawEventRect = function() {
  const $$ = this,
    d3 = $$.d3,
    config = $$.config

  function mouseout() {
    $$.svg.select('.' + CLASS.eventRect).style('cursor', null)
    $$.hideXGridFocus()
    $$.hideTooltip()
    $$.unexpandCircles()
    $$.unexpandBars()
  }

  const isHoveringDataPoint = (mouse, closest) =>
    closest &&
    ($$.isBarType(closest.id) ||
      $$.dist(closest, mouse) < config.point_sensitivity)

  const withName = d => (d ? $$.addName(Object.assign({}, d)) : null)

  // rects for mouseover
  $$.main
    .select('.' + CLASS.eventRects)
    .style(
      'cursor',
      config.zoom_enabled
        ? config.axis_rotated
          ? 'ns-resize'
          : 'ew-resize'
        : null
    )

  $$.eventRect
    .attr('x', 0)
    .attr('y', 0)
    .attr('width', $$.width)
    .attr('height', $$.height)
    .on(
      'mouseout',
      config.interaction_enabled
        ? function() {
            if (!config) {
              return
            } // chart is destroyed
            if ($$.hasArcType()) {
              return
            }
            if ($$.mouseover) {
              config.data_onmouseout.call($$.api, $$.mouseover)
              $$.mouseover = undefined
            }
            mouseout()
          }
        : null
    )
    .on(
      'mousemove',
      config.interaction_enabled
        ? function() {
            // do nothing when dragging
            if ($$.dragging) {
              return
            }

            const targetsToShow = $$.getTargetsToShow()

            // do nothing if arc type
            if ($$.hasArcType(targetsToShow)) {
              return
            }

            const mouse = d3.mouse(this)
            const closest = withName(
              $$.findClosestFromTargets(targetsToShow, mouse)
            )
            const isMouseCloseToDataPoint = isHoveringDataPoint(mouse, closest)

            // ensure onmouseout is always called if mousemove switch between 2 targets
            if (
              $$.mouseover &&
              (!closest ||
                closest.id !== $$.mouseover.id ||
                closest.index !== $$.mouseover.index)
            ) {
              config.data_onmouseout.call($$.api, $$.mouseover)
              $$.mouseover = undefined
            }
            if (closest && !$$.mouseover) {
              config.data_onmouseover.call($$.api, closest)
              $$.mouseover = closest
            }

            // show cursor as pointer if we're hovering a data point close enough
            $$.svg
              .select('.' + CLASS.eventRect)
              .style('cursor', isMouseCloseToDataPoint ? 'pointer' : null)

            // if tooltip not grouped, we want to display only data from closest data point
            const showSingleDataPoint =
              !config.tooltip_grouped || $$.hasType('stanford', targetsToShow)

            // find data to highlight
            let selectedData
            if (showSingleDataPoint) {
              if (closest) {
                selectedData = [closest]
              }
            } else {
              let closestByX
              if (closest) {
                // reuse closest value
                closestByX = closest
              } else {
                // try to find the closest value by X values from the mouse position
                const mouseX = config.axis_rotated ? mouse[1] : mouse[0]
                closestByX = $$.findClosestFromTargetsByX(
                  targetsToShow,
                  $$.x.invert(mouseX)
                )
              }

              // highlight all data for this 'x' value
              if (closestByX) {
                selectedData = $$.filterByX(targetsToShow, closestByX.x)
              }
            }

            // ensure we have data to show
            if (!selectedData || selectedData.length === 0) {
              return mouseout()
            }

            // inject names for each point
            selectedData = selectedData.map(withName)

            // show tooltip
            $$.showTooltip(selectedData, this)

            // expand points
            if (config.point_focus_expand_enabled) {
              $$.unexpandCircles()
              selectedData.forEach(function(d) {
                $$.expandCircles(d.index, d.id, false)
              })
            }

            // expand bars
            $$.unexpandBars()
            selectedData.forEach(function(d) {
              $$.expandBars(d.index, d.id, false)
            })

            // Show xgrid focus line
            $$.showXGridFocus(selectedData)
          }
        : null
    )
    .on(
      'click',
      config.interaction_enabled
        ? function() {
            const targetsToShow = $$.getTargetsToShow()

            if ($$.hasArcType(targetsToShow)) {
              return
            }

            const mouse = d3.mouse(this)
            const closest = withName(
              $$.findClosestFromTargets(targetsToShow, mouse)
            )

            if (!isHoveringDataPoint(mouse, closest)) {
              return
            }

            // select if selection enabled
            let sameXData
            if (!config.data_selection_grouped || $$.isStanfordType(closest)) {
              sameXData = [closest]
            } else {
              sameXData = $$.filterByX(targetsToShow, closest.x)
            }

            // toggle selected state
            sameXData.forEach(function(d) {
              $$.main
                .selectAll(
                  '.' + CLASS.shapes + $$.getTargetSelectorSuffix(d.id)
                )
                .selectAll('.' + CLASS.shape + '-' + d.index)
                .each(function() {
                  if (
                    config.data_selection_grouped ||
                    $$.isWithinShape(this, d)
                  ) {
                    $$.toggleShape(this, d, d.index)
                  }
                })
            })

            // call data_onclick on the closest data point
            if (closest) {
              const shape = $$.main
                .selectAll(
                  '.' + CLASS.shapes + $$.getTargetSelectorSuffix(closest.id)
                )
                .select('.' + CLASS.shape + '-' + closest.index)
              config.data_onclick.call($$.api, closest, shape.node())
            }
          }
        : null
    )
    .call(
      config.interaction_enabled && config.data_selection_draggable && $$.drag
        ? d3
            .drag()
            .on('drag', function() {
              $$.drag(d3.mouse(this))
            })
            .on('start', function() {
              $$.dragstart(d3.mouse(this))
            })
            .on('end', function() {
              $$.dragend()
            })
        : function() {}
    )
}
ChartInternal.prototype.getMousePosition = function(data) {
  var $$ = this
  return [$$.x(data.x), $$.getYScale(data.id)(data.value)]
}
ChartInternal.prototype.dispatchEvent = function(type, mouse) {
  var $$ = this,
    selector = '.' + CLASS.eventRect,
    eventRect = $$.main.select(selector).node(),
    box = eventRect.getBoundingClientRect(),
    x = box.left + (mouse ? mouse[0] : 0),
    y = box.top + (mouse ? mouse[1] : 0),
    event = document.createEvent('MouseEvents')

  event.initMouseEvent(
    type,
    true,
    true,
    window,
    0,
    x,
    y,
    x,
    y,
    false,
    false,
    false,
    false,
    0,
    null
  )
  eventRect.dispatchEvent(event)
}
