import { ActionMoveBase, FiltersMap, withoutIrrelevant } from '@wix/bex-core';
import {
  cairoDragActionUnsuccessfulUpdateInServer,
  cairoDragItem,
  cairoDragItemEndDrop,
  cairoTryAgainClicked,
} from '@wix/bex-core/bi';
import { action } from 'mobx';
import { OptimisticPatch } from '@wix/bex-core/dist/types/state/CollectionOptimisticPatch';
import {
  DragAndDropCollection,
  DragAndDropParentState,
} from './DragAndDropState';

const withoutDefaults = withoutIrrelevant<
  | 'currentTab'
  | 'currentView'
  | 'currentFilters'
  | 'currentSortOrder'
  | 'listSize'
  | 'filteredListSize'
  | 'initialItems'
>();

export interface CollectionDragAndDropBIParams<T, F extends FiltersMap> {
  state: DragAndDropParentState<T, F>;
  collection: DragAndDropCollection<F>;
  getCollectionSnapshot?: () => {
    [key: string]: string | number | undefined;
  };
  getBulkSelectCount: () => number;
  getPendingMovePatchesCount: () => number | null | undefined;
}
export class CollectionDragAndDropBI<T, F extends FiltersMap> {
  readonly state;
  readonly collection;
  readonly getCollectionSnapshot;
  readonly getBulkSelectCount;
  readonly getPendingMovePatchesCount;

  startTime = 0;

  constructor(params: CollectionDragAndDropBIParams<T, F>) {
    this.state = params.state;
    this.collection = params.collection;
    this.getCollectionSnapshot = params.getCollectionSnapshot;
    this.getBulkSelectCount = params.getBulkSelectCount;
    this.getPendingMovePatchesCount = params.getPendingMovePatchesCount;
  }

  get dnd() {
    return this.state._dnd;
  }

  get reportBi() {
    return this.state.reportBi;
  }

  onAttemptDragStart = action((itemId: string) => {
    if (!this.dnd) {
      return () => {};
    }
    const { reportBi, dnd, collection, getBulkSelectCount } = this;

    const { _isHandleActive } = dnd;
    const {
      query: { search },
    } = collection;

    const item = collection.getKeyedItem(itemId);

    const startTime = performance.now();

    reportBi(
      withoutDefaults(cairoDragItem)({
        itemId,
        itemIndex: item?.index,
        isFromHandle: _isHandleActive,
        numItemsBefore: getBulkSelectCount(),
        searchQuery: search.value,
        isFromSearch: !search.isEmpty,
      }),
    );

    return action(() => {
      reportBi(
        withoutDefaults(cairoDragItemEndDrop)({
          isFromHandle: _isHandleActive,
          itemId,
          itemIndexBefore: item?.index,
          duration: Math.round(performance.now() - startTime),
          endType: 'drag disabled',
          numItemsBefore: getBulkSelectCount(),
          searchQuery: search.value,
          isFromSearch: !search.isEmpty,
        }),
      );
    });
  });

  onDragStart() {
    if (!this.dnd) {
      return;
    }

    const { reportBi, dnd, collection, getBulkSelectCount } = this;
    const { active, _isHandleActive } = dnd;
    const {
      query: { search },
    } = collection;

    if (active == null) {
      return;
    }

    this.startTime = performance.now();

    reportBi(
      withoutDefaults(cairoDragItem)({
        itemId: active.key,
        itemIndex: active.index,
        isFromHandle: _isHandleActive,
        numItemsBefore: getBulkSelectCount(),
        searchQuery: search.value,
        isFromSearch: !search.isEmpty,
        dragAndDropID: dnd._moveId,
      }),
    );

    return (params: { endType?: 'clicked ESC' | 'drop outside' | null }) => {
      const { startTime } = this;

      const { overId } = dnd;

      const activeAfter = collection.getKeyedItem(active.key);

      reportBi(
        withoutDefaults(cairoDragItemEndDrop)({
          isFromHandle: _isHandleActive,
          itemId: active.key,
          duration: Math.round(performance.now() - startTime),
          endType:
            params.endType ??
            (overId === active.key ? 'drop back' : 'drop succeeded'),
          numItemsBefore: getBulkSelectCount(),
          itemIndexBefore: active.index,
          itemIndexAfter: activeAfter?.index,
          dragAndDropID: dnd._moveId,
        }),
      );
    };
  }

  onMoveFailure(params: {
    reason: 'Update failed' | 'Window closed' | 'Left page';
    patches: OptimisticPatch<T>[];
  }) {
    const { reportBi, getBulkSelectCount } = this;
    const { reason, patches } = params;
    const errorPatch = patches[0];

    if (!errorPatch?.move) {
      if (process.env.NODE_ENV !== 'production') {
        throw new Error('Missing move data in patch');
      }
      return;
    }

    const { from, over, moveId, isFromHandle } = errorPatch.move;

    reportBi(
      withoutDefaults(cairoDragActionUnsuccessfulUpdateInServer)({
        ...errorPatch.collectionSnapshot,
        numItemsBefore: getBulkSelectCount(),
        numOfWaitingUpdates: patches.length,
        timeFromEndDrag: Date.now() - errorPatch.createdAt,
        itemIndexBefore: from.index,
        itemIndexAfter: over.index,
        errorType: reason,
        itemId: from.key,
        dragAndDropID: moveId,
        wasFromHandle: isFromHandle,
      }),
    );
  }

  onMoveCancelled(move: ActionMoveBase<T>) {
    const {
      reportBi,
      getCollectionSnapshot,
      getBulkSelectCount,
      getPendingMovePatchesCount,
    } = this;

    const { from, over, moveId, isFromHandle } = move;

    reportBi(
      withoutDefaults(cairoDragActionUnsuccessfulUpdateInServer)({
        ...(getCollectionSnapshot?.() ?? {}),
        numItemsBefore: getBulkSelectCount(),
        numOfWaitingUpdates: getPendingMovePatchesCount() ?? 0,
        timeFromEndDrag: 0,
        itemIndexBefore: from.index,
        itemIndexAfter: over.index,
        errorType: 'External logic',
        itemId: from.key,
        dragAndDropID: moveId,
        wasFromHandle: isFromHandle,
      }),
    );
  }

  onMoveRetry(params: { patches: OptimisticPatch<T>[] }) {
    const { reportBi, collection } = this;
    const { patches } = params;
    const {
      query: { search },
    } = collection;
    const errorPatch = patches[0];

    reportBi(
      withoutDefaults(cairoTryAgainClicked)({
        ...errorPatch.collectionSnapshot,
        isFromSearch: !search.isEmpty,
        numOfWaitingUpdates: patches.length,
        actionName: 'drag',
      }),
    );
  }
}
