import {
  addEventListener,
  WixPatternsContainer,
  CollectionState,
  FiltersMap,
} from '@wix/bex-core';
import { action, makeObservable } from 'mobx';
import { DragAndDropParentState, DragAndDropState } from './DragAndDropState';
import { CollectionDragAndDropBI } from './CollectionDragAndDropBI';
import { createOptimisticActionsOrderByModeReaction } from '../../state/DragAndDrop/createOptimisticActionsOrderByModeReaction';
import { DragAndDropCancel } from './DragAndDrop';

export type CollectionDragAndDropParentState<
  T,
  F extends FiltersMap,
> = DragAndDropParentState<T, F> & {
  collection: CollectionState<T, F>;
  getCollectionSnapshot?: () => {
    [key: string]: string | number | undefined;
  };
};

interface CollectionDragAndDropStateParams<T, F extends FiltersMap> {
  container: WixPatternsContainer;
  state: CollectionDragAndDropParentState<T, F>;
  a11yContainer: {
    current: HTMLElement | null | undefined;
  };
}
export class CollectionDragAndDropState<T, F extends FiltersMap> {
  dnd: DragAndDropState<T, F>;
  state;
  bi;

  dragAndDropCancel?: DragAndDropCancel<T, F>;

  constructor(params: CollectionDragAndDropStateParams<T, F>) {
    const { state } = params;
    const { collection } = state;

    this.state = state;

    this.bi = new CollectionDragAndDropBI({
      state,
      collection,
      getCollectionSnapshot: state.getCollectionSnapshot,
      getPendingMovePatchesCount: () =>
        collection._optimisticActions?.pendingMovePatches.length,
      getBulkSelectCount: () => collection.bulkSelect.selectedCount,
    });

    this.dnd = new DragAndDropState<T, F>({
      state: this.state,
      bi: this.bi,
      collection: params.state.collection,
      container: params.container,
      move: () => this._collectionMove(),
      announcements: {
        dragHandleLabel: (id) => {
          const { translate: t } = params.container;
          const { itemName } = this.collection;

          const item = this.collection.getKeyedItem(id as string)?.item;

          return t('cairo.dragAndDrop.button-role-label.sr', {
            itemName: item ? itemName(item) : '',
          });
        },
        itemDescription: (id) => {
          const { itemName } = this.collection;

          const item = this.collection.getKeyedItem(id as string)?.item;

          return item ? itemName(item) : '';
        },
      },
    });

    this.dnd.onCancel = this._onCancel;

    makeObservable(this, {
      _collectionMove: action,
    });
  }

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

  get optimisticActions() {
    return this.collection._optimisticActions;
  }

  init() {
    const { optimisticActions, dnd, bi, collection } = this;
    const { container } = dnd;
    const { translate: t } = container;

    const disposers = [
      dnd.init(),
      createOptimisticActionsOrderByModeReaction(dnd, collection),
      addEventListener(container.events, 'navigation', () => {
        if (optimisticActions?.hasRunningSequence) {
          const patches = optimisticActions.pendingMovePatches;
          return {
            title: t('cairo.dragAndDrop.leaveSite.modal.title'),
            content: t('cairo.dragAndDrop.leaveSite.modal.description'),
            onResume: action(() => {
              bi.onMoveFailure({
                reason: 'Left page',
                patches,
              });
            }),
          };
        }
        return null;
      }),
      ...(optimisticActions
        ? [
            addEventListener(
              optimisticActions.events,
              'dragAndDropSequenceError',
              ({ patches }) => {
                bi.onMoveFailure({
                  reason: 'Update failed',
                  patches,
                });
              },
            ),
            addEventListener(
              optimisticActions.events,
              'dragAndDropSequenceRetry',
              ({ patches }) => {
                bi.onMoveRetry({ patches });
              },
            ),
          ]
        : []),
    ];

    const onWindowClosedDuringSequence = () => {
      if (optimisticActions?.hasRunningSequence) {
        bi.onMoveFailure({
          reason: 'Window closed',
          patches: optimisticActions.pendingMovePatches,
        });
      }
    };

    window.addEventListener('unload', onWindowClosedDuringSequence);

    return action(() => {
      disposers.forEach((d) => d());
      window.removeEventListener('unload', onWindowClosedDuringSequence);
    });
  }

  _onCancel = async () => {
    const { dnd } = this;
    const dragEndEvent = dnd._createDragEndEvent();

    if (dragEndEvent == null) {
      return false;
    }

    const shouldCancel = await this.dragAndDropCancel?.(
      dnd._createSimpleDragEndEvent(dragEndEvent),
    );

    if (!shouldCancel) {
      return false;
    }

    dnd._maybeShowErrorToast(shouldCancel, dragEndEvent);

    return true;
  };

  _collectionMove() {
    const { onSubmit, reorderModeState } = this.dnd;
    const { optimisticActions } = this;
    if (optimisticActions == null) {
      return;
    }

    const dragEndEvent = this.dnd._createDragEndEvent();

    if (dragEndEvent == null) {
      return;
    }

    optimisticActions.move(dragEndEvent, {
      onTryAgain: async () => {
        await reorderModeState?.onBulkSubmit();
      },
      submit: reorderModeState
        ? () => reorderModeState?.onSubmit
        : onSubmit
        ? (event) => onSubmit(this.dnd._createSimpleDragEndEvent(event))
        : undefined,
      collectionSnapshot: this.state.getCollectionSnapshot?.() ?? {},
    });
  }
}
