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

import { Feedback } from '../Feedback/Feedback'
import { getTokensPropType } from '../utils/props/tokens'
import { spacingProps } from '../utils/props/spacingProps'
import { variantProp } from '../utils/props/variantProp'
import { useThemeTokens } from '../ThemeProvider/useThemeTokens'
import { FieldsetContainer } from './FieldsetContainer'
import { getStackedContent } from '../StackView/getStackedContent'
import { InputLabel } from '../InputLabel/InputLabel'
import { useInputSupports } from '../InputSupports/useInputSupports'

/**
 * An alternative to InputSupports for groups of input elements that, on web, are to be
 * wrapped in a `<fieldset>` tag, with label content displated in a `<legend>` tag.
 *
 * Follows the same theming and most of the same props as InputSupports.
 */
export const Fieldset = React.forwardRef(
  (
    {
      copy = 'en',
      space,
      feedback,
      feedbackPosition = 'top',
      validation,
      showIcon = false,
      showErrorBorder = false,
      legend,
      hint,
      hintPosition,
      tooltip,
      inactive,
      accessibilityRole,
      name: fieldsetName,
      children,
      variant = {},
      tokens = {},
      ...rest
    },
    ref
  ) => {
    /* Fieldset tokenization:
    - error variant can only be activated by passing showErrorBorder and also having validaton as 'error'
     */
    const themeTokens = useThemeTokens('Fieldset', tokens, {
      error: validation === 'error' && showErrorBorder,
      ...variant
    })
    // Skips `inputId` because a fieldset' legend describes multiple inputs
    const { hintId, feedbackId, a11yProps } = useInputSupports({
      feedback,
      hint,
      label: legend,
      validation
    })

    const legendContent = legend && (
      <InputLabel
        copy={copy}
        label={legend}
        hint={hint}
        hintPosition={hintPosition}
        hintId={hintId}
        tooltip={tooltip}
      />
    )
    const feedbackContent = feedback && (
      <Feedback
        id={feedbackId}
        title={feedback}
        validation={validation}
        variant={{ icon: showIcon }}
      />
    )

    const borderStyles = ({
      outlineWidth,
      outlineColor,
      borderTopLeftRadius,
      borderTopRightRadius,
      borderBottomLeftRadius,
      borderBottomRightRadius,
      paddingBottom,
      paddingRight,
      paddingTop,
      paddingLeft,
      outlineOffset
    }) => ({
      outlineWidth,
      outlineColor,
      borderTopLeftRadius: borderTopLeftRadius * 0.1,
      borderTopRightRadius: borderTopRightRadius * 0.1,
      borderBottomLeftRadius: borderBottomLeftRadius * 0.1,
      borderBottomRightRadius: borderBottomRightRadius * 0.1,
      paddingBottom,
      paddingRight,
      paddingTop,
      paddingLeft,
      outlineOffset,
      borderWidth: 0,
      outlineStyle: validation === 'error' && showErrorBorder ? 'solid' : 'none'
    })

    // Some accessibility patterns depend on elements being direct children, e.g. fieldset -> legend,
    // and on MacOS voiceover, radiogroup -> radio. To allow elements to be direct children of the
    // fieldset as much as possible, but also allow different spacing within content and between
    // fieldset / feedback / etc, wrap resolved children in a fragment, and stack with `preserveFragments`.
    const childContent = (
      <React.Fragment key="fieldset-children">
        {typeof children === 'function' ? children(a11yProps) : children}
      </React.Fragment>
    )
    const legendWithKey = legendContent ? (
      <React.Fragment key="fieldset-legend">{legendContent}</React.Fragment>
    ) : null
    const feedbackWithKey = feedbackContent ? (
      <React.Fragment key="fieldset-feedback">{feedbackContent}</React.Fragment>
    ) : null
    const orderedContent =
      feedbackPosition === 'bottom'
        ? [legendWithKey, feedbackWithKey, childContent].filter(Boolean)
        : [legendWithKey, childContent, feedbackWithKey].filter(Boolean)
    const stackedContent = getStackedContent(orderedContent, { space, preserveFragments: true })
    return (
      <FieldsetContainer
        ref={ref}
        inactive={inactive}
        accessibilityRole={accessibilityRole}
        name={fieldsetName}
        borderStyle={borderStyles(themeTokens)}
        showBorderStyle={showErrorBorder}
        {...rest}
      >
        {stackedContent}
      </FieldsetContainer>
    )
  }
)

Fieldset.displayName = 'Fieldset'

Fieldset.propTypes = {
  variant: variantProp.propType,
  tokens: getTokensPropType('Fieldset'),
  /**
   * Content of the fieldset. A render function receives the accessibility props to spread onto
   * the grouped inputs.
   */
  children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
  /**
   * Whether the English or French copy will be used (e.g. for accessibility labels).
   */
  copy: PropTypes.oneOf(['en', 'fr']),
  /**
   * The accessibility role of the `<fieldset>` element itself. Other React Native accessibility
   * props are not supported because there is not an appropriate counterpart for Fieldsets.
   */
  accessibilityRole: PropTypes.string,
  /**
   * The amout of space between legend, feedback and content; according  to the theme's spacing scale.
   * Either a number corresponding to a position on the theme's spacing scale (1 is smallest, 2 is second smallest, etc),
   * or, a SpacingObject with viewport keys and options (see `useSpacingScale` for details).
   */
  space: spacingProps.types.spacingValue,
  /**
   * The text for the fieldset's legend, describing the fieldset content.
   */
  legend: PropTypes.string,
  /**
   * A short description of the expected input.
   */
  hint: PropTypes.string,
  /**
   * Position of the hint relative to label.
   */
  hintPosition: PropTypes.oneOf(['inline', 'below']),
  /**
   * A detailed description of validation error/success or additional instructions.
   * Visual variant is determined based on the `validation` prop.
   */
  feedback: PropTypes.string,
  /**
   * Position of the feedback block relative to the fieldset's content.
   */
  feedbackPosition: PropTypes.oneOf(['above', 'below']),
  /**
   * If true, the fieldset is treated as inactive (setting `disabled` attribute on web).
   */
  inactive: PropTypes.bool,
  /**
   * Content of an optional `Tooltip`. If set, a tooltip button will be shown next to the label.
   */
  tooltip: PropTypes.string,
  /**
   * On web, this is passed to the `<fieldset>`'s `name` attribute.
   */
  name: PropTypes.string,
  /**
   * Use to visually mark an input as valid or invalid.
   */
  validation: PropTypes.oneOf(['error', 'success']),
  /**
   * Use to show error or success icon in the feedback
   */
  showIcon: PropTypes.bool,
  /**
   * Use to show border in case of error for a group of components
   */
  showErrorBorder: PropTypes.bool
}
