import React from 'react'
import PropTypes from 'prop-types'
import { Platform, StyleSheet, TextInput as NativeTextInput, View } from 'react-native'
import { applyTextStyles, applyOuterBorder } from '../ThemeProvider/utils/styles'
import { useTheme } from '../ThemeProvider/useTheme'
import { useThemeTokens } from '../ThemeProvider/useThemeTokens'
import { StackView } from '../StackView/StackView'
import { IconButton } from '../IconButton/IconButton'
import { Icon } from '../Icon/Icon'
import { a11yProps } from '../utils/props/a11yProps'
import { getTokensPropType } from '../utils/props/tokens'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { textInputHandlerProps } from '../utils/props/handlerProps'
import { textInputProps } from '../utils/props/textInputProps'
import { useCopy } from '../utils/useCopy'
import { useInputValue } from '../utils/input'
import { useSpacingScale } from '../utils/useSpacingScale'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { dictionary } from './dictionary'

const [selectProps, selectedSystemPropTypes] = selectSystemProps([
  a11yProps,
  textInputHandlerProps,
  textInputProps,
  viewProps
])

const selectInputStyles = (
  {
    backgroundColor,
    color,
    borderWidth = 0,
    borderColor,
    borderRadius,
    paddingTop = 0,
    paddingBottom = 0,
    paddingLeft,
    paddingRight = 0,
    fontName,
    fontSize,
    fontWeight,
    lineHeight,
    icon,
    minLines,
    maxLines,
    width,
    height
  },
  themeOptions,
  inactive,
  type,
  buttonCount = 0,
  buttonSize = 0,
  buttonsGapSize = 0,
  isPassword,
  iconLeftWidth,
  iconLeftGap,
  direction
) => {
  // Subtract border width from padding so overall input width/height doesn't
  // jump around if the border width changes (avoiding NaN and negative padding)
  const offsetBorder = (value) =>
    typeof value === 'number' ? Math.max(0, value - borderWidth) : value

  const textStyles = applyTextStyles({
    fontName: isPassword ? undefined : fontName, // In this case, we don't want to apply the fontName to the input if it's a password because Monotype don't have support for the masked characters in mobile.
    fontSize,
    lineHeight,
    fontWeight,
    themeOptions
  })

  function linesToHeight(lines) {
    const { lineHeight: absoluteLineHeight } = textStyles

    return lines !== undefined ? lines * absoluteLineHeight + paddingTop + paddingBottom : undefined
  }

  const minHeight = linesToHeight(minLines)
  const maxHeight = linesToHeight(maxLines)

  const webStyles = Platform.select({
    web: {
      outline: 'none',
      cursor: inactive ? 'not-allowed' : undefined,
      resize: minHeight !== maxHeight ? 'vertical' : 'none' // does nothing for an input, only needed for textarea
    }
  })

  const getPaddingLeft = () => {
    if (type === 'card') {
      return offsetBorder(paddingLeft + 34)
    }
    if (direction === 'left') {
      return offsetBorder(iconLeftWidth + iconLeftGap + staticStyles.iconLeftContainer.paddingLeft)
    }
    return offsetBorder(paddingLeft)
  }

  const buttonSpacing = isPassword ? buttonsGapSize : -buttonsGapSize
  const adjustedPaddingRight = paddingRight + (buttonCount ? 1 : 0) * (buttonSize + buttonSpacing)
  const adjustedPaddingWithButtons = buttonCount > 1 ? paddingRight : adjustedPaddingRight
  const paddingWithIcon = buttonSize + adjustedPaddingRight

  return {
    backgroundColor,
    color,
    borderWidth,
    borderColor,
    borderRadius,
    paddingLeft: getPaddingLeft(),
    paddingRight: icon ? offsetBorder(paddingWithIcon) : offsetBorder(adjustedPaddingWithButtons),
    paddingTop: offsetBorder(paddingTop),
    paddingBottom: offsetBorder(paddingBottom),
    minHeight,
    maxHeight,
    width,
    height,
    ...textStyles,
    ...webStyles
  }
}

const selectOuterBorderStyles = ({
  outerBackgroundColor,
  outerBorderWidth,
  outerBorderColor,
  borderRadius
}) => ({
  background: outerBackgroundColor,
  ...applyOuterBorder({
    outerBorderColor,
    outerBorderWidth,
    borderRadius
  })
})

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

const selectIconContainerStyles = (
  { buttonSize, buttonsGapSize, paddingRight, paddingBottom },
  buttonCount
) => ({
  paddingRight: paddingRight + buttonCount * (buttonSize + buttonsGapSize),
  paddingBottom
})

const selectIconCardLeftContainerStyles = ({ leftIconPaddingBottom }) => ({
  paddingBottom: leftIconPaddingBottom
})

const selectIconLeftContainerStyles = ({ iconLeftPaddingBottom }) => ({
  paddingBottom: iconLeftPaddingBottom
})

const selectButtonsContainerStyle = ({ buttonsPaddingRight }) => ({
  paddingRight: buttonsPaddingRight
})

const getIcon = (cardNumber = '', { defaultCreditIcon, amexIcon, visaIcon, masterCardIcon }) => {
  const sanitizedCardNumber = cardNumber.replace(/[^0-9]/g, '')
  const firstFourDigits = sanitizedCardNumber.slice(0, 4)
  const number = Number(firstFourDigits)

  const visaRange = number >= 4000 && number <= 4999
  const amexRange = (number >= 3400 && number <= 3499) || (number >= 3700 && number <= 3799)
  const masterCardRange = (number >= 5100 && number <= 5599) || (number >= 2221 && number <= 2720)

  let selectedIcon = { icon: defaultCreditIcon, testID: 'default' }
  const objVisaIcon = { icon: visaIcon, testID: 'visa' }
  const objAmexIcon = { icon: amexIcon, testID: 'amex' }
  const objMasterCardIcon = { icon: masterCardIcon, testID: 'mastercard' }

  if (visaRange) selectedIcon = objVisaIcon
  else if (amexRange) selectedIcon = objAmexIcon
  else if (masterCardRange) selectedIcon = objMasterCardIcon

  return <Icon icon={selectedIcon.icon} variant={{ size: 'large' }} testID={selectedIcon.testID} />
}

export const TextInputBase = React.forwardRef(
  (
    {
      buttons = [],
      copy = 'en',
      height,
      inactive,
      initialValue,
      onBlur,
      onChange,
      onChangeText,
      onClear,
      onFocus,
      onMouseOut,
      onMouseOver,
      pattern,
      readOnly,
      tokens,
      value,
      variant = {},
      type,
      onKeyPress,
      direction,
      ...rest
    },
    ref
  ) => {
    const [isFocused, setIsFocused] = React.useState(false)
    const [showPassword, setShowPassword] = React.useState(false)

    const handleFocus = (event) => {
      setIsFocused(true)
      if (typeof onFocus === 'function') onFocus(event)
    }
    const handleBlur = (event) => {
      setIsFocused(false)
      if (typeof onBlur === 'function') onBlur(event)
    }

    const [isHovered, setIsHovered] = React.useState(false)
    const handleMouseOver = (event) => {
      setIsHovered(true)
      if (typeof onMouseOver === 'function') onMouseOver(event)
    }
    const handleMouseOut = (event) => {
      setIsHovered(false)
      if (typeof onMouseOut === 'function') onMouseOut(event)
    }

    const defaultRef = React.useRef()
    const inputRef = ref ?? defaultRef

    const { currentValue, resetValue, setValue, isControlled, isDirty } = useInputValue({
      value,
      initialValue,
      inputRef,
      onChange,
      readOnly
    })
    const { password, numeric } = variant
    const isNumeric = numeric || type === 'card' || type === 'number'
    const isPassword = password || type === 'password'

    const element = inputRef?.current
    React.useEffect(() => {
      if (Platform.OS === 'web' && pattern && element) {
        // React Native Web doesn't support `pattern`, so we have to attach it via a ref,
        // which a `pattern` user must provide anyway to call .checkValidity() on the element.
        element.pattern = pattern
      }
    }, [element, pattern])

    const handleChangeText = (event) => {
      const text = event.nativeEvent?.text ?? event.target?.value
      // Do NOT use `|| undefined`: empty string is falsy and would break the controlled contract
      let filteredText = isNumeric ? text?.replace(/[^\d]/g, '') ?? '' : text
      if (type === 'card' && filteredText) {
        const formattedValue = filteredText.replace(/[^a-zA-Z0-9]/g, '')
        const regex = new RegExp(`([a-zA-Z0-9]{4})(?=[a-zA-Z0-9])`, 'g')
        // Add a space every 4 digits starting from the 5th position
        filteredText = formattedValue.replace(regex, '$1 ').trim()
      }
      // Apply maxLength if provided
      if (rest.maxLength && filteredText && filteredText.length > rest.maxLength) {
        filteredText = filteredText.substring(0, rest.maxLength)
      }
      setValue(filteredText, event)
      if (typeof onChangeText === 'function') onChangeText(filteredText, event)
    }

    const states = { focus: isFocused, hover: isHovered, inactive }

    const themeTokens = useThemeTokens('TextInput', tokens, variant, states)

    const handleClear = (event) => {
      onClear?.(event)
      resetValue(event)
      inputRef?.current?.focus()
    }

    const handleShowOrHide = () => {
      if (!variant.inactive) setShowPassword(!showPassword)
    }

    const {
      buttonsGap,
      clearButtonIcon: ClearButtonIcon,
      icon: IconComponent,
      passwordShowButtonIcon,
      passwordHideButtonIcon,
      defaultCreditIcon,
      amexIcon,
      visaIcon,
      masterCardIcon,
      iconLeftGap
    } = themeTokens
    const buttonsGapSize = useSpacingScale(buttonsGap)
    const getCopy = useCopy({ dictionary, copy })
    const textInputButtons =
      direction === 'left'
        ? buttons.filter((button) => button && button.key !== 'submitIcon')
        : buttons
    const submitIcon =
      direction === 'left' ? buttons.find((button) => button && button.key === 'submitIcon') : null

    if (onClear && isDirty) {
      textInputButtons?.unshift(
        <IconButton
          accessibilityLabel={getCopy('clearButtonAccessibilityLabel')}
          icon={ClearButtonIcon}
          key="clear"
          onPress={handleClear}
          variant={{ subtle: true }}
        />
      )
    }

    if (isPassword) {
      textInputButtons?.unshift(
        <IconButton
          accessibilityLabel={
            showPassword
              ? getCopy('hidePasswordAccessibilityLabel')
              : getCopy('showPasswordAccessibilityLabel')
          }
          icon={!showPassword ? passwordShowButtonIcon : passwordHideButtonIcon}
          key={!showPassword ? 'hide' : 'show'}
          onPress={handleShowOrHide}
          variant={{ subtle: true, inactive: variant.inactive, size: 'large' }}
          tokens={{ width: 40, height: 40 }}
        />
      )
    }

    const inputProps = {
      ...selectProps(rest),
      editable: !inactive,
      onFocus: handleFocus,
      onBlur: handleBlur,
      onMouseOver: handleMouseOver,
      onMouseOut: handleMouseOut,
      onChange: handleChangeText,
      defaultValue: initialValue,
      maxLength: type === 'card' ? 19 : rest.maxLength,
      value: isControlled ? currentValue : undefined,
      onKeyPress
    }

    const { themeOptions } = useTheme()
    const iconLeftWidth = submitIcon ? submitIcon.props.tokens.size ?? 0 : 0
    const nativeInputStyle = selectInputStyles(
      { ...themeTokens, height },
      themeOptions,
      inactive,
      type,
      buttons?.length,
      themeTokens.buttonSize,
      buttonsGapSize,
      isPassword,
      iconLeftWidth,
      iconLeftGap,
      direction
    )

    const shouldShowSubmitIcon = submitIcon && direction === 'left' && !inactive

    return (
      <View style={selectOuterBorderStyles(themeTokens)}>
        {shouldShowSubmitIcon && (
          <View
            style={[staticStyles.iconLeftContainer, selectIconLeftContainerStyles(themeTokens)]}
          >
            {submitIcon}
          </View>
        )}
        {type === 'card' && (
          <View
            pointerEvents="none"
            style={[
              staticStyles.iconCardLeftContainer,
              selectIconCardLeftContainerStyles(themeTokens)
            ]}
          >
            {getIcon(currentValue, { defaultCreditIcon, amexIcon, visaIcon, masterCardIcon })}
          </View>
        )}
        <NativeTextInput
          ref={inputRef}
          keyboardType={isNumeric ? 'numeric' : 'default'}
          inputMode={isNumeric ? 'numeric' : undefined}
          style={nativeInputStyle}
          secureTextEntry={isPassword && !showPassword}
          {...inputProps}
        />
        {IconComponent && (
          <View
            pointerEvents="none" // avoid hijacking input press events
            style={[
              staticStyles.rightIconContainer,
              selectIconContainerStyles({ ...themeTokens, buttonsGapSize }, buttons?.length)
            ]}
          >
            <IconComponent {...selectIconTokens(themeTokens)} />
          </View>
        )}
        {buttons?.length > 0 && !inactive && (
          <View style={[staticStyles.buttonsContainer, selectButtonsContainerStyle(themeTokens)]}>
            <StackView direction="row" space={buttonsGap}>
              {textInputButtons}
            </StackView>
          </View>
        )}
      </View>
    )
  }
)
TextInputBase.displayName = 'TextInputBase'

TextInputBase.propTypes = {
  ...selectedSystemPropTypes,
  buttons: PropTypes.arrayOf(PropTypes.node),
  /**
   * 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({
      clearButtonAccessibilityLabel: PropTypes.string
    })
  ]),
  type: PropTypes.oneOfType([PropTypes.oneOf(['password', 'card', 'number'])]),
  height: PropTypes.number,
  inactive: PropTypes.bool,
  initialValue: PropTypes.string,
  onBlur: PropTypes.func,
  onChange: PropTypes.func,
  onChangeText: PropTypes.func,
  onClear: PropTypes.func,
  onFocus: PropTypes.func,
  onMouseOut: PropTypes.func,
  onMouseOver: PropTypes.func,
  onKeyPress: PropTypes.func,
  readOnly: PropTypes.bool,
  tokens: getTokensPropType('TextInput', 'TextArea'),
  value: PropTypes.string,
  variant: variantProp.propType
}

const staticStyles = StyleSheet.create({
  buttonsContainer: {
    position: 'absolute',
    flexDirection: 'row',
    alignItems: 'center',
    right: 0,
    top: 0,
    bottom: 0,
    justifyContent: 'center'
  },
  rightIconContainer: {
    position: 'absolute',
    right: 0,
    bottom: 0
  },
  iconCardLeftContainer: {
    paddingLeft: 10,
    position: 'absolute',
    left: 0,
    bottom: 0,
    zIndex: 1
  },
  iconLeftContainer: {
    paddingLeft: 10,
    position: 'absolute',
    left: 0,
    bottom: 0,
    zIndex: 1
  }
})
