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


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;
  currentInstrumentTicker: 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 ??
    initialInstrument;
  const initialInstrumentTicker =
    widgetProperties?.chartState?.savedInstrumentTicker ??
    initialInstrument;

  // 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 normalizedName = newName?.trim();
      const normalizedTicker = newTicker?.trim();

      const nextInstrumentName =
        normalizedName ||
        (symbolChanged
          ? newVal
          : currentInstrumentNameRef.current);

      const nextInstrumentTicker =
        normalizedTicker ||
        (symbolChanged
          ? newVal
          : currentInstrumentTickerRef.current);

      const nameChanged =
        currentInstrumentNameRef.current !==
        nextInstrumentName;

      const tickerChanged =
        currentInstrumentTickerRef.current !==
        nextInstrumentTicker;

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

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

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

      if (nameChanged) {
        currentInstrumentNameRef.current =
          nextInstrumentName;
        setCurrentInstrumentName(nextInstrumentName);
      }

      if (tickerChanged) {
        currentInstrumentTickerRef.current =
          nextInstrumentTicker;
        setCurrentInstrumentTicker(
          nextInstrumentTicker,
        );
      }

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

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

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

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

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

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

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

      onInstrumentChange(
        instrument.issKey,
        instrument.displayName,
        instrument.symbol,
        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) {
          onInstrumentChange(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();
    };
  }, [onInstrumentChange, widgetId]);

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