import React from 'react'
import PropTypes from 'prop-types'
import {
  selectSystemProps,
  useScrollBlocking,
  useThemeTokens,
  getTokensPropType
} from '@telus-uds/components-base'
import { Portal } from '@gorhom/portal'
import styled from 'styled-components'
import SpinnerContent from './SpinnerContent'
import { htmlAttrs, media } from '../utils'
import { BACKDROP_OPACITY, BACKDROP_Z_INDEX } from './constants'

const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])

const SpinnerContainer = styled.div(({ inline, fullScreen, overlay }) => ({
  position: 'relative',
  ...(inline && {
    display: overlay ? 'inline-block' : 'block',
    ...media().from('md').css({
      display: 'inline-block'
    })
  }),
  ...(fullScreen && { top: '50%', transform: 'translateY(-50%)' })
}))

const ContentOverlay = styled.div({
  position: 'absolute',
  width: '100%',
  top: 0,
  left: 0,
  height: '100%',
  zIndex: BACKDROP_Z_INDEX,
  pointerEvents: 'none'
})

const FullscreenOverlay = styled.div(({ fullScreenOverLayBackground }) => ({
  position: 'fixed',
  width: '100vw',
  height: '100vh',
  top: 0,
  left: 0,
  zIndex: BACKDROP_Z_INDEX,
  backgroundColor: fullScreenOverLayBackground
}))

const OpaqueContainer = styled.div(({ show = true }) => ({
  ...(show && {
    opacity: BACKDROP_OPACITY
  })
}))

const recursiveMap = (children, fn) =>
  React.Children.map(children, (child) => {
    if (!React.isValidElement(child)) {
      return child
    }
    if (child.props.children) {
      return fn(
        React.cloneElement(child, {
          children: recursiveMap(child.props.children, fn)
        })
      )
    }

    return fn(child)
  })

/**
 * Loading indicator.
 */
const Spinner = React.forwardRef(
  (
    {
      children,
      fullScreen = false,
      inline = false,
      label,
      labelPosition,
      show = false,
      isStatic = false,
      tokens,
      variant = {},
      persistChildrenState = false,
      ...rest
    },
    ref
  ) => {
    const { fullScreenOverLayBackground, size, thickness } = useThemeTokens(
      'Spinner',
      tokens,
      variant
    )

    const { size: sizeVariant = 'large' } = variant

    useScrollBlocking([fullScreen, show])

    // Overlay spinner with persistChildrenState enabled
    if (children && persistChildrenState) {
      return (
        <SpinnerContainer inline={inline} aria-live="assertive" overlay {...selectProps(rest)}>
          {/* Children ALWAYS rendered in same position - OpaqueContainer styles toggle with show */}
          <OpaqueContainer show={show} {...(show ? { inert: 'true', 'aria-hidden': 'true' } : {})}>
            {children}
          </OpaqueContainer>
          <ContentOverlay />
          {show && (
            <SpinnerContent
              label={label}
              labelPosition={labelPosition}
              overlay={true}
              size={size}
              thickness={thickness}
              sizeVariant={sizeVariant}
              isStatic={isStatic}
            />
          )}
        </SpinnerContainer>
      )
    }

    if (!show) {
      return children ?? null
    }

    // Full screen spinner
    if (fullScreen) {
      return (
        <Portal>
          <FullscreenOverlay fullScreenOverLayBackground={fullScreenOverLayBackground}>
            <SpinnerContainer
              inline={inline}
              fullScreen={fullScreen}
              aria-live="assertive"
              {...selectProps(rest)}
            >
              <SpinnerContent
                label={label}
                labelPosition={labelPosition}
                overlay={true}
                size={size}
                thickness={thickness}
                sizeVariant={sizeVariant}
                isStatic={isStatic}
              />
            </SpinnerContainer>
          </FullscreenOverlay>
        </Portal>
      )
    }

    // Overlay spinner
    if (children && !persistChildrenState) {
      return (
        <SpinnerContainer inline={inline} aria-live="assertive" overlay {...selectProps(rest)}>
          <SpinnerContent
            label={label}
            labelPosition={labelPosition}
            overlay={true}
            size={size}
            thickness={thickness}
            sizeVariant={sizeVariant}
            isStatic={isStatic}
          />

          <ContentOverlay />

          <OpaqueContainer inert="true">
            {recursiveMap(children, (c) => {
              if (c) {
                return React.cloneElement(c, { tabIndex: '-1', 'aria-hidden': 'true' })
              }
              return undefined
            })}
          </OpaqueContainer>
        </SpinnerContainer>
      )
    }

    // Standalone spinner
    return (
      <SpinnerContainer ref={ref} {...selectProps(rest)}>
        <SpinnerContent
          label={label}
          labelPosition={labelPosition}
          size={size}
          thickness={thickness}
          sizeVariant={sizeVariant}
          isStatic={isStatic}
        />
      </SpinnerContainer>
    )
  }
)

Spinner.displayName = 'Spinner'

Spinner.propTypes = {
  ...selectedSystemPropTypes,
  tokens: getTokensPropType('Spinner'),
  /**
   * Content to be overlaid while the spinner is active. Can be text, any HTML element,
   * or any component.
   */
  children: PropTypes.node,
  /**
   * Enables body locking.
   */
  fullScreen: PropTypes.bool,
  /**
   * Set the inline prop to true if the Spinner should only cover its children; if
   * the Spinner should cover the full width of its parent regardless of the size of
   * its children, inline should be set to false
   */
  inline: PropTypes.bool,
  /**
   * Communicates a message to assistive technology while visible. This same message
   * will appear underneath the spinner when its `size` is `large`.
   */
  label: PropTypes.string.isRequired,
  /**
   * The size of the spinner
   */
  // size: PropTypes.oneOf(['large', 'small']),
  /**
   * Whether or not to render the spinner.
   */
  show: PropTypes.bool,
  /**
   * If true, it should render a static spinner
   */
  isStatic: PropTypes.bool,
  /**
   * Determine where the label of the spinner should be placed, left, right, bottom or top.
   */
  labelPosition: PropTypes.string,
  /**
   * When true, preserves the state of overlaid children when `show` toggles.
   * Only applicable when the Spinner is used in overlay mode (with children).
   * Use this when overlaying stateful components such as Micro Frontends (MFEs).
   */
  persistChildrenState: PropTypes.bool
}

export default Spinner
