import React from 'react'
import PropTypes from 'prop-types'
import { Platform } from 'react-native'
import { viewports } from '@telus-uds/system-constants'
import { BaseView } from '../utils/BaseView/BaseView'
import { useViewport } from '../ViewportProvider/useViewport'
import { a11yProps } from '../utils/props/a11yProps'
import { StyleSheet, createMediaQueryStyles } from '../utils/ssr-media-query/ssr-media-query'

/**
 * Lists the viewports at which `min` and `max` place the children out of range.
 *
 * @param {string} [min] - smallest viewport at which the children are shown
 * @param {string} [max] - largest viewport at which the children are shown
 * @returns {string[]} viewport keys at which the children are hidden, smallest first
 *
 * @example
 * getHiddenViewports('sm', 'lg') // ['xs', 'xl']
 */
function getHiddenViewports(min, max) {
  const viewportsArray = viewports.keys
  const minIndex = viewportsArray.indexOf(min)
  const maxIndex = viewportsArray.indexOf(max)
  let hiddenViewports = []

  if (minIndex !== -1) {
    hiddenViewports = viewportsArray.slice(0, minIndex)
  }
  if (maxIndex !== -1) {
    hiddenViewports = hiddenViewports.concat(viewportsArray.slice(maxIndex + 1))
  }

  return hiddenViewports
}

/**
 * Builds one media rule per out-of-range viewport, each hiding the element it is applied to.
 *
 * Inheritance is off because every hidden viewport is listed explicitly; letting an unlisted
 * viewport inherit from the one below it would hide the children where they belong.
 *
 * @param {string[]} hiddenViewports - viewport keys at which the children are hidden
 * @returns {Object} media queries keyed by their query string
 */
function generateResponsiveStyles(hiddenViewports) {
  const styles = {}

  hiddenViewports.forEach((viewport) => {
    styles[viewport] = { display: 'none' }
  })

  return createMediaQueryStyles(styles, false)
}

/**
 * Renders `children` inside a `BaseView` whose media rules hide it outside the `min` to `max`
 * viewport range, so a server-side render carries every viewport's visibility without JavaScript.
 *
 * This tree differs from the `ResponsiveProp` tree used when `enableMediaQueryStyleSheet` is off,
 * in two ways consumers can depend on. A host element is introduced, so a flex or grid parent sees
 * this view as its item rather than the children, and any selector matching a first or last child
 * matches this view. And the children mount at every viewport: their effects run, their requests go
 * out, their timers start, and form fields inside an out-of-range branch are still submitted with
 * the form. Both are intentional, and neither can be avoided while visibility is driven by CSS,
 * which is what removes the hydration layout shift; the media ids have to attach to an element this
 * component owns, and the children's root elements belong to the consumer (#7223).
 *
 * `display: none` also removes the subtree from the browser's accessibility tree, so nothing more is
 * needed on web. Native has no CSS, so out-of-range children are hidden from assistive technology
 * explicitly, from the current viewport rather than from the resolved style.
 *
 * `viewport` is a dependency of the memo because the native `StyleSheet.create` resolves media rules
 * against `Dimensions.get('window')` as it is called, so its result is only valid for the viewport
 * that produced it. Caching it across viewport changes leaves the children hidden or shown where
 * they no longer belong (#7223).
 */
export const ResponsiveWithMediaQueryStyleSheet = React.forwardRef(
  ({ min, max, inheritedStyles = [], children }, ref) => {
    const viewport = useViewport()
    const { ids, styles, hiddenFromAssistiveTech } = React.useMemo(() => {
      const hiddenViewports = getHiddenViewports(min, max)
      const sheet = StyleSheet.create({
        responsive: {
          ...inheritedStyles.reduce((acc, prop) => {
            acc[prop] = 'inherit'
            return acc
          }, {}),
          ...generateResponsiveStyles(hiddenViewports)
        }
      })

      return {
        ...sheet,
        hiddenFromAssistiveTech: Platform.select({
          web: {},
          default: hiddenViewports.includes(viewport) ? a11yProps.nonFocusableProps : {}
        })
      }
    }, [min, max, inheritedStyles, viewport])

    return (
      <BaseView
        style={styles.responsive}
        dataSet={ids.responsive && { media: ids.responsive }}
        {...hiddenFromAssistiveTech}
        ref={ref}
      >
        {children}
      </BaseView>
    )
  }
)

ResponsiveWithMediaQueryStyleSheet.displayName = 'Responsive'

ResponsiveWithMediaQueryStyleSheet.propTypes = {
  /**
   * To hide children of `Responsive` if the current viewport is smaller than `min`
   */
  min: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
  /**
   * To hide children of `Responsive` if the current viewport is larger than `max`
   */
  max: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
  /**
   * Style property names for the wrapping element to take from its parent, so the element it adds
   * to the tree does not interrupt their inheritance
   */
  inheritedStyles: PropTypes.arrayOf(PropTypes.string),
  /**
   * Content shown while the current viewport is within the `min` to `max` range, and mounted but
   * hidden outside it
   */
  children: PropTypes.node.isRequired
}
