import React from 'react'
import PropTypes from 'prop-types'
import { a11yProps } from '../utils/props/a11yProps'
import { focusHandlerProps } from '../utils/props/handlerProps'
import { containUniqueFields } from '../utils/containUniqueFields'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { useMultipleInputValues, useInputValue } from '../utils/input'
import { useCopy } from '../utils/useCopy'
import { viewProps } from '../utils/props/viewProps'
import { variantProp } from '../utils/props/variantProp'
import { getTokensPropType } from '../utils/props/tokens'
import { copyPropTypes } from '../utils/props/copyPropTypes'
import { dictionary as defaultDictionary } from './dictionary'
import { useViewport } from '../ViewportProvider/useViewport'
import { useThemeTokens } from '../ThemeProvider/useThemeTokens'
import { StackView } from '../StackView/StackView'
import { StackWrap } from '../StackView/StackWrap'
import { Card } from '../Card/Card'
import { Fieldset } from '../Fieldset/Fieldset'

const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
const [selectItemProps, selectedItemPropTypes] = selectSystemProps([
  a11yProps,
  focusHandlerProps,
  viewProps
])
const uniqueFields = ['id']

export const CardGroup = React.forwardRef(
  (
    {
      copy = 'en',
      tokens = {},
      variant = {},
      items = [],
      validation,
      feedback,
      initialCheckedIds,
      checkedIds,
      onChange,
      role = 'radiogroup',
      isInactive,
      name: inputGroupName,
      dictionary = defaultDictionary,
      ...rest
    },
    ref
  ) => {
    const getCopy = useCopy({ dictionary, copy })
    const viewport = useViewport()
    const cardGroupTokens = useThemeTokens('CardGroup', tokens, variant, { viewport })
    const {
      space,
      fieldSpace,
      direction,
      showIcon,
      outlineWidth,
      borderTopLeftRadius,
      borderTopRightRadius,
      borderBottomLeftRadius,
      borderBottomRightRadius,
      outlineOffset,
      paddingTop,
      paddingBottom,
      paddingLeft,
      paddingRight
    } = cardGroupTokens

    const borderTokens = {
      outlineWidth,
      borderTopLeftRadius,
      borderTopRightRadius,
      borderBottomLeftRadius,
      borderBottomRightRadius,
      outlineOffset
    }

    const singleSelection = role === 'radiogroup'

    const inputControl = {
      value: checkedIds,
      initialValue: initialCheckedIds,
      onChange,
      readOnly: isInactive
    }
    const singleInputValue = useInputValue(inputControl)
    const multipleInputValues = useMultipleInputValues(inputControl)

    const StackContainer = direction === 'row' ? StackWrap : StackView

    if (!containUniqueFields(items, uniqueFields)) {
      throw new Error(`CardGroup items must have unique ${uniqueFields.join(', ')}`)
    }

    const handleChange = React.useCallback(
      (checked, event, cardId, itemOnChange) => {
        if (checked && singleSelection) {
          singleInputValue.setValue(cardId, event)
        } else {
          multipleInputValues.toggleOneValue(cardId, event)
        }
        return itemOnChange?.(checked, event)
      },
      [singleInputValue, multipleInputValues, singleSelection]
    )

    return (
      <Fieldset
        copy={copy}
        ref={ref}
        name={inputGroupName}
        space={fieldSpace}
        feedback={feedback}
        validation={validation}
        tokens={borderTokens}
        inactive={isInactive}
        showErrorBorder
        showIcon={showIcon}
        accessibilityRole={role}
        {...selectProps(rest)}
      >
        {(props) => (
          <StackContainer space={space}>
            {items.map(
              ({ id, interactiveCard, card, onPress: itemOnChange, ...itemRest }, index) => {
                const cardId = id || `${getCopy('prefixId')}[${index}]`
                const itemHandleChange = (checked, event) =>
                  handleChange(checked, event, cardId, itemOnChange)

                return (
                  <Card
                    id={cardId}
                    key={cardId}
                    onPress={itemHandleChange}
                    interactiveCard={{
                      ...interactiveCard,
                      tokens: {
                        paddingTop,
                        paddingBottom,
                        paddingLeft,
                        paddingRight,
                        ...interactiveCard?.tokens
                      },
                      variant: {
                        isControl: true,
                        selected: singleSelection
                          ? singleInputValue.currentValue === cardId
                          : multipleInputValues.currentValues.includes(cardId),
                        inactive: isInactive
                      },
                      selectionType: role
                    }}
                    {...selectItemProps(itemRest)}
                    {...props}
                  >
                    {card?.body}
                  </Card>
                )
              }
            )}
          </StackContainer>
        )}
      </Fieldset>
    )
  }
)

CardGroup.displayName = 'CardGroup'

const dictionaryContentShape = PropTypes.shape({
  prefixId: PropTypes.string.isRequired
})

CardGroup.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * Whether the English or French copy will be used (e.g. for accessibility labels).
   */
  copy: copyPropTypes,
  /**
   * Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
   */
  dictionary: PropTypes.shape({
    en: dictionaryContentShape,
    fr: dictionaryContentShape
  }),
  /**
   * Optional theme token overrides for the outer RadioCardGroup component
   */
  tokens: getTokensPropType('CardGroup'),
  /**
   * Theme variants, shared between both RadioCardGroup and RadioCard
   */
  variant: variantProp.propType,
  /**
   * Array of objects containing specifics for each Card to be rendered in the group.
   */
  items: PropTypes.arrayOf(
    PropTypes.exact({
      ...selectedItemPropTypes,
      id: PropTypes.string,
      interactiveCard: PropTypes.object,
      card: PropTypes.object,
      onPress: PropTypes.func
    })
  ),
  /**
   * Current validation status of the group, passed to the feedback element if there is one.
   */
  validation: PropTypes.oneOf(['error', 'success']),
  /**
   * If provided, a Feedback element is rendered containing this text.
   */
  feedback: PropTypes.string,
  /**
   * If provided, the card with this id is selected on first render.
   * If the role is radiogroup, it's a string
   * If the role is checkbox, it's a array of string.
   * Can have 'null' as value
   */
  initialCheckedIds: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
  /**
   * If not undefined, the card with this id is selected (or none is selected if `null`), and the
   * element's selection state will be controlled by its parent using the `onChange` function.
   * If the role is radiogroup, it's a string
   * If the role is checkbox, it's a array of string.
   * Can have 'null' as value
   */
  checkedIds: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
  /**
   * Function to call on change in selection state. Is required if the selection state is controlled
   * by a parent using checkedId and the input is not readOnly.
   */
  onChange: PropTypes.func,
  /**
   * Renders the card group like a radio card group or a checkbox card group
   */
  role: PropTypes.oneOf(['radiogroup', 'checkbox']),
  /**
   * If true, the card cannot be interacted with, elements are set as `disabled` and if the
   * theme supports `inactive` appearances rules, these are applied.
   */
  isInactive: PropTypes.bool,
  /**
   * On Web, this is passed to the `name` attribute of the fieldset and each input.
   */
  name: PropTypes.string
}
