import React from 'react'
import { View, Platform } from 'react-native'

import PropTypes from 'prop-types'
import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider/useThemeTokens'
import { a11yProps } from '../utils/props/a11yProps'
import { getTokensPropType, selectTokens } from '../utils/props/tokens'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { useInputValue } from '../utils/input'
import { useSpacingScale } from '../utils/useSpacingScale'
import { textInputHandlerProps } from '../utils/props/handlerProps'
import { textInputProps } from '../utils/props/textInputProps'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { contentfulProps } from '../utils/props/contentfulProps'
import { TextInputBase } from '../TextInput/TextInputBase'
import { ButtonBase } from '../Button/ButtonBase'
import { useCopy } from '../utils/useCopy'
import { dictionary } from './dictionary'
import { Icon } from '../Icon/Icon'

const [selectContainerProps, selectedContainerPropTypes] = selectSystemProps([
  a11yProps,
  viewProps,
  contentfulProps
])
const [selectInputProps, selectedInputPropTypes] = selectSystemProps([
  textInputHandlerProps,
  textInputProps
])

const selectInputTokens = ({ searchTokens, buttonTokens, buttonsGapSize }) => {
  const { paddingRight: inputPaddingRight, clearButtonIcon, submitButtonIcon } = searchTokens
  const { paddingLeft = 0, paddingRight = 0, iconSize = 0 } = buttonTokens

  const buttonWidth = paddingLeft + paddingRight + iconSize

  const paddingWithButtons =
    inputPaddingRight +
    (submitButtonIcon ? buttonWidth : 0) +
    (clearButtonIcon ? buttonsGapSize + buttonWidth : 0)

  return { ...selectTokens('TextInput', searchTokens), paddingRight: paddingWithButtons }
}
const selectButtonTokens = (tokens) => selectTokens('SearchButton', tokens)

const selectIconTokens = ({ iconSize, iconColor }) => ({ color: iconColor, size: iconSize })

const selectBorderTokens = ({ borderWidth, mobileBorderWidth, ...tokens }) => ({
  borderWidth: Platform.OS === 'web' ? borderWidth : mobileBorderWidth,
  ...tokens
})

const modifyButtonTokens = (tokens) => {
  const modifiedTokens = { ...tokens }

  if (Platform.OS !== 'web') {
    modifiedTokens.backgroundColor = tokens.mobileBackgroundColor
  }

  return modifiedTokens
}

/**
 * The `Search` component is a combination of a `TextInput` and 2 different kinds of custom buttons.
 * Use `Search` to feature autocomplete interactions.
 *
 * This component includes a clear button, which will appear as text is entered and pressing it will
 * cause the input's internal state value to be set to an empty string.
 *
 * This component may only be used as uncontrolled, as the search value is managed within an internal state.
 * You can use the `onChange` prop to react to value changes and the `onSubmit` prop to react on search submission.
 *
 * Use the following props to supply additional accessibility labels for the input - `accessibilityLabel`,
 * clear button - `clearButtonAccessibilityLabel`, and submit button - `submitButtonAccessibilityLabel`.
 */
export const Search = React.forwardRef(
  (
    {
      initialValue,
      value,
      placeholder,
      inactive,
      onChange,
      onSubmit,
      onClear,
      onFocus,
      accessibilityLabel,
      copy = 'en',
      tokens,
      variant,
      nativeSubmitBtnID,
      searchIconPosition = 'right',
      dataSet,
      ...rest
    },
    ref
  ) => {
    const { currentValue = '', setValue } = useInputValue({
      value,
      initialValue,
      onChange
    })

    const themeTokens = useThemeTokens('Search', tokens, { inactive, ...variant })
    const buttonTokens = useThemeTokens('SearchButton', tokens, variant)
    const getThemeTokens = useThemeTokensCallback('Search', tokens, variant)
    const getButtonTokens = useThemeTokensCallback('SearchButton', tokens, variant)

    // TODO: support overriding the a11y props using a custom dictionary
    const getCopy = useCopy({ dictionary, copy })

    const {
      placeholderColor,
      buttonsGap,
      clearButtonIcon: ClearButtonIcon,
      submitButtonIcon: SubmitButtonIcon
    } = themeTokens

    // get the actual gap value for the current viewport
    const buttonsGapSize = useSpacingScale(buttonsGap)

    const handleSubmit = (event) => {
      if (onSubmit !== undefined) {
        onSubmit(currentValue, event)
      }
    }

    const handleClear = (event) => {
      setValue('', event)
      onClear?.('', event)
      ref?.current?.focus()
    }

    const handleFocus = (event) => {
      if (onFocus !== undefined) onFocus(event)
    }

    const isEmpty = currentValue === ''

    // Accessibility label should always be present and correctly localised
    const a11yLabelText = accessibilityLabel || getCopy('accessibilityLabel')
    // Placeholder is optional and may be unset by passing an empty string
    const placeholderText = placeholder ?? a11yLabelText
    const { nativeID, testID, ...containerProps } = selectContainerProps(rest)

    return (
      <View {...containerProps} dataSet={dataSet}>
        <TextInputBase
          nativeID={nativeID}
          testID={testID}
          {...selectInputProps(rest)}
          ref={ref}
          tokens={(pressableStates) =>
            selectInputTokens({
              searchTokens: selectBorderTokens(getThemeTokens(pressableStates)),
              buttonTokens,
              buttonsGapSize,
              isEmpty
            })
          }
          placeholder={placeholderText}
          placeholderTextColor={placeholderColor}
          inactive={inactive}
          enablesReturnKeyAutomatically
          returnKeyType="search"
          value={currentValue}
          onChange={setValue}
          onSubmitEditing={handleSubmit}
          onFocus={handleFocus}
          accessibilityLabel={a11yLabelText}
          direction={searchIconPosition}
          buttons={[
            ClearButtonIcon && !isEmpty && (
              <ButtonBase
                accessibilityLabel={getCopy('clearButtonAccessibilityLabel')}
                accessibilityRole="button"
                inactive={inactive}
                key="clear"
                onPress={handleClear}
                tokens={(pressableStates) =>
                  modifyButtonTokens(selectButtonTokens(getButtonTokens(pressableStates)))
                }
              >
                {(buttonState) => (
                  <ClearButtonIcon {...selectIconTokens(getButtonTokens(buttonState))} />
                )}
              </ButtonBase>
            ),
            SubmitButtonIcon &&
              (searchIconPosition === 'right' ? (
                <ButtonBase
                  accessibilityLabel={getCopy('submitButtonAccessibilityLabel')}
                  accessibilityRole="button"
                  inactive={inactive}
                  key="submit-right"
                  onPress={handleSubmit}
                  tokens={(buttonState) =>
                    selectButtonTokens(getButtonTokens({ ...buttonState, priority: 'high' }))
                  }
                  nativeID={nativeSubmitBtnID}
                >
                  {(buttonState) => (
                    <SubmitButtonIcon
                      {...selectIconTokens(getButtonTokens({ ...buttonState, priority: 'high' }))}
                    />
                  )}
                </ButtonBase>
              ) : (
                <Icon
                  icon={SubmitButtonIcon}
                  key="submitIcon"
                  testID="iconLeft"
                  tokens={{ color: themeTokens.iconLeftColor, size: themeTokens.iconLeftSize }}
                />
              ))
          ]}
        />
      </View>
    )
  }
)
Search.displayName = 'Search'

Search.propTypes = {
  ...selectedContainerPropTypes,
  ...selectedInputPropTypes,
  /**
   * Use this to set the initial value of the search input.
   * Updating `initialValue` will **not** update the actual value.
   */
  initialValue: PropTypes.string,
  /**
   * Label rendered in the search input when it has no value.
   */
  placeholder: PropTypes.string,
  /**
   * Side of the search input on which the submit button is rendered. Defaults to `right`.
   */
  searchIconPosition: PropTypes.oneOf(['left', 'right']),
  /**
   * Disables all user interactions with the search input.
   */
  inactive: PropTypes.bool,
  /**
   * Use to react upon search input's value changes.
   * Will receive the searched value as an argument.
   */
  onChange: PropTypes.func,
  /**
   * Triggered when the search input is submitted,
   * either by pressing the submit button, "Enter" key on web,
   * or "Search" key in a mobile keyboard.
   */
  onSubmit: PropTypes.func,
  /**
   * Triggered when the clear button is pressed.
   */
  onClear: PropTypes.func,
  /**
   * Triggered when the search input is focused.
   */
  onFocus: PropTypes.func,
  /**
   *
   * Use to provide an accessible label for the input (visually hidden).
   */
  accessibilityLabel: PropTypes.string,
  /**
   * Select English or French copy for the accessible labels.
   * You may also pass in a custom dictionary object.
   */
  copy: PropTypes.oneOfType([
    PropTypes.oneOf(['en', 'fr']),
    PropTypes.shape({
      accessibilityLabel: PropTypes.string,
      clearButtonAccessibilityLabel: PropTypes.string,
      submitButtonAccessibilityLabel: PropTypes.string
    })
  ]),
  tokens: getTokensPropType('Search'),
  variant: variantProp.propType,
  /**
   * ID for Submit button element on web
   */
  nativeSubmitBtnID: PropTypes.string
}
