import React from 'react'
import PropTypes from 'prop-types'

import { viewports } from '@telus-uds/system-constants'
import { a11yProps } from '../utils/props/a11yProps'
import { viewProps } from '../utils/props/viewProps'
import { getA11yPropsFromHtmlTag, layoutTags } from '../utils/a11y/semantics'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { BaseView } from '../utils/BaseView/BaseView'
import { StyleSheet, createMediaQueryStyles } from '../utils/ssr-media-query/ssr-media-query'
import { useResponsiveProp } from '../utils/useResponsiveProp'
import { resolveContentMaxWidth } from '../utils/resolveContentMaxWidth'
import { GutterContext } from './providers/GutterContext'
import { applyInheritance } from './helpers'
import { useTheme } from '../ThemeProvider/useTheme'
import { useViewport } from '../ViewportProvider/useViewport'

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

/**
 * Calculates the maximum width for a given viewport based on limitWidth and contentMaxWidth settings.
 *
 * @param {string} viewportKey - The viewport key ('xs', 'sm', 'md', 'lg', 'xl')
 * @param {boolean} limitWidth - Whether to limit the width to viewport breakpoints
 * @param {any} contentWidthProp - The contentMaxWidth (or contentMinWidth) prop value
 * @param {number|string|null} maxWidth - The resolved max width value
 * @returns {number|string|null} The calculated maximum width for the viewport
 */
const getMaxWidthForViewport = (viewportKey, limitWidth, contentWidthProp, maxWidth) => {
  if (limitWidth) {
    if (viewportKey === 'xs') {
      return null
    }
    return viewports.map.get(viewportKey)
  }

  if (contentWidthProp) {
    return maxWidth
  }

  return null
}

/**
 * A mobile-first flexbox grid.
 */

export const FlexGrid = React.forwardRef(
  (
    {
      limitWidth = true,
      gutter = true,
      outsideGutter = true,
      xsReverse,
      smReverse,
      mdReverse,
      lgReverse,
      xlReverse,
      tag,
      accessibilityRole,
      children,
      dataSet,
      contentMaxWidth,
      contentMinWidth,
      ...rest
    },
    ref
  ) => {
    const reverseLevel = applyInheritance([xsReverse, smReverse, mdReverse, lgReverse, xlReverse])
    const viewport = useViewport()
    const {
      themeOptions,
      themeOptions: { enableMediaQueryStyleSheet }
    } = useTheme()

    let flexgridStyles
    let mediaIds

    // Support both contentMaxWidth and deprecated contentMinWidth for backwards compatibility
    const contentWidthProp = contentMaxWidth || contentMinWidth
    const contentWidthValue = useResponsiveProp(contentWidthProp)
    const responsiveWidth = useResponsiveProp(themeOptions?.contentMaxWidth)
    const maxWidth = resolveContentMaxWidth(contentWidthValue, responsiveWidth)

    const stylesByViewport = {
      xs: {
        maxWidth: getMaxWidthForViewport('xs', limitWidth, contentWidthProp, maxWidth),
        flexDirection: reverseLevel[0] ? 'column-reverse' : 'column'
      },
      sm: {
        maxWidth: getMaxWidthForViewport('sm', limitWidth, contentWidthProp, maxWidth),
        flexDirection: reverseLevel[1] ? 'column-reverse' : 'column'
      },
      md: {
        maxWidth: getMaxWidthForViewport('md', limitWidth, contentWidthProp, maxWidth),
        flexDirection: reverseLevel[2] ? 'column-reverse' : 'column'
      },
      lg: {
        maxWidth: getMaxWidthForViewport('lg', limitWidth, contentWidthProp, maxWidth),
        flexDirection: reverseLevel[3] ? 'column-reverse' : 'column'
      },
      xl: {
        maxWidth: getMaxWidthForViewport('xl', limitWidth, contentWidthProp, maxWidth),
        flexDirection: reverseLevel[4] ? 'column-reverse' : 'column'
      }
    }

    const staticStyles = {
      flexWrap: 'wrap',
      alignContent: 'stretch',
      width: outsideGutter ? '100%' : 'auto',
      marginVertical: 0,
      marginHorizontal: outsideGutter ? 'auto' : -16
    }

    if (enableMediaQueryStyleSheet) {
      const mediaQueryStyles = createMediaQueryStyles(stylesByViewport)
      const { ids, styles } = StyleSheet.create({
        flexgrid: {
          ...staticStyles,
          ...mediaQueryStyles
        }
      })
      flexgridStyles = styles.flexgrid
      mediaIds = ids.flexgrid
    } else {
      flexgridStyles = {
        ...staticStyles,
        ...stylesByViewport[viewport]
      }
    }

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

    const dataSetValue = mediaIds ? { media: mediaIds, ...dataSet } : dataSet

    return (
      <GutterContext.Provider value={gutter}>
        <BaseView ref={ref} {...props} style={flexgridStyles} dataSet={dataSetValue}>
          {children}
        </BaseView>
      </GutterContext.Provider>
    )
  }
)
FlexGrid.displayName = 'FlexGrid'

FlexGrid.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * Whether or not to give the grid a fixed width. This also centres the grid horizontally.
   */
  limitWidth: PropTypes.bool,
  /**
   * Whether or not to include gutters in between columns.
   */
  gutter: PropTypes.bool,
  /**
   * Whether or not to include gutter at the ends to the left and right of the FlexGrid
   */
  outsideGutter: PropTypes.bool,
  /**
   * Choose if the item order should be reversed from the 'xs' breakpoint. When you pass in false, the order will be normal from the xs breakpoint. By default, it inherits the behaviour set by the preceding prop.
   */
  xsReverse: PropTypes.bool,
  /**
   * Choose if the item order should be reversed from the 'sm' breakpoint. When you pass in false, the order will be normal from the sm breakpoint. By default, it inherits the behaviour set by the preceding prop.
   */
  smReverse: PropTypes.bool,
  /**
   * Choose if the item order should be reversed from the 'md' breakpoint. When you pass in false, the order will be normal from the md breakpoint. By default, it inherits the behaviour set by the preceding prop.
   */
  mdReverse: PropTypes.bool,
  /**
   * Choose if the item order should be reversed from the 'lg' breakpoint. When you pass in false, the order will be normal from the lg breakpoint. By default, it inherits the behaviour set by the preceding prop.
   */
  lgReverse: PropTypes.bool,
  /**
   * Choose if the item order should be reversed from the 'xl' breakpoint. When you pass in false, the order will be normal from the xl breakpoint. By default, it inherits the behaviour set by the preceding prop.
   */
  xlReverse: PropTypes.bool,
  /**
   * 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),
  /**
   * The rows and columns of the Grid. Will typically be `FlexGridRow` and `FlexGridCol` components.
   */
  children: PropTypes.node.isRequired,
  /**
   * The maximum width of the content in the FlexGrid.
   * This prop accepts responsive values for different viewports. If a number is provided,
   * it will be the max content width for the desired viewport.
   * - `xs`: 'max' | 'full' | <number>
   * - `sm`: 'max' | 'full' | <number>
   * - `md`: 'max' | 'full' | <number>
   * - `lg`: 'max' | 'full' | <number>
   * - `xl`: 'max' | 'full' | <number>
   */
  contentMaxWidth: PropTypes.shape({
    xl: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
    lg: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
    md: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
    sm: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
    xs: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number])
  }),
  /**
   * @deprecated Use `contentMaxWidth` instead. This prop will be removed in a future version.
   *
   * The minimum width of the content in the FlexGrid.
   * This prop accepts responsive values for different viewports. If a number is provided,
   * it will be the max content width for the desired viewport.
   */
  contentMinWidth: PropTypes.shape({
    xl: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
    lg: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
    md: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
    sm: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
    xs: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number])
  })
}
