export function FormulaInput()

in frontend/apps/quantgrid/src/app/components/FormulaBar/FormulaInput.tsx [45:410]


export function FormulaInput({ fieldName, inputIndex = 0 }: Props) {
  const eventBus = useEventBus<EventBusMessages>();
  const { selectedCell, functions, parsedSheets, sheetContent } =
    useContext(ProjectContext);
  const {
    editMode,
    formulaBarMode,
    formulaBarExpanded,
    theme,
    switchPointClickMode,
    isPointClickMode,
    canvasSpreadsheetMode,
  } = useContext(AppContext);
  const gridApi = useGridApi();

  const [codeEditor, setCodeEditor] = useState<
    editor.IStandaloneCodeEditor | undefined
  >();
  const [isEditingDimField, setIsEditingDimField] = useState(false);

  const setCode = useRef<SetCodeRefFunction>(null);
  const setFocus = useRef<SetFocusRefFunction>(null);
  const codeRef = useRef<string | null>(null);
  const skipBlurEffect = useRef(false);
  const isCurrentPointClickDimField = useRef<boolean>(false);

  // Point click mode state
  const cursorOffset = useRef<number>(0);
  const lastPointClickValue = useRef<string>('');
  const lastCodeEditorValue = useRef<string>('');

  const { saveFormulaInputValue, getSelectedCellValue } = useFormulaInput();
  const { borderColor } = useFormulaInputStyles(fieldName, isEditingDimField);

  const formulaInputEditMode = useMemo((): GridCellEditorMode => {
    if (editMode) return editMode;

    if (fieldName && !editMode) return 'edit_dim_expression';

    return editMode;
  }, [editMode, fieldName]);

  const onCodeChange = useCallback(
    (code: string) => {
      codeRef.current = code;

      if (!gridApi) return;

      const shouldOpenCellEditor =
        selectedCell &&
        isFormulaBarMonacoInputFocused() &&
        !gridApi.isCellEditorOpen();

      if (shouldOpenCellEditor) {
        gridApi.showCellEditor(selectedCell.col, selectedCell.row, code, {
          dimFieldName: fieldName,
        });
        setIsEditingDimField(true);
      }

      if (gridApi.isCellEditorOpen() && !gridApi.isCellEditorFocused()) {
        gridApi.setCellEditorValue(code);
      }
    },
    [gridApi, fieldName, selectedCell]
  );

  const closeCellEditor = useCallback(() => {
    if (gridApi?.isCellEditorOpen()) {
      gridApi?.hideCellEditor();
    }
  }, [gridApi]);

  const onSave = useCallback(() => {
    if (codeRef.current === null || gridApi?.isCellEditorFocused()) return;

    skipBlurEffect.current = true;
    const isCloseCellEditor = saveFormulaInputValue(
      codeRef.current,
      selectedCell,
      formulaInputEditMode,
      fieldName
    );

    if (isCloseCellEditor) {
      setIsEditingDimField(false);
      closeCellEditor();
    }
  }, [
    formulaInputEditMode,
    closeCellEditor,
    fieldName,
    gridApi,
    saveFormulaInputValue,
    selectedCell,
  ]);

  const onBlur = useCallback(() => {
    if (isPointClickMode) return;

    if (skipBlurEffect.current) {
      skipBlurEffect.current = false;

      return;
    }

    // setTimeout because we need to wait for the focus on cell editor
    setTimeout(() => {
      if (gridApi?.isCellEditorFocused()) return;

      const isCloseCellEditor = saveFormulaInputValue(
        codeRef.current || '',
        selectedCell,
        formulaInputEditMode,
        fieldName
      );

      if (isCloseCellEditor) {
        const value = getSelectedCellValue(selectedCell, fieldName) || '';
        codeRef.current = value;
        setCode.current?.(value);

        setIsEditingDimField(false);
        closeCellEditor();

        return;
      }
    }, 0);
  }, [
    isPointClickMode,
    gridApi,
    saveFormulaInputValue,
    selectedCell,
    formulaInputEditMode,
    fieldName,
    getSelectedCellValue,
    closeCellEditor,
  ]);

  const onEscape = useCallback(() => {
    // skip blur effect because CodeEditor after escape event calls onEscape and then onBlur
    skipBlurEffect.current = true;

    const value = getSelectedCellValue(selectedCell, fieldName) || '';
    codeRef.current = value;
    setCode.current?.(value);

    setIsEditingDimField(false);
    closeCellEditor();
  }, [closeCellEditor, fieldName, getSelectedCellValue, selectedCell]);

  const handleCellEditorUpdateValue = useCallback(
    (message: CellEditorUpdateValueMessage) => {
      let value = message.value;
      const { dimFieldName, cancelEdit } = message;

      if (cancelEdit) {
        value = getSelectedCellValue(selectedCell, fieldName) || '';
        setIsEditingDimField(false);
      }

      if (fieldName && dimFieldName !== fieldName && !cancelEdit) return;

      codeRef.current = value;
      setCode.current?.(value);
    },
    [fieldName, getSelectedCellValue, selectedCell]
  );

  const handlePointClickSetValue = useCallback(
    (message: PointClickSetValue) => {
      if (!isPointClickMode || !gridApi) return;

      if (!isCurrentPointClickDimField.current) return;

      const currentValue = codeRef.current || '';
      const offset = cursorOffset.current;
      const updatedOffset = lastPointClickValue.current
        ? offset + lastPointClickValue.current.length
        : offset;
      const updatedValue =
        currentValue.slice(0, offset) +
        message.value +
        currentValue.slice(updatedOffset);

      lastCodeEditorValue.current = updatedValue;
      lastPointClickValue.current = message.value;

      codeRef.current = updatedValue;
      setCode.current?.(updatedValue);

      if (gridApi.isCellEditorOpen() && !gridApi.isCellEditorFocused()) {
        gridApi.setCellEditorValue(updatedValue);
      }

      setFocus.current?.();
    },
    [setFocus, gridApi, isPointClickMode]
  );

  const onEditorReady = useCallback(
    (codeEditor: editor.IStandaloneCodeEditor | undefined) => {
      setCodeEditor(codeEditor);

      const value = getSelectedCellValue(selectedCell, fieldName) || '';

      if (gridApi?.isCellEditorOpen()) return;

      codeRef.current = value;
      setCode.current?.(value);
    },
    [fieldName, getSelectedCellValue, gridApi, selectedCell]
  );

  const onStartPointClick = useCallback(
    (offset: number) => {
      isCurrentPointClickDimField.current = true;
      switchPointClickMode(true, 'formula-bar');
      cursorOffset.current = offset;
    },
    [switchPointClickMode]
  );

  const onStopPointClick = useCallback(
    (offset: number) => {
      const isSameValue = codeRef.current === lastCodeEditorValue.current;
      const isOffsetChanged =
        cursorOffset.current + lastPointClickValue.current.length !== offset;

      if (isSameValue && !isOffsetChanged) {
        return;
      }

      lastPointClickValue.current = '';
      lastCodeEditorValue.current = '';
      cursorOffset.current = 0;
      isCurrentPointClickDimField.current = false;

      switchPointClickMode(false);
      if (canvasSpreadsheetMode && gridApi) {
        (gridApi as GridApi).updateSelection(null, { silent: true });
      }
    },
    [canvasSpreadsheetMode, gridApi, switchPointClickMode]
  );

  const handleFormulaBarFormulasMenuItemApply = useCallback(
    ({ formulaName }: FormulaBarFormulasMenuItemApplyMessage) => {
      const currentValue = codeRef.current ?? '';
      const currentCursorOffset = currentValue
        ? cursorOffset.current || (currentValue.length ?? 0)
        : 0;
      const addedValue = (currentValue.length ? '' : '=') + formulaName;
      codeRef.current =
        currentValue.slice(0, currentCursorOffset) +
        addedValue +
        currentValue.slice(currentCursorOffset);
      setCode.current?.(codeRef.current);

      if (gridApi?.isCellEditorOpen() && !gridApi?.isCellEditorFocused()) {
        gridApi.setCellEditorValue(codeRef.current);
      } else {
        if (!selectedCell) {
          gridApi?.updateSelection({
            startCol: 1,
            endCol: 1,
            startRow: 1,
            endRow: 1,
          });
        }
        gridApi?.showCellEditor(
          selectedCell?.col ?? 1,
          selectedCell?.row ?? 1,
          codeRef.current
        );
      }

      setFocus.current?.({
        cursorOffset: currentCursorOffset + addedValue.length - 1,
      });
      codeEditor?.getAction('editor.action.triggerParameterHints')?.run();
    },
    [gridApi, codeEditor, selectedCell]
  );

  const isReadOnly = useMemo(() => {
    return (
      formulaBarMode === 'value' &&
      selectedCell?.type === SelectedCellType.Total
    );
  }, [formulaBarMode, selectedCell]);

  useEffect(() => {
    const cellEditorUpdateValueListener = eventBus.subscribe(
      'CellEditorUpdateValue',
      handleCellEditorUpdateValue
    );

    const pointClickSetValueListener = eventBus.subscribe(
      'PointClickSetValue',
      handlePointClickSetValue
    );

    const formulaBarFormulasMenuListener = eventBus.subscribe(
      'FormulaBarFormulasMenuItemApply',
      handleFormulaBarFormulasMenuItemApply
    );

    return () => {
      cellEditorUpdateValueListener.unsubscribe();
      pointClickSetValueListener.unsubscribe();
      formulaBarFormulasMenuListener.unsubscribe();
    };
  }, [
    eventBus,
    handleCellEditorUpdateValue,
    handleFormulaBarFormulasMenuItemApply,
    handlePointClickSetValue,
  ]);

  useEffect(() => {
    const value = getSelectedCellValue(selectedCell, fieldName) || '';

    if (codeRef.current === value || isCellEditorOpen()) return;

    codeRef.current = value;
    setCode.current?.(value);
  }, [fieldName, getSelectedCellValue, selectedCell]);

  return (
    <div className="flex h-full w-full items-center">
      {inputIndex === 0 && <FormulaBarModeIndicator />}
      <div className={cx('flex h-full w-full items-center', borderColor)}>
        {fieldName && <FormulaBarTitle text={fieldName} />}
        <div className="w-full h-full relative pt-1">
          <CodeEditor
            codeEditorPlace="formulaBar"
            currentFieldName={selectedCell?.fieldName}
            currentTableName={selectedCell?.tableName}
            functions={functions}
            language="formula-bar"
            options={{
              ...formulaEditorOptions,
              domReadOnly: isReadOnly,
              readOnly: isReadOnly,
              wordWrap: formulaBarExpanded ? 'on' : 'off',
            }}
            parsedSheets={parsedSheets}
            setCode={setCode}
            setFocus={setFocus}
            sheetContent={sheetContent || ''}
            theme={theme}
            onBlur={onBlur}
            onCodeChange={onCodeChange}
            onEditorReady={onEditorReady}
            onEnter={formulaBarExpanded ? undefined : onSave}
            onEscape={onEscape}
            onSaveButton={onSave}
            onStartPointClick={onStartPointClick}
            onStopPointClick={onStopPointClick}
          />
        </div>
      </div>
    </div>
  );
}