import { ofType } from 'redux-observable';
import { concat, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';

import { UPSERT_TAGS } from '@/graphql/services/aiWorkbench/constants/tagLog';
import { TYPE_SUCCESS } from '@/modules/@common/notify/constants';

import { UPSERT_TAGS_PAYLOAD_NAME } from '@/graphql/services/aiWorkbench/types/tagLog/payload/upserTag';

import { selectedVideoSelector } from '../../../AiWorkbench/redux/selectors';
import { getSelectedVideoAction } from '../../../AiWorkbench/redux/slices';
import { dataSelector } from '../selectors';
import { setAction, upsertTagsAction } from '../slices';
import { gqlRequest } from '@/modules/@common/request';
import { showNotificationAction } from '@/modules/layout/redux/slices';

const query = `mutation ${UPSERT_TAGS}($payload: ${UPSERT_TAGS_PAYLOAD_NAME}) {
  ${UPSERT_TAGS}(payload: $payload) {
    startTime
    uid
    keywords {
      category
      value
      id
      labelId
      labelName
      color
    }
  }
}`;

function getTagLogDataUpdated(tagLogData, res) {
  let tagLogDataUpdated = [...tagLogData];
  const isExistStartTimeIndex = tagLogData.findIndex((tagLog) => tagLog.startTime === res.startTime);

  if (isExistStartTimeIndex >= 0) {
    const logRow = tagLogData[isExistStartTimeIndex];
    const isLogExistInLogRowIndex = logRow.log.findIndex((log) => log.uid === res.uid);
    const updatedLog =
      isLogExistInLogRowIndex >= 0
        ? logRow.log.map((log, index) => (index === isLogExistInLogRowIndex ? res : log))
        : [...logRow.log, res];

    tagLogDataUpdated[isExistStartTimeIndex] = { ...logRow, log: updatedLog };
  } else {
    const startIndex = tagLogData.findIndex((tag) => tag.startTime >= res.startTime);
    const logObject = {
      startTime: res.startTime,
      log: [res],
    };

    if (startIndex === 0) {
      tagLogDataUpdated = [logObject, ...tagLogDataUpdated];
    } else if (startIndex === -1) {
      tagLogDataUpdated = [...tagLogDataUpdated, logObject];
    } else {
      tagLogDataUpdated = [
        ...tagLogDataUpdated.slice(0, startIndex),
        logObject,
        ...tagLogDataUpdated.slice(startIndex),
      ];
    }
  }

  return tagLogDataUpdated;
}

export default (action$, state$) =>
  action$.pipe(
    ofType(upsertTagsAction.type),
    switchMap((action) => {
      const video = selectedVideoSelector(state$.value);
      const tagLogData = dataSelector(state$.value);

      const { startTime, keywords, logRowUid } = action.payload;

      // optimistic update
      const tagLogDataUpdatedOptimistics = getTagLogDataUpdated(tagLogData, { startTime, keywords, uid: logRowUid });

      const stream$ = gqlRequest({
        query,
        variables: {
          payload: {
            videoId: video.uid,
            keywords,
            startTime,
            logRowUid,
          },
        },
      }).pipe(
        switchMap(({ data, errors }) => {
          const payload = { isLoading: false };
          const actions = [of(setAction(payload))];

          if (!errors?.length) {
            const res = data[UPSERT_TAGS];
            const tagLogDataUpdated = getTagLogDataUpdated(tagLogData, res);

            actions.push(
              of(getSelectedVideoAction(video.uid)),
              of(setAction({ data: tagLogDataUpdated })),
              of(
                showNotificationAction({
                  type: TYPE_SUCCESS,
                  message: 'Tag updated',
                }),
              ),
            );
          }

          return concat(...actions);
        }),
      );

      return concat(
        of(
          setAction({
            isLoading: true,
            data: logRowUid ? tagLogDataUpdatedOptimistics : tagLogData,
          }),
        ),
        stream$,
      );
    }),
  );
