import React from 'react'
import PropTypes from 'prop-types'
import {
  Box,
  TextInput,
  Spacer,
  Feedback,
  InputLabel,
  useInputValue,
  useCopy,
  useThemeTokensCallback,
  getTokensPropType,
  selectSystemProps,
  a11yProps
} from '@telus-uds/components-base'
import { htmlAttrs } from '../utils'

import { InputField, InputWrapper } from './styles'
import defaultDictionary from './dictionary'
import SideButton from './SideButton'

const { isNaN } = Number

const isNumber = (value) => {
  return typeof value === 'number' && !isNaN(value)
}

const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, htmlAttrs])

const QuantitySelector = React.forwardRef(
  (
    {
      id = 'quantity-selector',
      minNumber,
      maxNumber,
      defaultValue,
      value,
      label,
      hint,
      hintPosition = 'inline',
      tooltip,
      onChange,
      dictionary = defaultDictionary,
      accessibilityLabel,
      copy = 'en',
      variant = {
        alternative: false
      },
      tokens,
      testID = 'quantity-selector-testid',
      inactive = false,
      ...rest
    },
    ref
  ) => {
    const { disabled } = variant
    const [error, setError] = React.useState('')
    const getCopy = useCopy({ dictionary, copy })

    const getValidatedNumber = (numberToEvaluate) => {
      if (isNaN(numberToEvaluate)) return null
      if (isNumber(minNumber) && numberToEvaluate < minNumber)
        throw getCopy('errors').numberIsTooSmall(minNumber)
      if (isNumber(maxNumber) && numberToEvaluate > maxNumber)
        throw getCopy('errors').numberIsTooBig(maxNumber)
      return numberToEvaluate
    }

    const { currentValue: number, setValue: setNumber } = useInputValue({
      value: getValidatedNumber(value),
      initialValue: getValidatedNumber(defaultValue),
      onChange
    })

    const isDecreaseEnabled =
      ((!disabled && !isNumber(minNumber)) || number > minNumber) && !inactive
    const isIncreaseEnabled =
      ((!disabled && !isNumber(maxNumber)) || number < maxNumber) && !inactive
    const inputValue = isNumber(number) ? number.toString() : ''

    const updateNumber = (newNumber, originalInputEvent) => {
      try {
        const validatedNumber = getValidatedNumber(newNumber)
        setNumber(validatedNumber, originalInputEvent)
        setError('')
      } catch (e) {
        setError(e)
      }
    }

    const inputChangeHandler = (textInputValue, event) => {
      if (textInputValue === '') setNumber(null)
      const numberFromTextInput = Number(textInputValue)
      if (isNumber(numberFromTextInput)) {
        updateNumber(numberFromTextInput, event)
      } else {
        setError(getCopy('errors').invalidCharacters)
      }
    }

    const getTokens = useThemeTokensCallback('QuantitySelector', tokens, {
      inactive,
      ...variant
    })

    const renderLabelAndSpacer = () => {
      const { showTopSpace } = getTokens()

      return (
        <>
          {(label || hint) && (
            <InputLabel
              forId={id}
              label={label}
              hint={hint}
              hintPosition={hintPosition}
              tooltip={tooltip}
            />
          )}
          {label || hint ? <Spacer space={2} /> : showTopSpace && <Spacer space={2} />}
        </>
      )
    }

    const renderTextInput = () => (
      <TextInput
        inactive={inactive}
        nativeID={id}
        value={inputValue}
        defaultValue={defaultValue}
        tokens={(textInputState) => {
          const {
            inputWidth,
            inputBorderWidth,
            inputBorderColor,
            textColor,
            inputBackgroundColor,
            ...restOfTokens
          } = getTokens({
            ...textInputState
          })
          return {
            ...restOfTokens,
            order: 1,
            borderWidth: inputBorderWidth,
            backgroundColor: inputBackgroundColor,
            color: textColor,
            width: inputWidth,
            borderColor: inputBorderColor,
            borderRadius: 0,
            outerBorderWidth: 0
          }
        }}
        onChange={inputChangeHandler}
        // Using title as an accessibility workaround
        // given that accessibilityLabel is not available
        keyboardType="numeric"
        accessibilityLabel={accessibilityLabel ?? getCopy('accessibility').a11yLabel}
        accessibilityRole="spinbutton"
        accessibilityValueMax={maxNumber}
        accessibilityValueMin={minNumber}
        accessibilityValueNow={number}
      />
    )

    const flexOrder = (order) => ({ order })

    return (
      <Box testID={testID} ref={ref} {...selectProps(rest)}>
        {renderLabelAndSpacer()}
        <InputWrapper>
          <InputField>{renderTextInput()}</InputField>
          <div style={flexOrder(0)}>
            <SideButton
              isEnabled={isDecreaseEnabled}
              onPress={() => updateNumber(number - 1)}
              onDoubleClick={() => updateNumber(number - 1)}
              tokens={tokens}
              variant={{ decrease: true, inactive, ...variant }}
              accessibilityLabel={getCopy('accessibility').decreaseButton}
              accessibilityDisabled={!isDecreaseEnabled}
            />
          </div>
          <div style={flexOrder(2)}>
            <SideButton
              isEnabled={isIncreaseEnabled}
              onPress={() => updateNumber(number + 1)}
              onDoubleClick={() => updateNumber(number + 1)}
              accessibilityLabel={getCopy('accessibility').increaseButton}
              accessibilityDisabled={!isIncreaseEnabled}
              tokens={tokens}
              variant={{ increase: true, inactive, ...variant }}
            />
          </div>
        </InputWrapper>
        {error ? (
          <Box vertical={2}>
            <Feedback validation="error">{error}</Feedback>
          </Box>
        ) : null}
      </Box>
    )
  }
)

// If a language dictionary entry is provided, it must contain every key
const dictionaryContentShape = PropTypes.shape({
  accessibility: PropTypes.shape({
    a11yLabel: PropTypes.string.isRequired,
    increaseButton: PropTypes.string.isRequired,
    decreaseButton: PropTypes.string.isRequired
  }).isRequired,
  errors: PropTypes.shape({
    numberIsTooSmall: PropTypes.func.isRequired,
    numberIsTooBig: PropTypes.func.isRequired,
    invalidCharacters: PropTypes.string.isRequired
  }).isRequired
})

QuantitySelector.displayName = 'QuantitySelector'

QuantitySelector.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * The id of the input field
   */
  id: PropTypes.string,
  /**
   * The minimum number allowed
   */
  minNumber: PropTypes.number,
  /**
   * The maximum number allowed
   */
  maxNumber: PropTypes.number,
  /**
   * The callback function that is called when the value of the input field changes
   */
  onChange: PropTypes.func,
  /**
   * The default value of the input field
   */
  defaultValue: PropTypes.number,
  /**
   * If the input's state is to be controlled by a parent component, use this prop
   * together with the `onChange` to pass down and update the lifted state.
   */
  value: PropTypes.number,
  /**
   * The label of the input field
   */
  label: PropTypes.string,
  /**
   * The hint of the input field
   */
  hint: PropTypes.string,
  /**
   * The position of the hint. Could be shown along side the label or below it
   */
  hintPosition: PropTypes.oneOf(['inline', 'below']),
  /**
   * Adds a question mark which will display a tooltip when clicked
   */
  tooltip: PropTypes.string,
  /**
   * The accessibility label of the input field
   */
  accessibilityLabel: PropTypes.string,
  /**
   * The dictionary object containing the content for the input field
   */
  dictionary: PropTypes.shape({
    en: dictionaryContentShape,
    fr: dictionaryContentShape
  }),
  /**
   * The language to use for the copy.
   */
  copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr'])]),
  variant: PropTypes.exact({
    alternative: PropTypes.bool,
    disabled: PropTypes.bool
  }),

  tokens: getTokensPropType('QuantitySelector'),
  /**
   * Sets `data-testid` attribute on the input field for testing purposes.
   */
  testID: PropTypes.string,
  /**
   * If true, the quantity selector will be disabled
   */
  inactive: PropTypes.bool
}

export default QuantitySelector
