import * as Haptics from 'expo-haptics'
import * as _ from 'lodash'
import React, { useCallback, useRef, useState } from 'react'
import { GestureResponderEvent, View } from 'react-native'

export const BarChartTouchWrapper: React.FC<{
  numBars: number
  onHoverBar?: (barIndex: number) => void
  onHoverBarEnd?: (barIndex: number) => void
}> = ({ numBars, onHoverBar, onHoverBarEnd, children }) => {
  const [viewWidth, setViewWidth] = useState<number | null>(null)
  const isHovering = useRef<boolean>(false)
  const index = useRef<number | null>(null)
  const hoverStartTimeout = useRef<NodeJS.Timeout | null>(null)
  const hoverStartLocation = useRef<{ x: number; y: number } | null>(null)
  const onLayout = useCallback((event) => {
    setViewWidth(event.nativeEvent.layout.width)
  }, [])

  const itemWidth = viewWidth != null ? viewWidth / numBars : null
  const getIndex = (x: number): number | null =>
    itemWidth != null
      ? _.clamp(Math.floor(x / itemWidth), 0, numBars - 1)
      : null

  const onStartHovering = () => {
    Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy)
    isHovering.current = true
    if (index.current != null) {
      onHoverBar?.(index.current)
    }
  }

  const handleGestureResponderEvent = (event: GestureResponderEvent) => {
    const newIndex = getIndex(event.nativeEvent.locationX)
    if (newIndex == null) {
      return
    }
    if (isHovering.current && newIndex !== index.current) {
      onHoverBar?.(newIndex)
      Haptics.selectionAsync()
    }
    index.current = newIndex
  }

  return (
    <View
      style={[{ flex: 1 }]}
      onLayout={onLayout}
      onStartShouldSetResponder={(event) => {
        hoverStartLocation.current = {
          x: event.nativeEvent.locationX,
          y: event.nativeEvent.locationY,
        }
        hoverStartTimeout.current = setTimeout(onStartHovering, 370)
        return true
      }}
      onResponderStart={handleGestureResponderEvent}
      onResponderMove={(event) => {
        if (hoverStartLocation.current) {
          const dX = event.nativeEvent.locationX - hoverStartLocation.current.x
          const dY = event.nativeEvent.locationY - hoverStartLocation.current.y
          const dist = Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2))
          if (dist > 20) {
            if (hoverStartTimeout.current) {
              clearTimeout(hoverStartTimeout.current)
            }
            hoverStartLocation.current = null
          }
        }

        handleGestureResponderEvent(event)
      }}
      onResponderTerminationRequest={() => false}
      onResponderRelease={(event) => {
        const newIndex = getIndex(event.nativeEvent.locationX)
        if (newIndex == null) {
          return
        }
        onHoverBarEnd?.(newIndex)
        isHovering.current = false
        if (hoverStartTimeout.current) {
          clearTimeout(hoverStartTimeout.current)
        }
      }}
    >
      {children}
    </View>
  )
}
