import React from 'react'
import PropTypes from 'prop-types'
import { Platform, Text, View } from 'react-native'

import { ButtonBase } from '../Button/ButtonBase'
import { Icon } from '../Icon/Icon'
import { StackWrap } from '../StackView/StackWrap'
import { getStackedContent } from '../StackView/getStackedContent'
import { useViewport } from '../ViewportProvider/useViewport'
import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider/useThemeTokens'
import { a11yProps } from '../utils/props/a11yProps'
import { containUniqueFields } from '../utils/containUniqueFields'
import { focusHandlerProps } from '../utils/props/handlerProps'
import { getTokensPropType, selectTokens } from '../utils/props/tokens'
import { pressProps } from '../utils/props/pressProps'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { useMultipleInputValues } from '../utils/input'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { getPressHandlersWithArgs } from '../utils/pressability'

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

const separateIconTextTokens = (
  {
    icon,
    iconPosition,
    iconSpace,
    iconSize,
    iconColor,
    iconBackground,
    iconBorderRadius,
    iconAlignSelf,
    iconPadding,
    iconTranslateX,
    iconTranslateY,
    ...rest
  },
  returnRest
) =>
  returnRest
    ? rest
    : {
        icon,
        iconPosition,
        iconSpace,
        iconWrapperStyle: {
          backgroundColor: iconBackground,
          borderRadius: iconBorderRadius,
          alignSelf: iconAlignSelf,
          padding: iconPadding,
          ...Platform.select({
            // TODO: https://github.com/telus/universal-design-system/issues/487
            web: { transition: 'color 200ms, background 200ms' }
          })
        },
        iconTokens: {
          size: iconSize,
          color: iconColor,
          translateX: iconTranslateX,
          translateY: iconTranslateY
        }
      }

const selectIconTextTokens = (tokens) => separateIconTextTokens(tokens, false)
const selectNonIconTextTokens = (tokens) => separateIconTextTokens(tokens, true)

export const Tags = React.forwardRef(
  (
    {
      variant,
      tokens,
      items = [],
      values,
      initialValues,
      maxValues,
      onChange,
      readOnly = false,
      inactive = false,
      accessibilityRole = Platform.select({ web: 'group', default: 'none' }),
      ...rest
    },
    ref
  ) => {
    const viewport = useViewport()
    const themeTokens = useThemeTokens('Tags', tokens, variant, { viewport })
    const stackTokens = selectTokens('StackView', themeTokens)
    const { direction, space } = themeTokens

    const getItemTokens = useThemeTokensCallback('TagsItem', tokens, variant)

    const getButtonTokens = (buttonState) =>
      // Remove icon-text-related tokens, since we want to handle them ourselves, not use ButtonBase's handling
      selectTokens('Button', selectNonIconTextTokens(getItemTokens(buttonState)))

    const { currentValues, toggleOneValue } = useMultipleInputValues({
      initialValues,
      values,
      maxValues,
      onChange,
      readOnly
    })

    const selectedProps = selectProps({
      accessibilityRole,
      ...rest
    })
    const itemA11yRole = 'checkbox'

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

    return (
      <StackWrap
        ref={ref}
        {...selectedProps}
        space={space}
        direction={direction}
        tokens={stackTokens}
      >
        {items.map(({ label, id = label, ref: itemRef, ...itemRest }, index) => {
          const isSelected = currentValues.includes(id)

          // Pass an object of relevant component state as first argument for any passed-in press handlers
          const pressHandlers = getPressHandlersWithArgs(rest, [{ id, label, currentValues }])

          const handlePress = (event) => {
            if (pressHandlers.onPress) pressHandlers.onPress(event)
            toggleOneValue(id, event)
          }

          const itemProps = selectItemProps({
            accessibilityState: { checked: isSelected },
            accessibilityRole: itemA11yRole,
            ...a11yProps.getPositionInSet(items.length, index),
            ...itemRest
          })

          return (
            <ButtonBase
              ref={itemRef}
              key={id}
              {...pressHandlers}
              onPress={handlePress}
              tokens={getButtonTokens}
              selected={isSelected}
              inactive={inactive}
              {...itemProps}
            >
              {({ textStyles, ...buttonState }) => {
                // TODO: once Icon/IconButton designs are stable, see if this sort of styling around
                // an icon should go in Icon itself, or possibly via an IconText token set. Related issues:
                // - Icon: https://github.com/telus/universal-design-system/issues/327
                // - IconButton: https://github.com/telus/universal-design-system/issues/281
                // - Token sets: https://github.com/telus/universal-design-system/issues/782

                const itemTokens = getItemTokens(buttonState)

                const {
                  iconTokens,
                  iconPosition,
                  iconSpace,
                  iconWrapperStyle,
                  icon: IconComponent
                } = selectIconTextTokens(itemTokens)

                const iconContent = IconComponent ? (
                  <View style={iconWrapperStyle}>
                    <Icon icon={IconComponent} tokens={iconTokens} />
                  </View>
                ) : null
                const textContent = <Text style={textStyles}>{label}</Text>

                return getStackedContent(
                  iconPosition === 'left' ? [iconContent, textContent] : [textContent, iconContent],
                  { space: iconSpace, direction: 'row' }
                )
              }}
            </ButtonBase>
          )
        })}
      </StackWrap>
    )
  }
)
Tags.displayName = 'Tags'

Tags.propTypes = {
  ...selectedSystemPropTypes,
  tokens: getTokensPropType('Tags'),
  variant: variantProp.propType,
  /**
   * Makes the current selection visible but prevents the user from changing it.
   */
  readOnly: PropTypes.bool,
  /**
   * If true, the tags cannot be interacted with, and if the theme supports `inactive`
   * appearance rules, these are applied.
   */
  inactive: PropTypes.bool,
  /**
   * If provided, sets a maximum number of items a user may select at once.
   */
  maxValues: PropTypes.number,
  /**
   * The options a user may select
   */
  items: PropTypes.arrayOf(
    PropTypes.shape({
      ...selectedItemPropTypes,
      /**
       * The text displayed to the user in the button, describing this option,
       * passed to the button as its child.
       */
      label: PropTypes.string.isRequired,
      /**
       * An optional accessibility label may be passed to ButtonGroup Buttons
       * which is applied as normal for a React Native accessibilityLabel prop.
       */
      accessibilityLabel: PropTypes.string,
      /**
       * An optional unique string may be provided to identify this option,
       * which will be used in code and passed to any onChange function.
       * If not provided, the label is used.
       */
      id: PropTypes.string,
      /**
       * An optional ref for one individual Tag button in the tags
       */
      ref: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.any })])
    })
  ),
  /**
   * If provided, this function is called when the current selection is changed
   * and is passed an array of the `id`s of all currently selected `items`.
   */
  onChange: PropTypes.func,
  /**
   * If the selected item(s) in the button group are to be controlled externally by
   * a parent component, pass an array of strings as well as an `onChange` handler.
   * Passing an array for "values" makes the ButtonGroup a "controlled" component that
   * expects its state to be handled via `onChange` and so doesn't handle it itself.
   */
  values: PropTypes.arrayOf(PropTypes.string),
  /**
   * If `values` is not passed, making the ButtonGroup an "uncontrolled" component
   * managing its own selected state, a default set of selections may be provided.
   * Changing the `initialValues` does not change the user's selections.
   */
  initialValues: PropTypes.arrayOf(PropTypes.string),
  /**
   * AccesibilityRole for the Tags
   */
  accessibilityRole: PropTypes.string
}
