import React from 'react'
import PropTypes from 'prop-types'
import { View, StyleSheet } from 'react-native'
import { a11yProps } from '../utils/props/a11yProps'
import { responsiveProps } from '../utils/props/responsiveProps'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { spacingProps } from '../utils/props/spacingProps'
import { useResponsiveProp, resolveResponsiveProp } from '../utils/useResponsiveProp'
import { useSpacingScale } from '../utils/useSpacingScale'
import { viewProps } from '../utils/props/viewProps'
import {
  StyleSheet as StyleSheetUtils,
  createMediaQueryStyles
} from '../utils/ssr-media-query/ssr-media-query'
import { useMediaQuerySpacing } from '../utils/useMediaQuerySpacing'
import { useTheme } from '../ThemeProvider/useTheme'

/**
 * @typedef {import('../utils/props/spacingProps.js').SpacingValue} SpacingValue
 * @typedef {import('../utils/props/spacingProps.js').SpacingObject} SpacingObject
 */

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

const selectSizeStyle = (size, direction) => ({
  // Only apply space in one direction at a time, else large spacers will increase the
  // 'across' size of the parent and mess up siblings with styles like `alignItems: stretch`.
  [direction === 'row' ? 'width' : 'height']: size
})

/**
 * An empty element used to create a gap between components that is sized according to the theme spacing scale,
 * passing its `space` prop ({@link SpacingValue}) to `useSpacingScale`.
 *
 * ## Simple spacing
 *
 * For most simple uses, pass a number to Spacer's `space` prop referring to an index in the theme's
 * spacing scale. For example, for a spacer of the theme's smallest non-zero spacing size:
 *
 * ```jsx
 * <Spacer space={1} />
 * ```
 *
 * ## Responsive spacing
 *
 * Different spacing scale sizes may be chosen for different viewports by passing `space` a {@link SpacingObject}
 * using keys for the viewports at and above which certain spacing indexes should apply.
 *
 * This can be useful when a Spacer is inside a layout with a shape that changes between viewports.
 *
 * For example, a spacer might need no width below 'md' viewport because the items it separates will be on separate lines,
 * but may need a visible size above that viewport, and a wider size at the highest viewport:
 *
 * ```jsx
 * <Spacer space={{ xs: 0, md: 2, xl: 3 }} />
 * ```
 *
 * Note that themes may also define viewport-specific behaviour for indexes on the spacing scale. These viewport
 * props should only be used where necessary to achieve a certain responsive layoutm and shouldn't be used to replace
 * or deviate from responsive spacing behaviour in a theme that is intended to apply universally.
 *
 * ## More options
 *
 * See `useSpacingScale` hook for more options that may be used with the `space` prop.
 *
 * ## Accessibility
 *
 * Spacer has no content and is ignored by tools such as screen readers. Use `Divider` for
 * separations between elements that may be treated as semantically meaningful on web.
 */
export const Spacer = React.forwardRef(
  ({ space = 1, direction: directionProp = 'column', dataSet, ...rest }, ref) => {
    const {
      themeOptions: { enableMediaQueryStyleSheet }
    } = useTheme()

    const { sizeByViewport } = useMediaQuerySpacing(space)

    const direction = useResponsiveProp(directionProp, 'column')
    const fallbackSize = useSpacingScale(space)
    const sizeStyle = selectSizeStyle(fallbackSize, direction)

    let spacerStyles
    let dataSetValue = dataSet

    if (enableMediaQueryStyleSheet) {
      const sizeKeyAt = (viewport) =>
        resolveResponsiveProp(directionProp, viewport, 'column') === 'row' ? 'width' : 'height'
      const stylesByViewport = {
        xs: { [sizeKeyAt('xs')]: sizeByViewport.xs, ...staticStyles.stretch },
        sm: { [sizeKeyAt('sm')]: sizeByViewport.sm, ...staticStyles.stretch },
        md: { [sizeKeyAt('md')]: sizeByViewport.md, ...staticStyles.stretch },
        lg: { [sizeKeyAt('lg')]: sizeByViewport.lg, ...staticStyles.stretch },
        xl: { [sizeKeyAt('xl')]: sizeByViewport.xl, ...staticStyles.stretch }
      }
      const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)

      const { ids, styles } = StyleSheetUtils.create({
        spacer: {
          ...mediaQueryStyles
        }
      })

      spacerStyles = styles.spacer
      dataSetValue = { media: ids.spacer, ...dataSet }
    } else {
      spacerStyles = [staticStyles.stretch, sizeStyle]
    }

    return <View ref={ref} style={spacerStyles} dataSet={dataSetValue} {...selectProps(rest)} />
  }
)
Spacer.displayName = 'Spacer'

Spacer.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * The size of the spacer according to the theme's spacing scale.
   * Either a number corresponding to a position on the theme's spacing scale (1 is smallest, 2 is second smallest, etc),
   * or, a SpacingObject with viewport keys and options (see `useSpacingScale` for details).
   */
  space: spacingProps.types.spacingValue,
  /**
   * The spacer applies space in only one direction, which is controlled by the `direction` prop:
   *
   * - `'column'` (default) applies space vertically; has a fixed height and not width.
   * - `'row'` applies space horizontally; has a fixed width and not height.
   *
   * `direction` is an optionally responsive prop and may be an object keyed by viewports, for
   * spacers inside a container whose own direction changes between viewports.
   */
  direction: responsiveProps.getTypeOptionallyByViewport(PropTypes.oneOf(['column', 'row'])),
  /**
   * Data attributes to be applied to the element. When media query stylesheet is enabled,
   * this will include media query IDs for responsive styling.
   */
  dataSet: PropTypes.object
}

const staticStyles = StyleSheet.create({
  stretch: {
    alignSelf: 'stretch'
  }
})
