import React from 'react';
import { Editor, EditorState, SelectionState } from 'draft-js';
import EditorUtilities from './EditorUtilities';
import { sizeTypes, inputToTagsSize, dataHooks } from './constants';
import { st, classes, vars } from './VariableInput.st.css.js';
import StatusIndicator from '../StatusIndicator';
import { StatusContext, getStatusFromContext, } from '../FormField/StatusContext';
/**
 * Error Boundary to catch Draft.js crashes on mobile devices with IME/autocorrect
 *
 * Known Issue: Draft.js has a fundamental incompatibility with Android's Input Method Editor (IME).
 * When Android autocorrect modifies the DOM directly (e.g., on SPACE key press), Draft.js attempts
 * to reconcile its virtual state with DOM nodes that have already been removed/modified by the IME,
 * resulting in "Failed to execute 'removeChild' on 'Node'" errors.
 *
 * This Error Boundary prevents the entire component from crashing and allows it to auto-recover.
 * While console warnings will still appear, the user experience remains functional.
 *
 * TODO: After Draft-JS migration, make sure this error is no longer thrown and then remove this error boundary.
 */
class EditorErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false, errorCount: 0 };
    }
    static getDerivedStateFromError() {
        return { hasError: true };
    }
    componentDidCatch(error, errorInfo) {
        console.warn('VariableInput: Draft.js error caught (likely mobile IME conflict):', error.message);
    }
    componentDidUpdate(prevProps, prevState) {
        // Auto-recover with max retry limit to prevent infinite loops
        if (this.state.hasError && this.state.errorCount < 5) {
            this.setState({ hasError: false, errorCount: this.state.errorCount + 1 });
        }
    }
    render() {
        return this.props.children;
    }
}
/** Input with variables as tags */
class VariableInput extends React.PureComponent {
    constructor(props) {
        super(props);
        this._handleCompositionStart = () => {
            this.isComposing = true;
        };
        this._handleCompositionUpdate = e => {
            // During IME composition, manually trigger onChange with current DOM text
            // This fixes Android autosuggestion issue where draft-js doesn't fire onChange
            if (this.props.onChange && e.target) {
                const text = e.target.textContent || '';
                this.props.onChange(text);
            }
        };
        this._handleCompositionEnd = () => {
            this.isComposing = false;
        };
        this._handleInput = e => {
            if (this.isComposing && this.props.onChange && e.target) {
                const text = e.target.textContent || '';
                this.props.onChange(text);
            }
        };
        this._handlePastedText = (text, html, editorState) => {
            /** We need to prevent new line when `multilne` is false,
             * here we are removing any new lines while pasting text   */
            if (/\r|\n/.exec(text)) {
                text = text.replace(/(\r\n|\n|\r)/gm, '');
                this._onEditorChange(EditorUtilities.insertText(editorState, text));
                return true;
            }
            return false;
        };
        this._isEmpty = () => this.state.editorState.getCurrentContent().getPlainText().length === 0;
        this._inputToTagSize = inputSize => {
            return inputToTagsSize[inputSize] || VariableInput.defaultProps.size;
        };
        this._toString = () => {
            const { variableTemplate: { prefix, suffix }, } = this.props;
            const { editorState } = this.state;
            return EditorUtilities.convertToString({
                editorState,
                prefix,
                suffix,
            });
        };
        this._onBlur = () => {
            const { onBlur = () => { } } = this.props;
            onBlur(this._toString());
        };
        this._onMouseDown = () => {
            this._isMouseDown = true;
        };
        this._onMouseUp = () => {
            this._isMouseDown = false;
        };
        this._onFocus = () => {
            const { onFocus = () => { } } = this.props;
            onFocus(this._toString());
        };
        this._onSubmit = () => {
            const { onSubmit = () => { } } = this.props;
            onSubmit(this._toString());
        };
        this._onChange = () => {
            const { onChange = () => { } } = this.props;
            // Only call onChange if not composing (during composition, manual handlers will call it)
            if (!this.isComposing) {
                onChange(this._toString());
            }
        };
        this._onEditorChange = editorState => {
            const prevHasFocus = this.state.editorState.getSelection().getHasFocus();
            const newHasFocus = editorState.getSelection().getHasFocus();
            const becomesFocused = !prevHasFocus && newHasFocus;
            const isKeyboard = !this._isMouseDown;
            if (becomesFocused && isKeyboard) {
                const selectionAtEnd = EditorState.moveSelectionToEnd(editorState).getSelection();
                editorState = EditorState.forceSelection(editorState, selectionAtEnd);
            }
            this._setEditorState(editorState);
        };
        this._setEditorState = (editorState, onStateChanged = () => { }) => {
            const { editorState: editorStateBefore } = this.state;
            const { variableTemplate: { prefix, suffix }, } = this.props;
            let updateEditorState = EditorUtilities.moveToEdge(editorState);
            let triggerCallback = () => { };
            if (EditorUtilities.isBlured(editorStateBefore, updateEditorState)) {
                // onChange is called after the editor blur handler
                // and we can't reflect the changes there, we moved the logic here.
                triggerCallback = this._onBlur;
                if (EditorUtilities.hasUnparsedEntity(updateEditorState, prefix, suffix)) {
                    updateEditorState = this._stringToContentState(EditorUtilities.convertToString({
                        editorState: updateEditorState,
                        prefix,
                        suffix,
                    }));
                }
            }
            else if (EditorUtilities.isContentChanged(editorStateBefore, updateEditorState)) {
                triggerCallback = this._onChange;
            }
            this.setState({ editorState: updateEditorState }, () => {
                triggerCallback();
                onStateChanged();
            });
        };
        this._stringToContentState = str => {
            const { variableParser = () => { }, variableTagPropsParser, variableTemplate: { prefix, suffix }, } = this.props;
            const { editorState } = this.state;
            const content = EditorUtilities.stringToContentState({
                str,
                variableParser,
                variableTagPropsParser,
                prefix,
                suffix,
            });
            return EditorUtilities.pushAndKeepSelection({
                editorState,
                content,
            });
        };
        this._setStringValue = (str, afterUpdated = () => { }) => {
            const updatedEditorState = EditorState.moveSelectionToEnd(this._stringToContentState(str));
            this._setEditorState(updatedEditorState, () => {
                afterUpdated(updatedEditorState);
            });
        };
        /** Set value to display in the input */
        this.setValue = value => {
            this._setStringValue(value, () => {
                this._onSubmit();
            });
        };
        /** Insert variable at the input cursor position */
        this.insertVariable = value => {
            const { variableParser, variableTagPropsParser, variableTemplate: { prefix, suffix }, } = this.props;
            const { editorState } = this.state;
            const text = variableParser(value);
            const tagProps = variableTagPropsParser(value);
            const newState = text
                ? EditorUtilities.insertEntity(editorState, { text, value, tagProps })
                : EditorUtilities.insertText(editorState, `${prefix}${value}${suffix} `);
            this._setEditorState(newState, () => {
                this._onSubmit();
            });
        };
        /** Insert Text at the input cursor position */
        this.insertText = value => {
            const { editorState } = this.state;
            const newState = EditorUtilities.insertText(editorState, value);
            this._setEditorState(newState, () => {
                this._onSubmit();
            });
        };
        /**
         * Focus the input at the end of the content.
         * Optionally, if variableKey is provided, focus after that specific variable.
         */
        this.focus = ({ variableKey } = {}) => {
            const { editorState } = this.state;
            let selectionToFocus;
            if (variableKey) {
                const targetRange = EditorUtilities.findEntityRangeByKey(editorState, variableKey);
                if (targetRange) {
                    const { blockKey, end } = targetRange;
                    selectionToFocus = new SelectionState({
                        anchorKey: blockKey,
                        anchorOffset: end,
                        focusKey: blockKey,
                        focusOffset: end,
                    });
                }
            }
            if (!selectionToFocus) {
                selectionToFocus =
                    EditorState.moveSelectionToEnd(editorState).getSelection();
            }
            const newEditorState = EditorState.forceSelection(editorState, selectionToFocus.merge({ hasFocus: true }));
            this.setState({ editorState: newEditorState }, () => {
                this._onFocus();
            });
        };
        const { size, disabled } = props;
        const decorator = EditorUtilities.decoratorFactory({
            tag: { size: this._inputToTagSize(size), disabled },
        });
        this.state = {
            editorState: EditorState.createEmpty(decorator),
        };
        this.editorRef = React.createRef();
        // IME composition tracking for Android autosuggestions
        this.isComposing = false;
        // Track mouse-initiated focus to avoid overriding click cursor position
        this._isMouseDown = false;
    }
    componentDidMount() {
        const { initialValue } = this.props;
        this._setStringValue(initialValue);
        // Add IME composition event listeners to handle Android autosuggestions
        if (this.editorRef.current) {
            const editorNode = this.editorRef.current.editor;
            if (editorNode) {
                editorNode.addEventListener('compositionstart', this._handleCompositionStart);
                editorNode.addEventListener('compositionupdate', this._handleCompositionUpdate);
                editorNode.addEventListener('compositionend', this._handleCompositionEnd);
                editorNode.addEventListener('input', this._handleInput);
            }
        }
    }
    componentWillUnmount() {
        if (this.editorRef.current) {
            const editorNode = this.editorRef.current.editor;
            if (editorNode) {
                editorNode.removeEventListener('compositionstart', this._handleCompositionStart);
                editorNode.removeEventListener('compositionupdate', this._handleCompositionUpdate);
                editorNode.removeEventListener('compositionend', this._handleCompositionEnd);
                editorNode.removeEventListener('input', this._handleInput);
            }
        }
    }
    render() {
        const { dataHook, multiline, rows, size, disabled, readOnly, placeholder, status, statusMessage, className, } = this.props;
        const singleLineProps = {
            handlePastedText: this._handlePastedText,
            handleReturn: () => 'handled',
        };
        const finalStatus = getStatusFromContext(this.context, status);
        return (React.createElement("div", { "data-hook": dataHook, onMouseDown: this._onMouseDown, onMouseUp: this._onMouseUp, className: st(classes.root, {
                disabled,
                readOnly,
                size,
                status: finalStatus,
                singleLine: !multiline,
            }, className), style: { [vars.rows]: rows } },
            React.createElement(EditorErrorBoundary, null,
                React.createElement(Editor, { ref: this.editorRef, spellCheck: this.props.spellCheck, editorState: this.state.editorState, onChange: this._onEditorChange, onFocus: this._onFocus, placeholder: placeholder, readOnly: disabled || readOnly, ...(readOnly && { tabIndex: 0 }), ...(!multiline && singleLineProps) })),
            (status || finalStatus === 'loading') && (React.createElement("span", { className: classes.indicatorWrapper },
                React.createElement(StatusIndicator, { dataHook: dataHooks.indicator, status: finalStatus, message: statusMessage })))));
    }
}
VariableInput.contextType = StatusContext;
VariableInput.displayName = 'VariableInput';
VariableInput.defaultProps = {
    initialValue: '',
    multiline: true,
    rows: 1,
    spellCheck: false,
    size: sizeTypes.medium,
    variableParser: () => { },
    variableTagPropsParser: () => ({}),
    variableTemplate: {
        prefix: '{{',
        suffix: '}}',
    },
};
export default VariableInput;
//# sourceMappingURL=VariableInput.js.map