import React from 'react'
import PropTypes from 'prop-types'
import { View } from 'react-native'
import { viewports } from '@telus-uds/system-constants'
import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider/useThemeTokens'
import { useResponsiveThemeTokens } from '../ThemeProvider/useResponsiveThemeTokens'
import { useTheme } from '../ThemeProvider/useTheme'
import { useViewport } from '../ViewportProvider/useViewport'
import { getTokensPropType } from '../utils/props/tokens'
import { variantProp } from '../utils/props/variantProp'
import { StyleSheet } from '../utils/ssr-media-query/ssr-media-query'
import { createMediaQueryStyles } from '../utils/ssr-media-query/utils/create-media-query-styles'
import { a11yProps } from '../utils/props/a11yProps'
import { linkProps } from '../utils/props/linkProps'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { viewProps } from '../utils/props/viewProps'
import { responsiveProps } from '../utils/props/responsiveProps'
import { hrefAttrsProp } from '../utils/props/hrefAttrsProp'
import { CardBase, selectStyles } from './CardBase'
import { PressableCardBase } from './PressableCardBase'
import { CheckboxButton } from '../Checkbox/CheckboxButton'
import { RadioButton } from '../Radio/RadioButton'

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

const SelectionType = {
  Checkbox: 'checkbox',
  Radio: 'radiogroup',
  None: undefined
}

const getAccessibilityRole = (selectionType, href) => {
  if (selectionType === SelectionType.Checkbox) return 'checkbox'
  if (selectionType === SelectionType.Radio) return 'radio'
  return href ? 'link' : 'button'
}

const selectInputStyle = ({ paddingTop, paddingRight, paddingLeft }, { inputPositionProp }) => ({
  position: 'absolute',
  top: paddingTop,
  ...(inputPositionProp === 'left' ? { left: paddingLeft } : { right: paddingRight })
})

const getInputProps = ({
  id,
  checkColor,
  boxBackgroundColor,
  checkBackgroundColor,
  isControlled,
  isChecked,
  isInactive,
  handleChange
}) => ({
  inputId: id,
  tokens: {
    iconColor: checkColor,
    inputBackgroundColor: boxBackgroundColor,
    iconBackgroundColor: checkBackgroundColor
  },
  isControlled,
  isChecked,
  inactive: isInactive,
  handleChange
})

/**
 * A basic card component, unstyled by default.
 *
 * ## Component API
 *
 * ### Style
 *
 * In order to control the style of a card, use the `style` variant.
 * The following tokens are supported:
 *
 * - `backgroundColor`
 *
 * ### Border
 *
 * The following border tokens can be used:
 *
 * - `borderColor`
 * - `borderRadius`
 * - `borderWidth`
 *
 * ### Padding
 *
 * Please use the following tokens to control the padding:
 *
 * - `paddingBottom`
 * - `paddingLeft`
 * - `paddingRight`
 * - `paddingTop`
 *
 * Note that various viewport sizes are supported as well.
 *
 * ### Shadow
 *
 * Feel free to use the following properties within the `shadow` token:
 *
 * - `inset`: boolean
 * - `color`: string
 * - `offsetX`: number
 * - `offsetY`: number
 * - `blur`: number
 * - `spread`: number
 *
 * ## Accessibility
 *
 * Card supports all the common a11y props. Please remember that by marking a card as `accessible`
 * you automatically make inaccessible its children, which may or may not be appropriate
 * depending on what you are trying to achieve.
 */
export const Card = React.forwardRef(
  (
    {
      children,
      tokens,
      variant,
      dataSet,
      onPress,
      id,
      interactiveCard,
      backgroundImage,
      testID,
      ...rest
    },
    ref
  ) => {
    const viewport = useViewport()
    const { themeOptions } = useTheme()

    const { enableMediaQueryStyleSheet } = themeOptions

    const useTokens = enableMediaQueryStyleSheet ? useResponsiveThemeTokens : useThemeTokens

    const themeTokens = useTokens(
      'Card',
      tokens,
      variant,
      !enableMediaQueryStyleSheet && { viewport }
    )

    const selected = interactiveCard?.variant?.selected
    const inactive = interactiveCard?.variant?.inactive
    const selectionType = interactiveCard?.selectionType
    const isControl = interactiveCard?.variant?.isControl === true

    const hasInteractiveBody = Boolean(interactiveCard?.body)

    // `viewport` is a string, so spreading it yields character-index keys and every
    // viewport-conditioned rule stops matching. Keep it as a named key (#7219).
    const getThemeTokens = useThemeTokensCallback('Card', interactiveCard?.tokens, {
      interactive: true,
      ...(interactiveCard?.variant || {}),
      viewport
    })

    // Under RNMQ `themeTokens` is a viewport map — read through `currentThemeTokens`, never
    // off `themeTokens` (#7219; fallback removable via #7241)
    const currentThemeTokens = enableMediaQueryStyleSheet
      ? themeTokens[viewport] ?? themeTokens[viewports.keys[0]]
      : themeTokens

    // When interactive area is present, spacing tokens should only be applied
    // to individual Card sections, not Card as a whole
    const { paddingTop, paddingBottom, paddingLeft, paddingRight, ...restOfTokens } = selectionType
      ? getThemeTokens()
      : currentThemeTokens

    const {
      inputBackgroundColor,
      iconBackgroundColor,
      iconColor,
      icon,
      inputBorderRadius,
      inputHeight,
      inputWidth,
      iconSize,
      inputShadow,
      inputBorderWidth,
      inputBorderColor
    } = getThemeTokens()

    const checkboxTokens = {
      inputBackgroundColor,
      iconBackgroundColor,
      icon,
      iconColor,
      inputBorderRadius,
      inputHeight,
      inputWidth,
      iconSize,
      inputShadow,
      inputBorderWidth,
      inputBorderColor
    }

    const radioTokens = {
      iconBackgroundColor,
      iconColor,
      containerShadow: inputShadow,
      icon,
      inputBackgroundColor,
      inputSize: inputHeight,
      checkedSize: iconSize,
      inputBorderWidth,
      inputBorderColor
    }

    let cardStyles
    let mediaIds

    if (enableMediaQueryStyleSheet) {
      const transformedThemeTokens = Object.entries(themeTokens).reduce(
        (acc, [vp, viewportTokens]) => {
          const tokensToTransform = hasInteractiveBody
            ? selectStyles({
                ...viewportTokens,
                paddingTop: 0,
                paddingBottom: 0,
                paddingLeft: 0,
                paddingRight: 0
              })
            : selectStyles(viewportTokens)

          acc[vp] = tokensToTransform
          return acc
        },
        {}
      )

      const mediaQueryStyleSheet = createMediaQueryStyles(transformedThemeTokens)

      const { ids, styles } = StyleSheet.create({
        card: { ...currentThemeTokens, ...mediaQueryStyleSheet }
      })
      cardStyles = styles.card
      mediaIds = ids.card
    } else {
      cardStyles = currentThemeTokens
    }

    const renderInputPerSelectionType = (props) => {
      const containerStyle = selectInputStyle(getThemeTokens(), {
        inputPositionProp: interactiveCard?.inputPosition
      })
      const inputTestID = testID && `${testID}-selection-input`

      if (!isControl) {
        return null
      }

      switch (selectionType) {
        case SelectionType.Checkbox:
          return (
            <View style={containerStyle} testID={inputTestID}>
              <CheckboxButton {...props} tokens={{ ...checkboxTokens, ...props?.tokens }} />
            </View>
          )
        case SelectionType.Radio:
          return (
            <View style={containerStyle} testID={inputTestID}>
              <RadioButton {...props} tokens={{ ...radioTokens, ...props?.tokens }} />
            </View>
          )
        default:
          return null
      }
    }

    return (
      <>
        <CardBase
          ref={ref}
          tokens={hasInteractiveBody ? restOfTokens : cardStyles}
          backgroundImage={backgroundImage}
          dataSet={mediaIds && { media: mediaIds }}
          testID={testID}
          {...selectProps(rest)}
        >
          {interactiveCard?.body && (
            <PressableCardBase
              ref={ref}
              tokens={getThemeTokens}
              dataSet={dataSet}
              onPress={onPress}
              href={interactiveCard?.href}
              hrefAttrs={interactiveCard?.hrefAttrs}
              // eslint-disable-next-line react-native-a11y/has-valid-accessibility-role -- getAccessibilityRole returns only valid values ('checkbox', 'radio', 'link', 'button') but linter cannot statically verify function calls
              accessibilityRole={getAccessibilityRole(selectionType, interactiveCard?.href)}
              {...selectProps(rest)}
            >
              {(cardState) => {
                const {
                  iconColor: checkColor,
                  inputBackgroundColor: boxBackgroundColor,
                  iconBackgroundColor: checkBackgroundColor
                } = getThemeTokens(
                  {
                    ...cardState,
                    selected,
                    interactive: true,
                    isControl
                  },
                  interactiveCard?.tokens
                )
                return (
                  <>
                    {renderInputPerSelectionType(
                      getInputProps({
                        id,
                        checkColor,
                        boxBackgroundColor,
                        checkBackgroundColor,
                        isControlled: true,
                        isChecked: selected || cardState?.hover,
                        isInactive: inactive,
                        onPress
                      })
                    )}
                    {typeof interactiveCard?.body === 'function'
                      ? interactiveCard.body(cardState)
                      : interactiveCard.body}
                  </>
                )
              }}
            </PressableCardBase>
          )}
          {children}
        </CardBase>
      </>
    )
  }
)

Card.displayName = 'Card'

Card.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * Card content.
   */
  children: PropTypes.node,
  /**
   * Card tokens.
   */
  tokens: getTokensPropType('Card'),
  /**
   * Card variant.
   */
  variant: variantProp.propType,
  /**
   * Function to call on pressing the card.
   * Note: This is only available when `interactiveCard` prop is used.
   */
  onPress: PropTypes.func,
  /**
   * Card ID.
   */
  id: PropTypes.string,
  /**
   * Object to set interactive card's properties
   * - body: The body of the interactive card, can be any renderable node
   * - tokens: The tokens to be used for the interactive card
   * - selectionType: If the card has `isControl` variant set to `true`, the selectionType can have one of the following options: 
        - `radiogroup`
        - `checkbox`
   * - variant: The variant to be used for the interactive card
   * - href: The href to be used for the interactive card
   * - hrefAttrs: The href attributes to be used for the interactive card
   */
  interactiveCard: PropTypes.shape({
    body: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
    tokens: getTokensPropType('Card'),
    selectionType: PropTypes.oneOf(Object.values(SelectionType)),
    variant: variantProp.propType,
    href: PropTypes.string,
    hrefAttrs: PropTypes.shape(hrefAttrsProp.types),
    inputPosition: PropTypes.oneOf(['left', 'right'])
  }),
  /**
   * Apply background image to the card.
   */
  backgroundImage: PropTypes.shape({
    // The image src is either a URI string or a number (when a local image src is bundled in IOS or Android app)
    // src is an object when used responsively to provide different image sources for different screen sizes
    src: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]).isRequired,
    alt: PropTypes.string,
    resizeMode: responsiveProps.getTypeOptionallyByViewport(
      PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center'])
    ),
    position: responsiveProps.getTypeOptionallyByViewport(
      PropTypes.oneOf(['bottom', 'left', 'right', 'top'])
    ),
    align: responsiveProps.getTypeOptionallyByViewport(
      PropTypes.oneOf(['start', 'end', 'center', 'stretch'])
    )
  }),
  /**
   * Data set for the card.
   */
  dataSet: PropTypes.object,
  /**
   * Test ID used for e2e testing.
   */
  testID: PropTypes.string
}
