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

import { useViewport } from '../ViewportProvider/useViewport'
import { useThemeTokens } from '../ThemeProvider/useThemeTokens'
import { a11yProps } from '../utils/props/a11yProps'
import { containUniqueFields } from '../utils/containUniqueFields'
import { focusHandlerProps } from '../utils/props/handlerProps'
import { getTokensPropType } from '../utils/props/tokens'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { useInputValue } from '../utils/input'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { StackView } from '../StackView/StackView'
import { StackWrap } from '../StackView/StackWrap'
import { RadioCard } from './RadioCard'
import { Fieldset } from '../Fieldset/Fieldset'

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

/**
 * A group of Cards that behave as a radio button group. Use when users select a single choice from mutually
 * exclusive options with need to show additional information for each option. The whole cards are each
 * interactive as one item.
 *
 * ## Component API
 *
 * Use the `items` prop to pass an array of objects describing each RadioCard in the group:
 *
 *  - `title`: main text passed to RadioCard's `title` prop
 *  - `content`: React content passed to RadioCard's `children` prop
 *  - `id`: identifier used to store which RadioCard is selected (uses `title` if undefinded)
 *  - `onChange`: optional function called on selection, in addition to updating the group's selection state
 *
 * ### Controlled version
 *
 * If the selection state is controlled from outside, it needs to receive `checkedId` and `onChange` props.
 *
 * ### Uncontrolled version
 *
 * If the RadioCardGroup manages its own state, you can use `initialCheckedId` prop to provide the initial value.
 * Whenever the radio card gets toggled, it calls the `onChange` callback with the new value (string).
 *
 * ### Use in forms
 *
 * For web forms, the `name` prop may be used to define the name of the group's `fieldset` and input elements.
 *
 * ### Validation
 *
 * Validation state may be set by passing 'error' or 'success' to the `validation` prop.
 *
 * ## A11y guidelines
 *
 * RadioCardGroup accepts all the common accessibility props, but also sets accessibility role `'radiogroup'`
 * and controls the accessibility state of children like RadioCard and Feedback based on current state.
 *
 * @example
 * ```jsx
 * <RadioCardGroup
 *   initialCheckedId="card1"
 *   items={[
 *     { title: 'Card 1', id: 'card1', content: <Typography>More info</Typography> },
 *     { title: 'Card 2', id: 'card2', content: <Typography>Extra content</Typography> },
 *     { title: 'Card 3', id: 'card3', content: <Typography>More content</Typography> }
 *   ]}
 *   legend="Cards"
 *   hint="Choose a card to select"
 * />
 * ```
 */
export const RadioCardGroup = React.forwardRef(
  (
    {
      copy = 'en',
      tokens,
      radioCardTokens,
      variant,
      items = [],
      legend,
      tooltip,
      hint,
      hintPosition = 'inline',
      validation,
      feedback,
      initialCheckedId,
      checkedId,
      onChange,
      readOnly,
      name: inputGroupName,
      inactive,
      ...rest
    },
    ref
  ) => {
    const viewport = useViewport()
    const {
      space,
      fieldSpace,
      direction,
      showIcon,
      outlineWidth,
      borderTopLeftRadius,
      borderTopRightRadius,
      borderBottomLeftRadius,
      borderBottomRightRadius,
      outlineOffset
    } = useThemeTokens('RadioCardGroup', tokens, variant, {
      viewport
    })

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

    const { currentValue, setValue } = useInputValue({
      value: checkedId,
      initialValue: initialCheckedId,
      onChange,
      readOnly: readOnly || inactive
    })

    const StackContainer = direction === 'row' ? StackWrap : StackView
    // Needs 'radiogroup' role on direct parent of radios for MacOS Voiceover's numbering to work,
    // and also needs 'radiogroup' role on fieldset for correct description on focusing the set.
    // TODO: test this on more web screen readers.

    const uniqueFields = ['id']
    if (!containUniqueFields(items, uniqueFields)) {
      throw new Error(`RadioCardGroup items must have unique ${uniqueFields.join(', ')}`)
    }

    return (
      <Fieldset
        copy={copy}
        ref={ref}
        name={inputGroupName}
        legend={legend}
        tooltip={tooltip}
        hint={hint}
        hintPosition={hintPosition}
        space={fieldSpace}
        feedback={feedback}
        inactive={inactive || readOnly}
        validation={validation}
        showErrorBorder
        tokens={borderTokens}
        showIcon={showIcon}
        accessibilityRole="radiogroup"
        {...selectProps(rest)}
      >
        {(props) => (
          <StackContainer space={space} accessibilityRole="radiogroup">
            {items.map(({ title, content, id, onChange: itemOnChange, ...itemRest }, index) => {
              const cardId = id || `RadioCard[${index}]`
              const handleChange = (newCheckedState, event) => {
                if (typeof itemOnChange === 'function') itemOnChange(newCheckedState, event)
                if (newCheckedState) setValue(cardId, event)
              }

              return (
                <RadioCard
                  key={cardId}
                  id={cardId}
                  checked={currentValue === cardId}
                  onChange={handleChange}
                  inactive={inactive}
                  title={title}
                  name={inputGroupName}
                  tokens={radioCardTokens}
                  variant={variant}
                  readOnly={readOnly}
                  {...selectItemProps(itemRest)}
                  {...props}
                >
                  {content}
                </RadioCard>
              )
            })}
          </StackContainer>
        )}
      </Fieldset>
    )
  }
)
RadioCardGroup.displayName = 'RadioCardGroup'

RadioCardGroup.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * Whether the English or French copy will be used (e.g. for accessibility labels).
   */
  copy: PropTypes.oneOf(['en', 'fr']),
  /**
   * Optional theme token overrides for the outer RadioCardGroup component
   */
  tokens: getTokensPropType('RadioCardGroup'),
  /**
   * Optional theme token overrides for each inner RadioCard component
   */
  radioCardTokens: getTokensPropType('RadioCard'),
  /**
   * Theme variants, shared between both RadioCardGroup and RadioCard
   */
  variant: variantProp.propType,
  /**
   * Array of objects containing specifics for each RadioCard to be rendered in the group.
   */
  items: PropTypes.arrayOf(
    PropTypes.exact({
      ...selectedItemPropTypes,
      title: PropTypes.string,
      content: PropTypes.node,
      id: PropTypes.string,
      onChange: PropTypes.func
    })
  ),
  /**
   * Main text used to describe this group, used in Fieldset's Legend element.
   */
  legend: PropTypes.string,
  /**
   * Optional additional text giving more detail to help a user make a choice.
   */
  hint: PropTypes.string,
  /**
   * Position of the hint relative to label. Use `below` to display a larger hint below the label.
   */
  hintPosition: PropTypes.oneOf(['inline', 'below']),
  /**
   * Optional tooltip text content to include alongside the legend and hint.
   */
  tooltip: PropTypes.string,
  /**
   * 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 radio card with this id is selected on first render.
   */
  initialCheckedId: PropTypes.string,
  /**
   * If not undefined, the radio 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.
   */
  checkedId: 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,
  /**
   * If true, the radio cards cannot be selected by the user and simply show their current state.
   */
  readOnly: PropTypes.bool,
  /**
   * If true, the radio card cannot be interacted with, elements are set as `disabled` and if the
   * theme supports `inactive` appearances rules, these are applied.
   */
  inactive: PropTypes.bool,
  /**
   * On Web, this is passed to the `name` attribute of the fieldset and each radio input.
   */
  name: PropTypes.string
}
