Загрузка данных


import { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';

import { communicator } from '@core/comm';
import { useAppSelect } from '@hooks/useAppSelector';
import { CORPACTIONS_OPEN_EXESTED_WIDGET_EVENT, HIGHLIGHT_WIDGET_EVENT } from '@modules/widgets/shared';
import { addContentPropsToWidget, unbindWidgets } from '@store/slices/widgets';
import { getState } from '@store/store';
import { useWidgetsBind } from '@utils/hooks/useWidgetsBind';

import { DEFAULT_SYMBOL } from '../const';

import { useChartPublicContext } from './useChartPublicContext';

import type { WidgetProperties } from '../properties/types';
import type { ChartContainerProps, SelectedInstrument } from '../types';

import type { Dispatch, SetStateAction } from 'react';
import type { Widget } from 'types/Widgets';

interface UseChartComponentFacadeReturn {
  dropDownOpen: boolean;
  setDropdownOpen: Dispatch<SetStateAction<boolean>>;
  currentInstrument: string;
  currentInstrumentName: string;
  isWidgetHeaderContextMenuOpen: boolean;
  setIsWidgetHeaderContextMenuOpen: Dispatch<SetStateAction<boolean>>;
  onDropInstruments: (instrument: SelectedInstrument, withUpdate?: boolean) => void;
  addInstrumentFromModal: (instruments: SelectedInstrument[]) => void;
  isOver: boolean;
}

type SaveContentPropsOptions = Partial<WidgetProperties['chartState']> & {
  withUpdate?: boolean;
  cleanIndicativeData?: boolean;
};

export default function useChartComponentFacade(props: ChartContainerProps): UseChartComponentFacadeReturn {
  const { widgetId } = props;

  const dispatch = useDispatch();

  const [isOver, setIsOver] = useState(false);
  const [dropDownOpen, setDropdownOpen] = useState(false);
  const [isWidgetHeaderContextMenuOpen, setIsWidgetHeaderContextMenuOpen] = useState(false);

  const widgetProperties = useAppSelect(
    (state) => state.widgets.widgets.find(({ id }) => id === widgetId)?.widgetContentProps,
  ) as WidgetProperties | undefined;

  const initialInstrument = widgetProperties?.chartState?.savedInstrument ?? DEFAULT_SYMBOL;
  const initialInstrumentName = widgetProperties?.chartState.savedInstrumentName ?? DEFAULT_SYMBOL;
  const initialInstrumentTicker = widgetProperties?.chartState.savedInstrumentTicker ?? DEFAULT_SYMBOL;

  // TODO временно реф из-за непредсказуемого изменения если используется useState,
  // нужен рефакторинг и вернуть обратно useState
  const currentInstrumentRef = useRef(initialInstrument);
  const currentInstrumentNameRef = useRef(initialInstrumentName);
  const currentInstrumentTickerRef = useRef(initialInstrumentTicker);

  const [currentInstrument, setCurrentInstrument] = useState(initialInstrument);
  const [currentInstrumentName, setCurrentInstrumentName] = useState(initialInstrumentName);
  const [currentInstrumentTicker, setCurrentInstrumentTicker] = useState(initialInstrumentTicker);

  const { triggerRelatedWidgetsToUpdate, getMasterInstrumentFromPublicContext } = useWidgetsBind({
    widgetId,
  });

  const triggerRelatedWidgetsToUpdateRef = useRef(triggerRelatedWidgetsToUpdate);

  useEffect(() => {
    triggerRelatedWidgetsToUpdateRef.current = triggerRelatedWidgetsToUpdate;
  }, [triggerRelatedWidgetsToUpdate]);

  const saveContentProps = useCallback(
    (val: SaveContentPropsOptions): void => {
      const { withUpdate, cleanIndicativeData, ...chartStateVal } = val;

      const oldProps = (getState().widgets.widgets.find(({ id }) => id === widgetId) as Widget | undefined)
        ?.widgetContentProps;

      if (!oldProps) {
        return;
      }

      dispatch(
        addContentPropsToWidget({
          id: widgetId,
          withoutSend: !withUpdate,
          widgetContentProps: {
            chartState: {
              ...oldProps.chartState,
              ...chartStateVal,
            },
            // при смене инструмента очищаем индикативные данные, переданные из виджета Индикативные котировки
            indicativeData: cleanIndicativeData ? undefined : oldProps.indicativeData,
          },
        }),
      );
    },
    [dispatch, widgetId],
  );

  const onInstrumentChange = useCallback(
    (newVal: string, newName?: string, newTicker?: string, withUpdate?: boolean, unbind = true): void => {
      if (!newVal) {
        return;
      }

      setIsWidgetHeaderContextMenuOpen(false);

      const symbolChanged = currentInstrumentRef.current !== newVal;
      const nextInstrumentName = newName?.trim() ?? (symbolChanged ? '' : currentInstrumentNameRef.current);
      const nameChanged = currentInstrumentNameRef.current !== nextInstrumentName;

      if (!symbolChanged && !nameChanged) {
        return;
      }

      if (symbolChanged) {
        currentInstrumentRef.current = newVal;
        setCurrentInstrument(newVal);

        if (unbind) {
          dispatch(unbindWidgets({ widgetId }));
        }
      }

      currentInstrumentNameRef.current = nextInstrumentName;
      setCurrentInstrumentName(nextInstrumentName);

      // при смене инструмента очищаем индикативные данные виджета график
      // т.к. логика для графика индикатива построена на наличии в widgetContentProps данных indicativeData
      saveContentProps({
        savedInstrument: newVal,
        savedInstrumentName: nextInstrumentName || undefined,
        withUpdate,
        cleanIndicativeData: symbolChanged,
      });

      if (symbolChanged) {
        triggerRelatedWidgetsToUpdateRef.current(newVal);
      }
    },
    [dispatch, saveContentProps, widgetId],
  );

  const addInstrumentFromModal = useCallback(
    (instruments: SelectedInstrument[]) => {
      const instrument = instruments[0];

      if (!instrument?.issKey) {
        return;
      }

      onInstrumentChange(instrument.issKey, instrument.displayName);
    },
    [onInstrumentChange],
  );

  const onInstrumentChangeFromBind = useCallback(
    (instrumentId: string, instrumentName?: string) => {
      onInstrumentChange(instrumentId, instrumentName, true, false);
    },
    [onInstrumentChange],
  );

  const onDropInstruments = useCallback(
    (instrument: SelectedInstrument, withUpdate?: boolean): void => {
      if (!instrument.issKey) {
        return;
      }

      onInstrumentChange(instrument.issKey, instrument.displayName, withUpdate);
    },
    [onInstrumentChange],
  );

  useChartPublicContext({
    setCurrInstrument: onInstrumentChangeFromBind,
    widgetId,
    getMasterInstrumentFromPublicContext,
  });

  useEffect(() => {
    triggerRelatedWidgetsToUpdateRef.current(currentInstrumentRef.current);
  }, []);

  useEffect(() => {
    const unsubscribe = communicator.listen(
      {
        messageType: CORPACTIONS_OPEN_EXESTED_WIDGET_EVENT,
      },
      (message) => {
        const issKey = (message as Record<number, string>)[widgetId];

        if (issKey) {
          addInstrumentFromModal([{ issKey }]);
        }
      },
    );

    const unsubscribeHighlighter = communicator.listen(
      {
        messageType: HIGHLIGHT_WIDGET_EVENT,
      },
      (message) => {
        const typedMessage = message as Record<number, boolean>;

        if (Object.keys(typedMessage).includes(String(widgetId))) {
          setIsOver(typedMessage[widgetId]);
        }
      },
    );

    return () => {
      unsubscribe();
      unsubscribeHighlighter();
    };
  }, [addInstrumentFromModal, widgetId]);

  return {
    dropDownOpen,
    setDropdownOpen,
    currentInstrument,
    currentInstrumentName,
    isWidgetHeaderContextMenuOpen,
    setIsWidgetHeaderContextMenuOpen,
    onDropInstruments,
    addInstrumentFromModal,
    isOver,
  };
}