/**
 * ✔ value
 * - type: Partially. Not support `safe-password`、`nickname`
 * ✔ password
 * ✔ placeholder
 * - placeholder-style: Only support color.
 * - placeholder-class: Only support color.
 * ✔ disabled
 * ✔ maxlength
 * ✔ cursor-spacing
 * ✔ auto-focus
 * ✔ focus
 * ✔ confirm-type
 * ✘ always-embed
 * ✔ confirm-hold
 * ✔ cursor
 * ✔ cursor-color
 * ✔ selection-start
 * ✔ selection-end
 * ✔ adjust-position
 * ✔ hold-keyboard
 * ✘ safe-password-cert-path
 * ✘ safe-password-length
 * ✘ safe-password-time-stamp
 * ✘ safe-password-nonce
 * ✘ safe-password-salt
 * ✘ safe-password-custom-hash
 * - bindinput: No `keyCode` info.
 * - bindfocus: No `height` info.
 * - bindblur: No `encryptedValue`、`encryptError` info.
 * ✔ bindconfirm
 * ✘ bindkeyboardheightchange
 * ✘ bindnicknamereview
 * ✔ bind:selectionchange
 * ✘ bind:keyboardcompositionstart
 * ✘ bind:keyboardcompositionupdate
 * ✘ bind:keyboardcompositionend
 * ✘ bind:onkeyboardheightchange
 */
import { forwardRef, useRef, useState, useContext, useEffect, createElement } from 'react';
import { TextInput } from 'react-native';
import { warn } from '@mpxjs/utils';
import { useUpdateEffect, useTransformStyle, useLayout, extendObject, isAndroid } from './utils';
import useInnerProps, { getCustomEvent } from './getInnerListeners';
import useNodesRef from './useNodesRef';
import { FormContext, KeyboardAvoidContext } from './context';
import Portal from './mpx-portal';
const inputModeMap = {
    text: 'text',
    number: 'numeric',
    idcard: 'text',
    digit: 'decimal'
};
const Input = forwardRef((props, ref) => {
    const { style = {}, allowFontScaling = false, type = 'text', value, password, 'placeholder-style': placeholderStyle = {}, disabled, maxlength = 140, 'cursor-spacing': cursorSpacing = 0, 'auto-focus': autoFocus, focus, 'confirm-type': confirmType = 'done', 'confirm-hold': confirmHold = false, cursor, 'cursor-color': cursorColor, 'selection-start': selectionStart = -1, 'selection-end': selectionEnd = -1, 'enable-var': enableVar, 'external-var-context': externalVarContext, 'parent-font-size': parentFontSize, 'parent-width': parentWidth, 'parent-height': parentHeight, 'adjust-position': adjustPosition = true, 'keyboard-type': originalKeyboardType, 'hold-keyboard': holdKeyboard = false, bindinput, bindfocus, bindblur, bindconfirm, bindselectionchange, 
    // private
    multiline, 'auto-height': autoHeight, bindlinechange } = props;
    const formContext = useContext(FormContext);
    const keyboardAvoid = useContext(KeyboardAvoidContext);
    let formValuesMap;
    if (formContext) {
        formValuesMap = formContext.formValuesMap;
    }
    const parseValue = (value) => {
        if (typeof value === 'string') {
            if (value.length > maxlength && maxlength >= 0) {
                return value.slice(0, maxlength);
            }
            return value;
        }
        if (typeof value === 'number')
            return value + '';
        return '';
    };
    const defaultValue = parseValue(value);
    // 微信小程序的 input 永远是单行，textAlignVertical 固定为 auto
    // multiline 为 true 时表示是 textarea 组件复用此逻辑
    const textAlignVertical = multiline ? 'top' : 'auto';
    const isAutoFocus = !!autoFocus || !!focus;
    const tmpValue = useRef(defaultValue);
    const cursorIndex = useRef(0);
    const lineCount = useRef(0);
    const [inputValue, setInputValue] = useState(defaultValue);
    const [contentHeight, setContentHeight] = useState(0);
    const [selection, setSelection] = useState({ start: -1, end: tmpValue.current.length });
    const styleObj = extendObject({ padding: 0, backgroundColor: '#fff' }, style, multiline && autoHeight ? { height: 'auto' } : {});
    const { hasPositionFixed, hasSelfPercent, normalStyle, setWidth, setHeight } = useTransformStyle(styleObj, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight });
    const nodeRef = useRef(null);
    useNodesRef(props, ref, nodeRef, {
        style: normalStyle
    });
    const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef });
    useEffect(() => {
        if (value !== tmpValue.current) {
            const parsed = parseValue(value);
            tmpValue.current = parsed;
            setInputValue(parsed);
        }
    }, [value]);
    useEffect(() => {
        if (selectionStart > -1) {
            setSelection({ start: selectionStart, end: selectionEnd === -1 ? tmpValue.current.length : selectionEnd });
        }
        else if (typeof cursor === 'number') {
            setSelection({ start: cursor, end: cursor });
        }
    }, [cursor, selectionStart, selectionEnd]);
    // have not selection on the Android platformg
    const getCursorIndex = (changedSelection, prevValue, curValue) => {
        if (changedSelection)
            return changedSelection.end;
        if (!prevValue || !curValue || prevValue.length === curValue.length)
            return curValue.length;
        const prevStr = prevValue.substring(cursorIndex.current);
        const curStr = curValue.substring(cursorIndex.current);
        return cursorIndex.current + curStr.length - prevStr.length;
    };
    const onChange = (evt) => {
        const { text, selection } = evt.nativeEvent;
        // will trigger twice on the Android platformg, prevent the second trigger
        if (tmpValue.current === text)
            return;
        const index = getCursorIndex(selection, tmpValue.current, text);
        tmpValue.current = text;
        cursorIndex.current = index;
        if (bindinput) {
            const result = bindinput(getCustomEvent('input', evt, {
                detail: {
                    value: tmpValue.current,
                    cursor: cursorIndex.current
                },
                layoutRef
            }, props));
            if (typeof result === 'string') {
                tmpValue.current = result;
                setInputValue(result);
            }
            else {
                setInputValue(tmpValue.current);
            }
        }
        else {
            setInputValue(tmpValue.current);
        }
    };
    const setKeyboardAvoidContext = () => {
        if (keyboardAvoid) {
            keyboardAvoid.current = { cursorSpacing, ref: nodeRef, adjustPosition, holdKeyboard, readyToShow: true };
        }
    };
    const onTouchStart = () => {
        // 手动聚焦时初始化 keyboardAvoid 上下文
        // auto-focus/focus 不会触发而是在 useEffect 中初始化
        setKeyboardAvoidContext();
    };
    const onTouchEnd = (evt) => {
        evt.nativeEvent.origin = 'input';
    };
    const onFocus = (evt) => {
        if (!keyboardAvoid?.current) {
            // Android：从一个正聚焦状态 input，聚焦到另一个新的 input 时，正常会触发如下时序：
            // 新的 Input `onTouchStart` -> 旧输入框键盘 `keyboardDidHide` -> 新的 Input `onFocus`
            // 导致这里的 keyboardAvoid.current 为 null，所以需要判空重新初始化。
            setKeyboardAvoidContext();
        }
        const focusAction = () => {
            bindfocus?.(getCustomEvent('focus', evt, {
                detail: {
                    value: tmpValue.current || '',
                    height: keyboardAvoid?.current?.keyboardHeight
                },
                layoutRef
            }, props));
            if (keyboardAvoid?.current?.onKeyboardShow) {
                keyboardAvoid.current.onKeyboardShow = undefined;
            }
        };
        if (keyboardAvoid?.current) {
            // 有 keyboardAvoiding
            if (keyboardAvoid.current.keyboardHeight) {
                // 仅以下场景触发顺序：先 keyboardWillShow 获取高度 -> 后 onFocus，可以立即执行
                // - iOS + 手动点击聚焦
                focusAction();
            }
            else {
                // 其他场景触发顺序：先 onFocus -> 后 keyboardWillShow 获取高度 -> 执行回调
                // - iOS + auto-focus/focus=true 自动聚焦
                // - Android 手动点击聚焦/自动聚焦 都一样
                evt.persist();
                keyboardAvoid.current.onKeyboardShow = focusAction;
            }
        }
        else {
            // 兜底：无 keyboardAvoiding 直接执行 focus 回调
            focusAction();
        }
    };
    const onBlur = (evt) => {
        bindblur && bindblur(getCustomEvent('blur', evt, {
            detail: {
                value: tmpValue.current || '',
                cursor: cursorIndex.current
            },
            layoutRef
        }, props));
    };
    const onSubmitEditing = (evt) => {
        bindconfirm(getCustomEvent('confirm', evt, {
            detail: {
                value: tmpValue.current || ''
            },
            layoutRef
        }, props));
    };
    const onSelectionChange = (evt) => {
        const { selection } = evt.nativeEvent;
        const { start, end } = selection;
        cursorIndex.current = start;
        setSelection(selection);
        bindselectionchange && bindselectionchange(getCustomEvent('selectionchange', evt, {
            detail: {
                selectionStart: start,
                selectionEnd: end
            },
            layoutRef
        }, props));
    };
    const onContentSizeChange = (evt) => {
        const { width, height } = evt.nativeEvent.contentSize;
        if (width && height) {
            if (!multiline || !autoHeight || height === contentHeight)
                return;
            lineCount.current += height > contentHeight ? 1 : -1;
            const lineHeight = lineCount.current === 0 ? 0 : height / lineCount.current;
            bindlinechange &&
                bindlinechange(getCustomEvent('linechange', evt, {
                    detail: {
                        height,
                        lineHeight,
                        lineCount: lineCount.current
                    },
                    layoutRef
                }, props));
            setContentHeight(height);
        }
    };
    const resetValue = () => {
        tmpValue.current = '';
        setInputValue('');
    };
    const getValue = () => {
        return inputValue;
    };
    if (formValuesMap) {
        if (!props.name) {
            warn('If a form component is used, the name attribute is required.');
        }
        else {
            formValuesMap.set(props.name, { getValue, resetValue });
        }
    }
    useEffect(() => {
        return () => {
            if (formValuesMap && props.name) {
                formValuesMap.delete(props.name);
            }
        };
    }, []);
    useEffect(() => {
        if (isAutoFocus) {
            // auto-focus/focus=true 初始化 keyboardAvoidContext
            setKeyboardAvoidContext();
        }
    }, [isAutoFocus]);
    useUpdateEffect(() => {
        if (!nodeRef?.current) {
            return;
        }
        // RN autoFocus 属性仅在初次渲染时生效
        // 后续更新需要手动调用 focus/blur 方法，和微信小程序对齐
        isAutoFocus
            ? nodeRef.current?.focus()
            : nodeRef.current?.blur();
    }, [isAutoFocus]);
    // 使用 multiline 来修复光标位置问题
    // React Native 的 TextInput 在 textAlign center + placeholder 时光标会跑到右边
    // 这个问题只在 Android 上出现
    // 参考：https://github.com/facebook/react-native/issues/28794 (Android only)
    const needMultilineFix = isAndroid && !multiline;
    const innerProps = useInnerProps(extendObject({}, props, layoutProps, {
        ref: nodeRef,
        style: extendObject({}, normalStyle, layoutStyle),
        allowFontScaling,
        inputMode: originalKeyboardType ? undefined : inputModeMap[type],
        keyboardType: originalKeyboardType,
        secureTextEntry: !!password,
        defaultValue: defaultValue,
        value: inputValue,
        maxLength: maxlength === -1 ? undefined : maxlength,
        editable: !disabled,
        autoFocus: isAutoFocus,
        selection: selectionStart > -1 || typeof cursor === 'number' ? selection : undefined,
        selectionColor: cursorColor,
        blurOnSubmit: multiline ? confirmType !== 'return' : !confirmHold,
        underlineColorAndroid: 'rgba(0,0,0,0)',
        textAlignVertical: textAlignVertical,
        placeholderTextColor: placeholderStyle?.color,
        multiline: multiline || needMultilineFix,
        onTouchStart,
        onTouchEnd,
        onFocus,
        onBlur,
        onChange,
        onSelectionChange,
        onContentSizeChange,
        onSubmitEditing: bindconfirm && onSubmitEditing
    }, needMultilineFix ? { numberOfLines: 1 } : {}, !!multiline && confirmType === 'return' ? {} : { enterKeyHint: confirmType }), [
        'type',
        'password',
        'placeholder-style',
        'disabled',
        'auto-focus',
        'focus',
        'confirm-type',
        'confirm-hold',
        'cursor',
        'cursor-color',
        'selection-start',
        'selection-end'
    ], {
        layoutRef
    });
    const finalComponent = createElement(TextInput, innerProps);
    if (hasPositionFixed) {
        return createElement(Portal, null, finalComponent);
    }
    return finalComponent;
});
Input.displayName = 'MpxInput';
export default Input;
