/**
 * Copyright (c) Nicolas Gallagher.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import { useMergeRefs } from '@tamagui/react-native-web-internals'
import * as React from 'react'

import type { ViewProps } from '../View/index'
import { View } from '../View/index'

type Props = ViewProps & {
  onMomentumScrollBegin?: (e: any) => void
  onMomentumScrollEnd?: (e: any) => void
  onScroll?: (e: any) => void
  onScrollBeginDrag?: (e: any) => void
  onScrollEndDrag?: (e: any) => void
  onTouchMove?: (e: any) => void
  onWheel?: (e: any) => void
  scrollEnabled?: boolean
  scrollEventThrottle?: number
  showsHorizontalScrollIndicator?: boolean
  showsVerticalScrollIndicator?: boolean
}

function normalizeScrollEvent(e) {
  return {
    nativeEvent: {
      contentOffset: {
        get x() {
          return e.target.scrollLeft
        },
        get y() {
          return e.target.scrollTop
        },
      },
      contentSize: {
        get height() {
          return e.target.scrollHeight
        },
        get width() {
          return e.target.scrollWidth
        },
      },
      layoutMeasurement: {
        get height() {
          return e.target.offsetHeight
        },
        get width() {
          return e.target.offsetWidth
        },
      },
    },
    timeStamp: Date.now(),
  }
}

function shouldEmitScrollEvent(lastTick: number, eventThrottle: number) {
  const timeSinceLastTick = Date.now() - lastTick
  return eventThrottle > 0 && timeSinceLastTick >= eventThrottle
}

/**
 * Encapsulates the Web-specific scroll throttling and disabling logic
 */
const ScrollViewBase = React.forwardRef((props: Props, forwardedRef) => {
  const {
    onScroll,
    onTouchMove,
    onWheel,
    scrollEnabled = true,
    scrollEventThrottle = 0,
    showsHorizontalScrollIndicator,
    showsVerticalScrollIndicator,
    style,
    // strip RN-only props that shouldn't reach the DOM
    onMomentumScrollBegin,
    onMomentumScrollEnd,
    onScrollBeginDrag,
    onScrollEndDrag,
    ...rest
  } = props

  const scrollState = React.useRef({ isScrolling: false, scrollLastTick: 0 })
  const scrollTimeout = React.useRef(null)
  const scrollRef = React.useRef(null)

  function createPreventableScrollHandler(handler: Function) {
    return (e: object) => {
      if (scrollEnabled) {
        if (handler) {
          handler(e)
        }
      }
    }
  }

  function handleScroll(e: any) {
    e.stopPropagation()
    if (e.target === scrollRef.current) {
      e.persist()
      // A scroll happened, so the scroll resets the scrollend timeout.
      if (scrollTimeout.current != null) {
        clearTimeout(scrollTimeout.current)
      }
      // @ts-ignore
      scrollTimeout.current = setTimeout(() => {
        handleScrollEnd(e)
      }, 100)
      if (scrollState.current.isScrolling) {
        // Scroll last tick may have changed, check if we need to notify
        if (
          shouldEmitScrollEvent(scrollState.current.scrollLastTick, scrollEventThrottle)
        ) {
          handleScrollTick(e)
        }
      } else {
        // Weren't scrolling, so we must have just started
        handleScrollStart(e)
      }
    }
  }

  function handleScrollStart(e: object) {
    scrollState.current.isScrolling = true
    handleScrollTick(e)
  }

  function handleScrollTick(e: object) {
    scrollState.current.scrollLastTick = Date.now()
    if (onScroll) {
      onScroll(normalizeScrollEvent(e))
    }
  }

  function handleScrollEnd(e: object) {
    scrollState.current.isScrolling = false
    if (onScroll) {
      onScroll(normalizeScrollEvent(e))
    }
  }

  const hideHorizontalScrollbar = showsHorizontalScrollIndicator === false
  const hideVerticalScrollbar = showsVerticalScrollIndicator === false

  return (
    <View
      {...rest}
      className={
        '_dsp_contents' +
        (hideHorizontalScrollbar ? ' _hsb-x' : '') +
        (hideVerticalScrollbar ? ' _hsb-y' : '')
      }
      onScroll={handleScroll}
      onTouchMove={createPreventableScrollHandler(onTouchMove as any)}
      onWheel={createPreventableScrollHandler(onWheel as any)}
      ref={useMergeRefs(scrollRef, forwardedRef)}
      style={[
        style,
        // @ts-ignore
        !scrollEnabled && styles.scrollDisabled,
      ]}
    />
  )
})

// Chrome doesn't support e.preventDefault in this case; touch-action must be
// used to disable scrolling.
// https://developers.google.com/web/updates/2017/01/scrolling-intervention
const styles = {
  scrollDisabled: {
    overflowX: 'hidden',
    overflowY: 'hidden',
    touchAction: 'none',
  },
}

export { ScrollViewBase }
export default ScrollViewBase
