import React from 'react'
import { Platform, Pressable, StyleSheet, Text, View } from 'react-native'
import { propTypes } from './shared'
import { useFloating, arrow, offset, shift, flip, autoPlacement } from '../utils/floating-ui'

import { applyShadowToken, applyTextStyles } from '../ThemeProvider/utils/styles'
import { useThemeTokens } from '../ThemeProvider/useThemeTokens'
import { a11yProps } from '../utils/props/a11yProps'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { selectTokens } from '../utils/props/tokens'
import { viewProps } from '../utils/props/viewProps'
import { TooltipButton } from '../TooltipButton/TooltipButton'
import { useCopy } from '../utils/useCopy'
import { dictionary } from './dictionary'

const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])

const selectTooltipStyles = ({
  backgroundColor,
  paddingTop,
  paddingBottom,
  paddingLeft,
  paddingRight,
  borderRadius
}) => ({
  backgroundColor,
  paddingTop,
  paddingBottom,
  paddingLeft,
  paddingRight,
  borderRadius
})
const selectTooltipShadowStyles = ({ shadow, borderRadius }) => ({
  borderRadius,
  ...applyShadowToken(shadow)
})

const selectToolTipSizeStyles = ({ maxWidth, minWidth }) => ({
  maxWidth,
  minWidth
})

const selectMobileTooltipPositionStyles = ({ top, left, position }) => {
  return { top, left, position }
}

const selectMobileArrowStyles = (
  { backgroundColor, arrowWidth, arrowBorderRadius, shadow },
  { placement, x: arrowX, y: arrowY }
) => {
  const staticSide = {
    top: 'bottom',
    right: 'left',
    bottom: 'top',
    left: 'right'
  }[placement.split('-')[0]]

  return {
    backgroundColor,
    width: arrowWidth,
    height: arrowWidth,
    borderBottomRightRadius: arrowBorderRadius, // this corner will be the arrow tip after rotation
    transform: [{ rotateZ: '45deg' }],
    ...applyShadowToken(shadow),
    left: arrowX != null && arrowX,
    top: arrowY != null && arrowY,
    right: '',
    bottom: '',
    [staticSide]: '-4px'
  }
}

const selectTextStyles = (tokens) => applyTextStyles(selectTokens('Typography', tokens))
const defaultControl = (pressableState, variant) => (
  <TooltipButton pressableState={pressableState} variant={variant} />
)

/**
 * Tooltip provides a descriptive and detailed explanation or instructions. It can be used next to an input label
 * to help a user fill it in, or as a standalone component.
 *
 * By default the TooltipButton component will be used as a control for triggering the tooltip, but you may attach
 * a tooltip to any other component. A render function can be used to adjust the control's styling on state changes (hover, focus, etc.).
 *
 * ### Positioning
 * By default a Tooltip will be automatically positioned in a way that ensures it fits within the viewport.
 * You may suggest a position with a prop - it will be used, unless the tooltip would end up outside the viewport.
 *
 * ### Usage criteria
 * - You may use one when the information is useful only to a small percentage of users (ie. tech savvy people wouldn't need this info).
 * - Tooltips may also be useful when vertical space is an issue.
 */
export const Tooltip = React.forwardRef(
  (
    {
      children,
      content,
      position = 'auto',
      copy = 'en',
      onPress = () => {},
      tokens,
      variant,
      inline = false,
      nativeID,
      ...rest
    },
    ref
  ) => {
    const [isOpen, setIsOpen] = React.useState(false)
    const arrowRef = React.useRef()

    const [tooltipDimensions, setTooltipDimensions] = React.useState(null)

    const positions = {
      auto: undefined, // Auto needs to specifically bet set to undefined
      above: 'top',
      right: 'right',
      below: 'bottom',
      left: 'left'
    }

    // "AutoPlacement" can't be used with "flip"
    const additionalMiddleware = position !== 'auto' ? autoPlacement : flip

    const {
      x,
      y,
      reference,
      floating,
      strategy,
      placement,
      middlewareData: { arrow: arrowData = {} }
    } = useFloating({
      placement: positions[position],
      middleware: [shift(), additionalMiddleware(), offset(15), arrow({ element: arrowRef })]
    })

    const getCopy = useCopy({ dictionary, copy })
    const themeTokens = useThemeTokens('Tooltip', tokens, variant)

    const toggleIsOpen = () => {
      onPress()
      setIsOpen(!isOpen)
    }
    const close = () => setIsOpen(false)

    const getPressableState = ({ pressed, hovered, focused }) => ({
      pressed,
      hover: hovered,
      focus: focused
    })

    const onTooltipLayout = ({
      nativeEvent: {
        layout: { width, height }
      }
    }) => {
      if (
        tooltipDimensions === null ||
        tooltipDimensions.width !== width ||
        tooltipDimensions.height !== height
      ) {
        setTooltipDimensions({
          width: Platform.select({
            web: width + 0.3, // avoids often unnecessary line breaks due to subpixel rendering of fonts
            native: width
          }),
          height
        })
      }
    }

    React.useEffect(() => {
      if (!isOpen) {
        setTooltipDimensions(null)
      }
    }, [isOpen])

    const control = children !== undefined ? children : defaultControl
    const pressableStyles =
      control === defaultControl ? Platform.select({ web: { outline: 'none' } }) : undefined
    const pressableHitSlop =
      control === defaultControl ? { top: 10, bottom: 10, left: 10, right: 10 } : undefined

    return (
      <View
        style={[
          staticStyles.container,
          Platform.select({
            web: {
              display: inline ? 'inline-block' : 'flex'
            }
          })
        ]}
        {...selectProps(rest)}
        ref={ref}
      >
        <Pressable
          onPress={toggleIsOpen}
          ref={reference}
          onBlur={close}
          style={pressableStyles}
          hitSlop={pressableHitSlop}
          accessibilityLabel={getCopy('a11yText')}
          accessibilityRole="button"
          nativeID={nativeID}
        >
          {typeof control === 'function'
            ? (pressableState) => control(getPressableState(pressableState), variant)
            : control}
        </Pressable>
        {isOpen && (
          <View
            ref={floating}
            style={[
              selectTooltipShadowStyles(themeTokens),
              staticStyles.tooltip, // applied separately so that it doesn't cover the arrow
              selectToolTipSizeStyles,
              selectMobileTooltipPositionStyles({
                position: strategy,
                top: y ?? 0,
                left: x ?? 0
              })
            ]}
            onLayout={onTooltipLayout}
            accessibilityRole="alert"
          >
            <View
              style={[
                staticStyles.arrow,
                selectMobileArrowStyles(themeTokens, {
                  ...arrowData,
                  placement
                })
              ]}
              ref={arrowRef}
            />
            <View style={selectTooltipStyles(themeTokens)}>
              <Text style={selectTextStyles(themeTokens)}>{content}</Text>
            </View>
          </View>
        )}
      </View>
    )
  }
)
Tooltip.displayName = 'Tooltip'

Tooltip.propTypes = {
  ...selectedSystemPropTypes,
  ...propTypes
}

const staticStyles = StyleSheet.create({
  container: {
    alignItems: 'flex-start',
    position: 'absolute'
  },
  tooltip: {
    position: 'absolute',
    top: 0,
    left: 0
  },
  arrow: {
    position: 'absolute'
  }
})
