import React from 'react'
import { Animated, Platform, StyleSheet, View } from 'react-native'
import propTypes from 'prop-types'
import { StackView } from '../StackView/StackView'
import { useThemeTokens } from '../ThemeProvider/useThemeTokens'
import { a11yProps } from '../utils/props/a11yProps'
import { getTokensPropType } from '../utils/props/tokens'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { responsiveProps } from '../utils/props/responsiveProps'
import { useResponsiveProp } from '../utils/useResponsiveProp'
import { useSpacingScale } from '../utils/useSpacingScale'
import { spacingProps } from '../utils/props/spacingProps'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { useSkeletonNativeAnimation } from './useSkeletonNativeAnimation'
import { skeletonWebAnimation } from './skeletonWebAnimation'

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

const selectSkeletonStyles = ({ color, radius }) => ({
  backgroundColor: color,
  borderRadius: radius,
  maxWidth: '100%'
})

const selectLineStyles = ({ skeletonHeight, lineWidth, radius }) => ({
  width: lineWidth,
  height: skeletonHeight,
  borderRadius: radius
})

const selectShapeStyles = ({ skeletonHeight }) => ({
  height: skeletonHeight,
  width: skeletonHeight
})

const selectSquareStyles = ({ radius }) => ({
  borderRadius: radius
})

export const Skeleton = React.forwardRef(
  ({ tokens, variant, sizeIndex, sizePixels, characters, lines, shape = 'line', ...rest }, ref) => {
    const themeTokens = useThemeTokens('Skeleton', tokens, variant)
    const pixels = useResponsiveProp(sizePixels)
    const spacingScaleValue =
      typeof pixels === 'number'
        ? // Size by an exact number of pixels
          { options: { size: pixels } }
        : // Size by an index on the spacing scale (getting default index from theme if none provided)
          sizeIndex || themeTokens.size
    const skeletonHeight = useSpacingScale(spacingScaleValue)

    // Animation color and animation duration
    const { secondColor, animationDuration } = themeTokens

    const nativeAnimation = useSkeletonNativeAnimation({ animationDuration })

    const getAnimationBasedOnPlatform = () => {
      const animation =
        Platform.OS === 'web'
          ? skeletonWebAnimation(secondColor, animationDuration)
          : nativeAnimation
      // We must pass the animation styles through `StyleSheet.create`
      // @see https://github.com/necolas/react-native-web/issues/2387
      const styles = StyleSheet.create({ animation })

      return styles.animation
    }

    const getLineWidth = () => {
      if (characters) {
        return characters * themeTokens.baseWidth
      }

      return themeTokens.characters * themeTokens.baseWidth
    }

    const getStyledBasedOnShape = () => {
      if (shape === 'circle') {
        return selectShapeStyles({ skeletonHeight })
      }

      if (shape === 'box') {
        return [
          selectShapeStyles({ skeletonHeight }),
          selectSquareStyles({ radius: themeTokens.squareRadius })
        ]
      }

      return selectLineStyles({
        skeletonHeight,
        lineWidth: getLineWidth(),
        radius: themeTokens.lineRadius
      })
    }

    const renderSkeleton = (index = 0) => {
      // @see https://github.com/necolas/react-native-web/issues/2387
      const Component = Platform.OS === 'web' ? View : Animated.View

      return (
        <Component
          testID="skeleton"
          key={`skeleton-${index + 1}`}
          style={[
            selectSkeletonStyles(themeTokens),
            getAnimationBasedOnPlatform(),
            getStyledBasedOnShape()
          ]}
        />
      )
    }

    return (
      <StackView space={themeTokens.spaceBetweenLines} ref={ref} {...selectProps(rest)}>
        {lines > 1 ? [...Array(lines)].map((_, index) => renderSkeleton(index)) : renderSkeleton()}
      </StackView>
    )
  }
)
Skeleton.displayName = 'Skeleton'

Skeleton.propTypes = {
  ...selectedSystemPropTypes,
  tokens: getTokensPropType('Skeleton'),
  variant: variantProp.propType,
  /**
   * Sets the size of Skeleton lines or shape according to the theme's spacing scale. For example, size={1} gives the smallest non-zero theme-defined spacing size.
   *
   * May also accept an object with responsive viewport keys or spacing scale options - see `useSpacingScale` for details.
   */
  sizeIndex: spacingProps.types.spacingValue,
  /**
   * Sets the size of Skeleton lines or shape to an exact number of pixels. Use when it's necessary to exactly match sizes of images or other boxes.
   *
   * Accepts a number or an object with responsive viewport keys, e.g. { xs: 32, lg: 64 } would be 32px at xs, sm and md and 64 at lg and xl viewports.
   */
  sizePixels: responsiveProps.getTypeOptionallyByViewport(propTypes.number),
  /**
   * Determines the width of simulated lines of text if the Skeleton's shape is 'line' (the default shape).
   *
   * Only has any affect if shape is line (the default). If unset, takes a default value from the theme.
   */
  characters: propTypes.number,
  /**
   * Determines how many Skeleton items are rendered (default 1).
   *
   * Recommended usage is to simulate paragraphs of text when Skeleton's shape is 'line' (the default shape).
   *
   * The amount of spacing between multiple lines is controlled by theme tokens.
   */
  lines: propTypes.number,
  /**
   * Determines if the skeleton should resemble lines of text (default), a circle, or a square box with themed rounded corners.
   */
  shape: propTypes.oneOf(['line', 'circle', 'box'])
}
