import React from 'react'
import PropTypes from 'prop-types'
import { View, StyleSheet, Platform } from 'react-native'
import { inputSupportsProps } from '../utils/props/inputSupportsProps'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { TextInput } from '../TextInput/TextInput'
import { StackView } from '../StackView/StackView'
import { InputSupports } from '../InputSupports/InputSupports'
import { useThemeTokens } from '../ThemeProvider/useThemeTokens'

const [selectProps, selectedSystemPropTypes] = selectSystemProps([inputSupportsProps])

const selectCodeTextInputTokens = ({ outerBorderColor, outerBackgroundColor }) => {
  return {
    outerBorderColor,
    outerBackgroundColor,
    icon: null
  }
}

export const Validator = React.forwardRef(
  ({ value = '', inactive, mask = '', onChange, tokens = {}, variant = {}, ...rest }, ref) => {
    const defaultRef = React.useRef()
    const codeRef = ref ?? defaultRef

    const { supportsProps } = selectProps(rest)
    const strValidation = supportsProps.validation
    const validatorsLength = 6
    const prefix = 'code'
    const sufixValidation = 'Validation'

    const [codes, setCodes] = React.useState(() => Array(validatorsLength).fill(''))

    const [isHover, setIsHover] = React.useState(false)
    const handleMouseOver = () => {
      setIsHover(true)
    }

    const handleMouseOut = () => {
      setIsHover(false)
    }

    const themeTokens = useThemeTokens('TextInput', tokens, variant, { hover: isHover })

    // Create refs for input elements
    const codeReferences = React.useMemo(() => {
      return Array.from({ length: validatorsLength }, () => React.createRef())
    }, [validatorsLength])

    // Keep onChange and mask in refs to avoid re-creating event listeners
    const onChangeRef = React.useRef(onChange)
    const maskRef = React.useRef(mask)
    React.useEffect(() => {
      onChangeRef.current = onChange
      maskRef.current = mask
    }, [onChange, mask])

    // Update a single code digit
    const updateCode = React.useCallback(
      (index, digit) => {
        setCodes((prevCodes) => {
          const newCodes = [...prevCodes]
          newCodes[index] = digit

          if (onChangeRef.current) {
            const codeString = newCodes.join('')
            const singleCodesObj = {}
            newCodes.forEach((code, i) => {
              singleCodesObj[prefix + i] = code
              singleCodesObj[prefix + i + sufixValidation] = code ? 'success' : ''
            })
            onChangeRef.current(codeString, singleCodesObj)
          }

          return newCodes
        })
      },
      [prefix, sufixValidation]
    )

    // Handle input change
    const handleInputChange = React.useCallback(
      (index, event) => {
        const val = event.nativeEvent?.value || event.target?.value

        // This prevents the infinite loop where setting element.value triggers another input event
        if (maskRef.current && val === maskRef.current.substring(0, 1)) {
          return
        }

        const numericOnly = val.replace(/\D/g, '').substring(0, 1)

        // Update state
        updateCode(index, numericOnly)

        // Update DOM element
        const element = codeReferences[index]?.current
        if (element) {
          if (maskRef.current && numericOnly) {
            element.value = maskRef.current.substring(0, 1)
          } else {
            element.value = numericOnly
          }
        }

        // Move to next field if digit entered
        if (numericOnly && index < validatorsLength - 1) {
          const nextElement = codeReferences[index + 1]?.current
          nextElement?.focus()
        } else if (index === validatorsLength - 1) {
          element?.blur()
        }
      },
      [codeReferences, updateCode, validatorsLength]
    )

    // Handle backspace
    const handleKeyPress = React.useCallback(
      (index, event) => {
        if (!(event.keyCode === 8 || event.code === 'Backspace')) {
          return
        }

        const currentElement = codeReferences[index]?.current

        if (currentElement) {
          currentElement.value = ''
        }
        updateCode(index, '')

        if (index > 0) {
          const previousElement = codeReferences[index - 1]?.current
          previousElement?.focus()
        }
      },
      [codeReferences, updateCode]
    )

    const getCodeComponents = () => {
      return Array.from({ length: validatorsLength }, (_, i) => {
        const codeId = prefix + i
        const codeInputProps = {
          nativeID: codeId,
          keyboardType: 'numeric',
          ref: codeReferences[i] ?? null,
          validation: strValidation || (codes[i] ? 'success' : ''),
          tokens: selectCodeTextInputTokens(themeTokens),
          // Only use secureTextEntry in React Native, web handles mask differently
          secureTextEntry: !!(Platform.OS !== 'web' && mask),
          selectTextOnFocus: Platform.OS !== 'web',
          onFocus: () => {
            const element = codeReferences[i]?.current
            if (Platform.OS === 'web' && element?.select) {
              return element.select() ?? null
            }
            if (element?.focus) {
              return element.focus() ?? null
            }
            return null
          },
          onKeyPress: (event) => handleKeyPress(i, event),
          onMouseOver: handleMouseOver,
          onMouseOut: handleMouseOut,
          inactive
        }

        // For React Native, use onChangeText and maxLength
        if (Platform.OS !== 'web') {
          codeInputProps.maxLength = 1
          codeInputProps.value = codes[i]
          codeInputProps.onChange = () => {}

          codeInputProps.onChangeText = (text) => {
            if (text) {
              updateCode(i, text)

              if (i < validatorsLength - 1) {
                setTimeout(() => {
                  codeReferences[i + 1]?.current?.focus()
                }, 50)
              }
            } else {
              updateCode(i, '')

              if (i > 0) {
                setTimeout(() => {
                  codeReferences[i - 1]?.current?.focus()
                }, 50)
              }
            }
          }
        }

        if (!codeInputProps.validation) {
          delete codeInputProps.validation
        }

        return (
          <View key={codeId} style={staticStyles.codeInputWidth}>
            <TextInput {...codeInputProps} />
          </View>
        )
      })
    }

    // Sync external value prop to internal state
    React.useEffect(() => {
      if (value === '') {
        setCodes(Array(validatorsLength).fill(''))
        return
      }
      if (Number(value).toString() !== 'NaN') {
        const digits = value.split('').slice(0, validatorsLength)
        const newCodes = Array(validatorsLength).fill('')
        digits.forEach((digit, i) => {
          if (/\d/.test(digit)) {
            newCodes[i] = digit
          }
        })
        setCodes(newCodes)
      }
    }, [value, validatorsLength])

    // Sync codes state to DOM elements
    React.useEffect(() => {
      codes.forEach((code, i) => {
        const element = codeReferences[i]?.current
        if (element && element.value !== undefined) {
          if (mask && code) {
            element.value = mask.substring(0, 1)
          } else {
            element.value = code
          }
        }
      })
    }, [codes, codeReferences, mask])

    // Setup event listeners - only runs once on mount
    React.useEffect(() => {
      if (Platform.OS !== 'web') {
        return undefined
      }

      const handlePaste = (event) => {
        event.preventDefault()
        const clipBoardText = event.clipboardData.getData('text')
        const numericOnly = clipBoardText.replace(/\D/g, '').substring(0, validatorsLength)

        const newCodes = Array(validatorsLength).fill('')
        numericOnly.split('').forEach((digit, i) => {
          newCodes[i] = digit
        })
        setCodes(newCodes)

        if (onChangeRef.current) {
          const singleCodesObj = {}
          newCodes.forEach((code, i) => {
            singleCodesObj[prefix + i] = code
            singleCodesObj[prefix + i + sufixValidation] = code ? 'success' : ''
          })
          onChangeRef.current(numericOnly, singleCodesObj)
        }
      }

      const handleCopy = (event) => {
        setCodes((currentCodes) => {
          const clipBoardText = currentCodes.join('')
          event.clipboardData.setData('text/plain', clipBoardText)
          event.preventDefault()
          return currentCodes
        })
      }

      // Add event listeners to each input
      codeReferences.forEach((inputRef, i) => {
        const element = inputRef?.current
        if (element && typeof element.addEventListener === 'function') {
          element.addEventListener('paste', handlePaste)
          element.addEventListener('copy', handleCopy)
          element.addEventListener('input', (event) => handleInputChange(i, event))
        }
      })

      // Cleanup
      return () => {
        codeReferences.forEach((inputRef) => {
          const element = inputRef?.current
          if (element && typeof element.removeEventListener === 'function') {
            element.removeEventListener('paste', handlePaste)
            element.removeEventListener('copy', handleCopy)
            element.removeEventListener('input', (event) =>
              handleInputChange(event.target.dataset.index, event)
            )
          }
        })
      }
      /*
       * codeReferences and handleInputChange are intentionally omitted from dependencies
       * because we want event listeners to be registered ONLY ONCE when the component mounts.
       * codeReferences is stable (created with useMemo) and handleInputChange is stable (useCallback).
       * Including them would cause unnecessary re-registration of listeners on each render.
       */
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])

    return (
      <InputSupports
        {...supportsProps}
        feedbackProps={{
          ...rest?.feedbackProps,
          showFeedbackIcon: true
        }}
      >
        <StackView space={2} direction="row" ref={codeRef}>
          {getCodeComponents()}
        </StackView>
      </InputSupports>
    )
  }
)
Validator.displayName = 'Validator'

Validator.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * The value is a 6-digit code, may be only numeric characters, non numeric character aren't renderize
   */
  value: PropTypes.string,
  /**
   * If true, the component is inactive and non editable.
   */
  inactive: PropTypes.bool,
  /**
   * Show masked characters and obscure the actual input. For example: '*'
   */
  mask: PropTypes.string,
  /**
   * Use to react upon input's value changes. Required when the `value` prop is set. Will receive the input's value as an argument.
   */
  onChange: PropTypes.func
}

const staticStyles = StyleSheet.create({
  codeInputWidth: {
    width: 43
  }
})
