/**
 * Copyright (c) Nicolas Gallagher.
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import type { PlatformMethods } from '../../types';
import type { TextInputProps } from './types';
import * as React from 'react';
import createElement from '../createElement';
import * as forwardedProps from '../../modules/forwardedProps';
import pick from '../../modules/pick';
import useElementLayout from '../../modules/useElementLayout';
import useLayoutEffect from '../../modules/useLayoutEffect';
import useMergeRefs from '../../modules/useMergeRefs';
import usePlatformMethods from '../../modules/usePlatformMethods';
import useResponderEvents from '../../modules/useResponderEvents';
import { getLocaleDirection, useLocaleContext } from '../../modules/useLocale';
import StyleSheet from '../StyleSheet';
import TextInputState from '../../modules/TextInputState';
import { warnOnce } from '../../modules/warnOnce';

/**
 * Determines whether a 'selection' prop differs from a node's existing
 * selection state.
 */
declare var isSelectionStale: (node: any, selection: any) => any;
/**
 * Certain input types do no support 'selectSelectionRange' and will throw an
 * error.
 */
declare var setSelection: (node: any, selection: any) => any;
const forwardPropsList = Object.assign({}, forwardedProps.defaultProps, forwardedProps.accessibilityProps, forwardedProps.clickProps, forwardedProps.focusProps, forwardedProps.keyboardProps, forwardedProps.mouseProps, forwardedProps.touchProps, forwardedProps.styleProps, {
  autoCapitalize: true,
  autoComplete: true,
  autoCorrect: true,
  autoFocus: true,
  defaultValue: true,
  disabled: true,
  lang: true,
  maxLength: true,
  onChange: true,
  onScroll: true,
  placeholder: true,
  pointerEvents: true,
  readOnly: true,
  rows: true,
  spellCheck: true,
  value: true,
  type: true
});
declare var pickProps: (props: any) => any; // If an Input Method Editor is processing key input, the 'keyCode' is 229.
// https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode
declare function isEventComposing(nativeEvent: any): any;
let focusTimeout: ?TimeoutID = null;
const TextInput: React.AbstractComponent<TextInputProps, HTMLElement & PlatformMethods> = React.forwardRef((props, forwardedRef) => {
  const {
    autoCapitalize = 'sentences',
    autoComplete,
    autoCompleteType,
    autoCorrect = true,
    blurOnSubmit,
    clearTextOnFocus,
    dir,
    editable = true,
    enterKeyHint,
    inputMode,
    keyboardType = 'default',
    multiline = false,
    numberOfLines = 1,
    onBlur,
    onChange,
    onChangeText,
    onContentSizeChange,
    onFocus,
    onKeyPress,
    onLayout,
    onMoveShouldSetResponder,
    onMoveShouldSetResponderCapture,
    onResponderEnd,
    onResponderGrant,
    onResponderMove,
    onResponderReject,
    onResponderRelease,
    onResponderStart,
    onResponderTerminate,
    onResponderTerminationRequest,
    onScrollShouldSetResponder,
    onScrollShouldSetResponderCapture,
    onSelectionChange,
    onSelectionChangeShouldSetResponder,
    onSelectionChangeShouldSetResponderCapture,
    onStartShouldSetResponder,
    onStartShouldSetResponderCapture,
    onSubmitEditing,
    placeholderTextColor,
    readOnly,
    returnKeyType,
    rows,
    secureTextEntry = false,
    selection,
    selectTextOnFocus,
    spellCheck
  } = props;
  let type;
  let _inputMode;
  if (inputMode != null) {
    _inputMode = inputMode;
    if (inputMode === 'email') {
      type = 'email';
    } else if (inputMode === 'tel') {
      type = 'tel';
    } else if (inputMode === 'search') {
      type = 'search';
    } else if (inputMode === 'url') {
      type = 'url';
    } else {
      type = 'text';
    }
  } else if (keyboardType != null) {
    warnOnce('keyboardType', 'keyboardType is deprecated. Use inputMode.');
    switch (keyboardType) {
      case 'email-address':
        type = 'email';
        break;
      case 'number-pad':
      case 'numeric':
        _inputMode = 'numeric';
        break;
      case 'decimal-pad':
        _inputMode = 'decimal';
        break;
      case 'phone-pad':
        type = 'tel';
        break;
      case 'search':
      case 'web-search':
        type = 'search';
        break;
      case 'url':
        type = 'url';
        break;
      default:
        type = 'text';
    }
  }
  if (secureTextEntry) {
    type = 'password';
  }
  const dimensions = React.useRef({
    height: null,
    width: null
  });
  const hostRef = React.useRef(null);
  const handleContentSizeChange = React.useCallback(hostNode => {
    if (multiline && onContentSizeChange && hostNode != null) {
      const newHeight = hostNode.scrollHeight;
      const newWidth = hostNode.scrollWidth;
      if (newHeight !== dimensions.current.height || newWidth !== dimensions.current.width) {
        dimensions.current.height = newHeight;
        dimensions.current.width = newWidth;
        onContentSizeChange({
          nativeEvent: {
            contentSize: {
              height: dimensions.current.height,
              width: dimensions.current.width
            }
          }
        });
      }
    }
  }, [multiline, onContentSizeChange]);
  const imperativeRef = React.useMemo(() => hostNode => {
    // TextInput needs to add more methods to the hostNode in addition to those
    // added by `usePlatformMethods`. This is temporarily until an API like
    // `TextInput.clear(hostRef)` is added to React Native.
    if (hostNode != null) {
      hostNode.clear = function () {
        if (hostNode != null) {
          hostNode.value = '';
        }
      };
      hostNode.isFocused = function () {
        return hostNode != null && TextInputState.currentlyFocusedField() === hostNode;
      };
      handleContentSizeChange(hostNode);
    }
  }, [handleContentSizeChange]);
  declare function handleBlur(e: any): any;
  declare function handleChange(e: any): any;
  declare function handleFocus(e: any): any;
  declare function handleKeyDown(e: any): any;
  declare function handleSelectionChange(e: any): any;
  useLayoutEffect(() => {
    const node = hostRef.current;
    if (node != null && selection != null) {
      setSelection(node, selection);
    }
    if (document.activeElement === node) {
      TextInputState._currentlyFocusedNode = node;
    }
  }, [hostRef, selection]);
  const component = multiline ? 'textarea' : 'input';
  useElementLayout(hostRef, onLayout);
  useResponderEvents(hostRef, {
    onMoveShouldSetResponder,
    onMoveShouldSetResponderCapture,
    onResponderEnd,
    onResponderGrant,
    onResponderMove,
    onResponderReject,
    onResponderRelease,
    onResponderStart,
    onResponderTerminate,
    onResponderTerminationRequest,
    onScrollShouldSetResponder,
    onScrollShouldSetResponderCapture,
    onSelectionChangeShouldSetResponder,
    onSelectionChangeShouldSetResponderCapture,
    onStartShouldSetResponder,
    onStartShouldSetResponderCapture
  });
  const {
    direction: contextDirection
  } = useLocaleContext();
  const supportedProps = pickProps(props);
  supportedProps.autoCapitalize = autoCapitalize;
  supportedProps.autoComplete = autoComplete || autoCompleteType || 'on';
  supportedProps.autoCorrect = autoCorrect ? 'on' : 'off';
  // 'auto' by default allows browsers to infer writing direction
  supportedProps.dir = dir !== undefined ? dir : 'auto';
  if (returnKeyType != null) {
    warnOnce('returnKeyType', 'returnKeyType is deprecated. Use enterKeyHint.');
  }
  supportedProps.enterKeyHint = enterKeyHint || returnKeyType;
  supportedProps.inputMode = _inputMode;
  supportedProps.onBlur = handleBlur;
  supportedProps.onChange = handleChange;
  supportedProps.onFocus = handleFocus;
  supportedProps.onKeyDown = handleKeyDown;
  supportedProps.onSelect = handleSelectionChange;
  if (editable != null) {
    warnOnce('editable', 'editable is deprecated. Use readOnly.');
  }
  supportedProps.readOnly = readOnly || !editable;
  if (numberOfLines != null) {
    warnOnce('numberOfLines', 'TextInput numberOfLines is deprecated. Use rows.');
  }
  supportedProps.rows = multiline ? rows != null ? rows : numberOfLines : undefined;
  supportedProps.spellCheck = spellCheck != null ? spellCheck : autoCorrect;
  supportedProps.style = [{
    '--placeholderTextColor': placeholderTextColor
  }, styles.textinput$raw, styles.placeholder, props.style];
  supportedProps.type = multiline ? undefined : type;
  const platformMethodsRef = usePlatformMethods(supportedProps);
  const setRef = useMergeRefs(hostRef, platformMethodsRef, imperativeRef, forwardedRef);
  supportedProps.ref = setRef;
  const langDirection = props.lang != null ? getLocaleDirection(props.lang) : null;
  const componentDirection = props.dir || langDirection;
  const writingDirection = componentDirection || contextDirection;
  const element = createElement(component, supportedProps, {
    writingDirection
  });
  return element;
});
TextInput.displayName = 'TextInput';
// $FlowFixMe
TextInput.State = TextInputState;
const styles = StyleSheet.create({
  textinput$raw: {
    MozAppearance: 'textfield',
    WebkitAppearance: 'none',
    backgroundColor: 'transparent',
    border: '0 solid black',
    borderRadius: 0,
    boxSizing: 'border-box',
    font: '14px System',
    margin: 0,
    padding: 0,
    resize: 'none'
  },
  placeholder: {
    placeholderTextColor: 'var(--placeholderTextColor)'
  }
});
export default TextInput;