import React from 'react'
import PropTypes from 'prop-types'
import { View } from 'react-native'

import { Divider } from '../Divider/Divider'
import { a11yProps } from '../utils/props/a11yProps'
import { getA11yPropsFromHtmlTag, layoutTags } from '../utils/a11y/semantics'
import { getTokensPropType } from '../utils/props/tokens'
import { responsiveProps } from '../utils/props/responsiveProps'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { spacingProps } from '../utils/props/spacingProps'
import { useResponsiveProp, resolveResponsivePropByViewport } from '../utils/useResponsiveProp'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import {
  StyleSheet as StyleSheetUtils,
  createMediaQueryStyles
} from '../utils/ssr-media-query/ssr-media-query'
import { useAllViewportTokens } from '../utils/ssr-media-query/utils/use-all-viewport-tokens'
import { useTheme } from '../ThemeProvider/useTheme'
import { getStackedContent } from './getStackedContent'
import { staticStyles, selectFlexStyles, getSpacerDirectionByViewport } from './common'

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

/**
 * StackView evenly spaces its children, by default placing `Spacer` components between them.
 *
 * ## Spacing
 *
 * StackView takes the same spacing props as `Spacer` and passes them directly to the layout components
 * it uses for spacing.
 *
 * ## Direction
 *
 * Use `direction` to control how the children are stacked:
 *
 * - `'column'` (default) stacks and spaces the children vertically.
 * - `'row'` stacks and spaces the children horizontally.
 * - `'column-reverse'` acts the same as `'column'`, but reverses the direction.
 * - `'row-reverse'` acts the same as `'row'`, but reverses the direction.
 *
 * `direction` is an optionally responsive prop and may be an object keyed by viewports. For example,
 * `{ xs: 'column', md: 'row' } stacks as a column on `xs` and `sm` viewports and a row at 'md' and above.
 *
 * With the `enableMediaQueryStyleSheet` theme option, a responsive `direction` is emitted as CSS media
 * rules rather than resolved against the JavaScript viewport, so a server render does not flip
 * direction on hydration.
 *
 * ## Theming, alignment and justification
 *
 * `StackView` is a pure layout component and has no theme styles beyond a small set of layout styles
 * (`alignItems`, `justifyContent`, `flexGrow`) which may be set using the `tokens` prop. If the
 * tokens prop is a function it will recieve the current viewport.
 *
 * All other styling such as borders, background colors, padding etc should be applied by wrapping
 * the `StackView` in other components. For example, a `StackView` may be the child of a `Card`.
 *
 * ## Separator options
 *
 * By default, StackView inserts a `<Spacer>` component between each rendered child.
 *
 * With the `divider` prop, it instead renders a `Divider` instead of a `Spacer`. The gap between
 * items will be double what it would be without this prop plus the width of the divider.
 *
 * ## Accessibility
 *
 * `StackView` may take any React Native accessibility props and applies them to the outer `View`.
 * It applies no accessibility props of its own, unless `divider` prop is passed (`Divider` has a
 * semantic role but only on web, not within native apps).
 */
export const StackView = React.forwardRef(
  (
    {
      space = 0,
      divider,
      direction: directionProp = 'column',
      children,
      variant,
      tokens,
      tag,
      accessibilityRole,
      dataSet,
      ...rest
    },
    ref
  ) => {
    const direction = useResponsiveProp(directionProp, 'column')
    const {
      themeOptions: { enableMediaQueryStyleSheet }
    } = useTheme()

    const selectedProps = selectProps({
      accessibilityRole,
      ...getA11yPropsFromHtmlTag(tag, accessibilityRole),
      ...rest
    })

    const allTokens = useAllViewportTokens('StackView', tokens, variant)

    const directionByViewport = resolveResponsivePropByViewport(directionProp, 'column')
    const content = getStackedContent(children, {
      direction,
      divider,
      space,
      spacerDirection: enableMediaQueryStyleSheet
        ? getSpacerDirectionByViewport(directionByViewport)
        : undefined
    })

    let stackViewStyles
    let dataSetValue = dataSet

    if (enableMediaQueryStyleSheet) {
      const stylesByViewport = {
        xs: {
          ...selectFlexStyles(allTokens.xs),
          flexDirection: directionByViewport.xs,
          width: allTokens.xs.width
        },
        sm: {
          ...selectFlexStyles(allTokens.sm),
          flexDirection: directionByViewport.sm,
          width: allTokens.sm.width
        },
        md: {
          ...selectFlexStyles(allTokens.md),
          flexDirection: directionByViewport.md,
          width: allTokens.md.width
        },
        lg: {
          ...selectFlexStyles(allTokens.lg),
          flexDirection: directionByViewport.lg,
          width: allTokens.lg.width
        },
        xl: {
          ...selectFlexStyles(allTokens.xl),
          flexDirection: directionByViewport.xl,
          width: allTokens.xl.width
        }
      }

      const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)

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

      stackViewStyles = styles.stackView
      dataSetValue = { media: ids.stackView, ...dataSet }
    } else {
      const flexStyles = selectFlexStyles(allTokens.current)
      const size = { width: allTokens.current.width }
      stackViewStyles = [flexStyles, staticStyles[direction], size]
    }

    return (
      <View ref={ref} {...selectedProps} style={stackViewStyles} dataSet={dataSetValue}>
        {content}
      </View>
    )
  }
)
StackView.displayName = 'StackView'

StackView.propTypes = {
  ...selectedSystemPropTypes,
  tokens: getTokensPropType('StackView'),
  variant: variantProp.propType,
  /**
   * Sets the `flexDirection` of the container and, if `divider` is used, gives the Divider the appropriate direction.
   */
  direction: responsiveProps.getTypeOptionallyByViewport(
    PropTypes.oneOf(['column', 'row', 'column-reverse', 'row-reverse'])
  ),
  /**
   * If true, renders a UDS `Divider` component between each item. If an object is passed,
   * this object is passes as props to each Divider.
   */
  divider: PropTypes.oneOfType([PropTypes.bool, PropTypes.shape(Divider.propTypes)]),
  /**
   * 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,
  /**
   * Optional semantic HTML tag, to apply to the container on web. Does not change styling.
   *
   * In native apps, if a header tag is provided ("h1", "h2" etc) and accessibilityRole prop
   * is not defined, the accessibilityRole will default to "heading".
   */
  tag: PropTypes.oneOf(layoutTags),
  /**
   * A StackView may take any children, but will have no effect if it is only passed one child or is passed children
   * wrapped in a component. If necessary, children may be wrapped in one React Fragment.
   */
  children: PropTypes.node,
  /**
   * 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
}
