import type {
  CollectionOptimisticActions,
  CollectionState,
  FiltersMap,
} from '@wix/bex-core';
import type { EditableTableState } from './EditableTableState';

export interface EditableTableMutationHandlers<T> {
  /** Factory to create a new empty item with a fresh ID */
  createItem: () => T;
  /** Submit updated items to the server */
  submitUpdate: (items: T[]) => Promise<T[] | void>;
  /** Submit newly created items to the server */
  submitCreate: (items: T[]) => Promise<T[] | void>;
  /** Submit item deletions to the server */
  submitDelete: (items: T[]) => Promise<void>;
}

export class MutationState<T, F extends FiltersMap> {
  private readonly _handlers: EditableTableMutationHandlers<T>;
  private readonly _optimisticActions: CollectionOptimisticActions<T, F>;
  private readonly _parent: EditableTableState<T, F>;

  constructor(
    parent: EditableTableState<T, F>,
    handlers: EditableTableMutationHandlers<T>,
    createOptimisticActions: (
      collection: CollectionState<T, F>,
    ) => CollectionOptimisticActions<T, F>,
  ) {
    this._parent = parent;
    this._handlers = handlers;
    this._optimisticActions = createOptimisticActions(parent.collection);
  }

  init() {
    return this._optimisticActions.init();
  }

  addItem(afterItemKey?: string): string {
    const newItem = this._handlers.createItem();
    this._optimisticActions.createOne(newItem, {
      submit: this._handlers.submitCreate,
      afterItemKey,
      successToast: 'Item created',
      errorToast: (_err: unknown, { retry }: { retry: () => void }) => ({
        message: 'Failed to create item',
        action: { onClick: retry, text: 'Retry' },
      }),
    });
    return this._parent.collection.itemKey(newItem);
  }

  duplicateItem(rowKey: string) {
    const keyedItem = this._parent.keyedItems.find((ki) => ki.key === rowKey);
    if (!keyedItem) {
      return;
    }

    const sourceItem = keyedItem.item;
    const newItem = this._handlers.createItem();
    const { itemKey } = this._parent.collection;
    const newId = itemKey(newItem);
    const sourceId = itemKey(sourceItem);
    const duplicate = { ...sourceItem } as any;
    for (const k of Object.keys(duplicate)) {
      if (String(duplicate[k]) === sourceId) {
        duplicate[k] = newId;
        break;
      }
    }
    this._optimisticActions.createOne(duplicate as T, {
      submit: this._handlers.submitCreate,
      afterItemKey: keyedItem.key,
      successToast: 'Item duplicated',
      errorToast: (_err: unknown, { retry }: { retry: () => void }) => ({
        message: 'Failed to duplicate item',
        action: { onClick: retry, text: 'Retry' },
      }),
    });
  }

  deleteItems(rowKeys: string[]) {
    const items = rowKeys
      .map((key) => this._parent.keyedItems.find((ki) => ki.key === key)?.item)
      .filter((item): item is T => item != null);
    if (items.length === 0) {
      return;
    }

    this._optimisticActions.deleteMany(items, {
      submit: this._handlers.submitDelete,
      successToast:
        items.length === 1 ? 'Item deleted' : `${items.length} items deleted`,
      errorToast: (_err: unknown, { retry }: { retry: () => void }) => ({
        message: 'Failed to delete items',
        action: { onClick: retry, text: 'Retry' },
      }),
    });
  }

  updateItems(updatedItems: T[]) {
    if (updatedItems.length === 0) {
      return;
    }
    this._optimisticActions.updateMany(updatedItems, {
      keepPosition: true,
      submit: this._handlers.submitUpdate,
      successToast:
        updatedItems.length === 1
          ? 'Item updated'
          : `${updatedItems.length} items updated`,
      errorToast: (_err: unknown, { retry }: { retry: () => void }) => ({
        message: 'Failed to update items',
        action: { onClick: retry, text: 'Retry' },
      }),
    });
  }
}
