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 { useMultipleInputValues } from '../utils/input'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { getStackedContent } from '../StackView/getStackedContent'
import { Checkbox } from './Checkbox'
import { Fieldset } from '../Fieldset/Fieldset'

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

/**
 * A group of Checkboxs that behave as a fieldset. Use when users select any number of choices from options.
 *
 * ## Component API
 *
 * Use the `items` prop to pass an array of objects describing each Checkbox in the group:
 *
 *  - `label`: main text passed to CheckboxGroup's `label` prop
 *  - `id`: identifier used to store which CheckboxGroup is selected (uses `label` 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 CheckboxGroup 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
 *
 * CheckboxGroup accepts all the common accessibility props, and controls the accessibility state
 * of children like Checkbox and Feedback based on current state.
 *
 * @example
 * ```jsx
 * <CheckboxGroup
 *   initialCheckedIds="check1"
 *   items={[
 *     { label: 'Checkbox 1', id: 'check1' },
 *     { label: 'Checkbox 2', id: 'check2' },
 *     { label: 'Checkbox 3', id: 'check3' }
 *   ]}
 *   legend="Checkboxes"
 *   hint="Choose from these options"
 * />
 * ```
 */
export const CheckboxGroup = React.forwardRef(
  (
    {
      tokens,
      radioTokens,
      variant,
      items = [],
      legend,
      tooltip,
      hint,
      hintPosition = 'inline',

      validation,
      feedback,
      initialCheckedIds,
      checkedIds,
      onChange,
      readOnly,
      name: inputGroupName,
      inactive,
      ...rest
    },
    ref
  ) => {
    const viewport = useViewport()

    const {
      space,
      fieldSpace,
      showIcon,
      outlineWidth,
      borderTopLeftRadius,
      borderTopRightRadius,
      borderBottomLeftRadius,
      borderBottomRightRadius,
      outlineOffset
    } = useThemeTokens('CheckboxGroup', tokens, variant, {
      viewport
    })

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

    const { currentValues, toggleOneValue } = useMultipleInputValues({
      values: checkedIds,
      initialValues: initialCheckedIds,
      onChange,
      readOnly: readOnly || inactive
    })

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

    const checkboxes = items.map(
      (
        {
          label,
          id,
          description,
          onChange: itemOnChange,
          ref: itemRef,
          inactive: itemInactive,
          variant: itemVariant,
          ...itemRest
        },
        index
      ) => {
        const checkboxId = id || `Checkbox[${index}]`
        const handleChange = (newCheckedState, event) => {
          if (typeof itemOnChange === 'function') itemOnChange(newCheckedState, event)
          toggleOneValue(checkboxId, event)
        }

        return (
          <Checkbox
            ref={itemRef}
            key={checkboxId}
            id={checkboxId}
            checked={currentValues.includes(checkboxId)}
            onChange={handleChange}
            inactive={itemInactive}
            label={label}
            name={inputGroupName}
            tokens={radioTokens}
            variant={{ ...variant, ...itemVariant }}
            description={description}
            {...selectItemProps(itemRest)}
          />
        )
      }
    )

    return (
      <Fieldset
        ref={ref}
        name={inputGroupName}
        legend={legend}
        tooltip={tooltip}
        hint={hint}
        hintPosition={hintPosition}
        space={fieldSpace}
        feedback={feedback}
        inactive={inactive}
        validation={validation}
        showErrorBorder
        tokens={borderTokens}
        showIcon={showIcon}
        {...selectProps(rest)}
      >
        {getStackedContent(checkboxes, { space, direction: 'column' })}
      </Fieldset>
    )
  }
)
CheckboxGroup.displayName = 'CheckboxGroup'

CheckboxGroup.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * Optional theme token overrides for the outer CheckboxGroup component
   */
  tokens: getTokensPropType('CheckboxGroup'),
  /**
   * Optional theme token overrides for each inner Checkbox component
   */
  radioTokens: getTokensPropType('Checkbox'),
  /**
   * Theme variants, shared between both CheckboxGroup and Checkbox
   */
  variant: variantProp.propType,
  /**
   * Array of objects containing specifics for each Checkbox to be rendered in the group.
   */
  items: PropTypes.arrayOf(
    PropTypes.exact({
      ...selectedItemPropTypes,
      label: PropTypes.string,
      id: PropTypes.string,
      onChange: PropTypes.func,
      ref: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.any })]),
      inactive: PropTypes.bool,
      description: PropTypes.string
    })
  ),
  /**
   * 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 checkboxed with this ids are selected on first render.
   */
  initialCheckedIds: PropTypes.arrayOf(PropTypes.string),
  /**
   * If not undefined, the checkboxes with these ids is selected, and the
   * element's selection state will be controlled by its parent using the `onChange` function.
   */
  checkedIds: 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,
  /**
   * If true, the checkboxes cannot be selected by the user and simply show their current state.
   */
  readOnly: PropTypes.bool,
  /**
   * If true, the checkboxes 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 checkbox input.
   */
  name: PropTypes.string
}
