import React, { forwardRef, useImperativeHandle } from 'react'
import { TypeGuards } from '@codeleap/types'
import { TextInput as NativeTextInput } from 'react-native'
import { InputBase, selectInputBaseProps } from '../InputBase'
import { Touchable } from '../Touchable'
import { MaskedTextInput } from '../../modules/textInputMask'
import { AnyRecord, AppIcon, IJSX, StyledComponentProps, StyledComponentWithProps } from '@codeleap/styles'
import { TextInputProps } from './types'
import { MobileStyleRegistry } from '../../Registry'
import { useStylesFor } from '../../hooks'
import { useTextInput } from './useTextInput'
import { useInputBasePartialStyles } from '../InputBase/useInputBasePartialStyles'

export * from './styles'
export * from './types'

export const TextInput = forwardRef<NativeTextInput, TextInputProps>((props, ref) => {
  const allProps = {
    ...TextInput.defaultProps,
    ...props,
  }

  const {
    inputBaseProps,
    others,
  } = selectInputBaseProps(allProps)

  const {
    debugName,
    visibilityToggle,
    masking,
    secure,
    field,
    onChangeMask,
    onPress,
    visibleIcon,
    hiddenIcon,
    style,
    autoAdjustSelection,
    selectionStart,
    forceError,
    multiline,
    onLayout,
    ...textInputProps
  } = others

  const styles = useStylesFor(TextInput.styleRegistryName, style)

  const {
    validation,
    inputValue,
    onInputValueChange,
    innerInputRef,
    wrapperRef,
    isFocused, 
    secureTextEntry,
    currentSelection,
    hasMultipleLines,
    hasValue,
    hasError,
    toggleSecureTextEntry,
    handleMaskChange,
    handleBlur,
    handleFocus,
  } = useTextInput(allProps)

  useImperativeHandle(ref, () => {
    if (!innerInputRef.current) return null

    return {
      ...innerInputRef.current,
    } as NativeTextInput
  })
  
  /** Swaps the underlying element to the masked variant; the ref forwarding also changes — `refInput` is used instead of `ref` because MaskedTextInput does not forward refs the standard way. */
  const InputElement = masking ? MaskedTextInput : NativeTextInput

  const isPressable = TypeGuards.isFunction(onPress)

  const isDisabled = !!inputBaseProps.disabled

  /** `placeholderTextColor` and `selectionColor` are scalar RN props that cannot accept style arrays, so their colors are extracted as plain values here rather than in the style array passed to the element. */
  const partialStyles = useInputBasePartialStyles(styles, ['placeholder', 'selection'], {
    disabled: isDisabled,
    error: !!hasError,
    focus: isFocused || textInputProps?.focused,
  })

  const visibilityToggleProps = visibilityToggle ? {
    onPress: toggleSecureTextEntry,
    icon: (secureTextEntry ? hiddenIcon : visibleIcon) as AppIcon,
    debugName: `${debugName} toggle visibility`,
  } : null

  const rightIcon = inputBaseProps?.rightIcon ?? visibilityToggleProps

  const maskingExtraProps = masking ? {
    onChangeText: handleMaskChange,
    ref: null,
    refInput: (inputRef) => {
      if (!!inputRef) innerInputRef.current = inputRef
    },
    ...masking,
  } : {
    onChangeText: onInputValueChange,
  }

  /** When `onPress` is supplied the input acts as a button: editing and the caret are disabled so the native keyboard never opens, and `pointerEvents="none"` prevents touch from reaching the input itself. */
  const buttonModeProps = isPressable ? {
    editable: false,
    caretHidden: true,
  } : {}

  return <InputBase
    {...inputBaseProps}
    onLayout={onLayout}
    ref={wrapperRef}
    innerWrapper={isPressable ? Touchable : undefined}
    debugName={debugName}
    error={hasError ? validation?.message || forceError : null}
    style={{
      ...styles,
      innerWrapper: [
        styles?.innerWrapper,
        multiline && styles['innerWrapper:multiline'],
        hasMultipleLines && styles['innerWrapper:hasMultipleLines'],
      ],
    }}
    innerWrapperProps={{
      ...(inputBaseProps.innerWrapperProps || {}),
      onPress,
      debugName,
      dismissKeyboard: false,
    }}
    rightIcon={rightIcon}
    focused={isFocused || textInputProps?.focused}
    hasValue={hasValue}
  >
    <InputElement
      allowFontScaling={false}
      editable={!isPressable && !isDisabled}
      {...buttonModeProps}
      selection={autoAdjustSelection ? currentSelection : undefined}
      placeholderTextColor={partialStyles?.placeholder?.color}
      selectionColor={partialStyles?.selection?.color}
      secureTextEntry={secure && secureTextEntry}
      textAlignVertical={multiline ? 'top' : undefined}
      multiline={multiline}
      {...textInputProps}
      value={inputValue}
      onChangeText={onInputValueChange}
      onBlur={handleBlur}
      onFocus={handleFocus}
      style={[
        styles?.input,
        multiline && styles['input:multiline'],
        (isFocused || textInputProps?.focused) && styles['input:focused'],
        hasError && styles['input:error'],
        isDisabled && styles['input:disabled'],
        hasMultipleLines && styles['input:hasMultipleLines'],
        hasValue && styles['input:typed'],
      ]}
      ref={innerInputRef}
      pointerEvents={isPressable ? 'none' : undefined}
      {...maskingExtraProps}
    />
  </InputBase>
}) as StyledComponentWithProps<TextInputProps>

TextInput.styleRegistryName = 'TextInput'
TextInput.elements = [...InputBase.elements, 'input', 'placeholder', 'selection']
TextInput.rootElement = 'wrapper'

TextInput.withVariantTypes = <S extends AnyRecord>(styles: S) => {
  return TextInput as (props: StyledComponentProps<TextInputProps, typeof styles>) => IJSX
}

TextInput.defaultProps = {
  hiddenIcon: 'input-visiblity:hidden' as AppIcon,
  visibleIcon: 'input-visiblity:visible' as AppIcon,
  visibilityToggle: false,
  autoAdjustSelection: false,
  selectionStart: 0,
  secure: false,
} as Partial<TextInputProps>

MobileStyleRegistry.registerComponent(TextInput)
