import {
  CreateManyParams,
  DeepPartial,
  DeleteManyParams,
  FiltersMap,
  KeyedItem,
  OptimisticActions,
  ToastConfig,
  UpdateAllParams,
  UpdateManyParams,
} from '@wix/bex-core';
import { NestedTableNodeState } from './NestedTableNodeState';
import once from 'lodash/once';
import { getNodeOrLeaf } from './getNodeOrLeaf';
import { NestedTableNodeStatePublicAPI } from './NestedTableNodeStatePublicAPI';
import { action, makeObservable } from 'mobx';

export interface NestedDeleteManyParams<T, R> extends DeleteManyParams<T, R> {}

export interface NestedUpdateManyParams<T, R> extends UpdateManyParams<T, R> {
  /**
   * Indicates whether to propagate the update to child nodes.
   */
  propagate?: boolean;
  /**
   * Partial data to patch the items with.
   */
  patch?: DeepPartial<T>;
}

export interface NestedTableNodeStateOptimisticActionsPublicAPI<T>
  extends OptimisticActions<T> {
  /**
   * Updates multiple items
   * @param items - The items to update.
   * @param params - Additional parameters for the update operation.
   * @param params.propagate - Indicates whether to propagate the update children nodes
   * @param params.patch - Partial data to patch the items with.
   */
  updateMany<R>(items: T[], params: NestedUpdateManyParams<T, R>): void;

  /**
   * Updates one item
   * @param item - The items to update.
   * @param params - Additional parameters for the update operation.
   * @param params.propagate - Indicates whether to propagate the update children nodes
   * @param params.patch - Partial data to patch the items with.
   */
  updateOne<R>(item: T, params: NestedUpdateManyParams<T, R>): void;

  /**
   * Deletes multiple items and their descendants
   * @param items - An array of items to delete.
   * @param params - Additional parameters for the delete operation.
   */
  deleteMany<R>(items: T[], params: NestedDeleteManyParams<T, R>): void;
  /**
   * Deletes one item and its descendants
   * @param item - An array of items to delete.
   * @param params - Additional parameters for the delete operation.
   */
  deleteOne<R>(item: T, params: NestedDeleteManyParams<T, R>): void;
}

interface BaseActionParams {
  submit: (...args: any) => any;
  successToast?: ToastConfig | string;
  errorToast?: (
    err: unknown,
    params: { retry: () => void },
  ) => ToastConfig | string;
}

export class NestedTableNodeStateOptimisticActions<
  C extends string,
  T,
  F extends FiltersMap,
> implements NestedTableNodeStateOptimisticActionsPublicAPI<T>
{
  readonly node;

  constructor(node: NestedTableNodeState<C, T, F>) {
    this.node = node;

    makeObservable(this, {
      _propagate: action,
      _runAction: action,
      deleteMany: action,
      updateMany: action,
    });
  }

  _getActionsParams<A extends BaseActionParams>(params: A) {
    const primaryActionParams = {
      ...params,
      submit: once(params.submit),
    };

    const secondaryActionParams = {
      ...primaryActionParams,
      successToast: undefined,
      errorToast: undefined,
    };

    return {
      primaryActionParams,
      secondaryActionParams,
    };
  }

  _runAction<A extends BaseActionParams>(
    action: (optimisticActions: OptimisticActions<T>, params: A) => void,
    params: A,
  ) {
    const {
      node: { flatModeCollection, children, nestedTable },
    } = this;

    const { primaryActionParams, secondaryActionParams } =
      this._getActionsParams(params);

    if (children?._optimisticActions) {
      action(children._optimisticActions, primaryActionParams);
    }

    if (flatModeCollection?._optimisticActions) {
      action(flatModeCollection._optimisticActions, secondaryActionParams);
    }

    if (
      nestedTable.toolbar.collection !== children &&
      nestedTable.toolbar.collection._optimisticActions
    ) {
      action(
        nestedTable.toolbar.collection._optimisticActions,
        secondaryActionParams,
      );
    }
  }

  _propagate(
    items: T[],
    action: (
      nodes: NestedTableNodeStatePublicAPI<C, any, any>,
      subItems: T[],
    ) => void,
  ) {
    const { node } = this;

    if (!node.children) {
      return;
    }

    const {
      itemKey,
      result: { _keyedItemsMap },
    } = node.collection;

    const directItems = items.map((item) =>
      _keyedItemsMap.get(itemKey(item)),
    ) as KeyedItem<T>[];

    const directNodes = directItems.map((keyedItem) =>
      getNodeOrLeaf(node, keyedItem),
    );

    for (const subNode of directNodes) {
      const subItems = subNode.children?.result.items as T[];

      if (!subItems?.length) {
        continue;
      }

      action(subNode, subItems);
    }
  }

  createOne(item: T, params: CreateManyParams<T>) {
    this.createMany([item], params);
  }

  createMany(items: T[], params: CreateManyParams<T>) {
    this._runAction((a, p) => a.createMany(items, p), params);
  }

  updateAll<R>(patchOrFn: (item: T) => Partial<T>, params: UpdateAllParams<R>) {
    this._runAction((a, p) => a.updateAll(patchOrFn, p), params);
  }

  deleteMany<R>(items: T[], params: NestedDeleteManyParams<T, R>) {
    const { primaryActionParams, secondaryActionParams } =
      this._getActionsParams(params);

    this._propagate(items, (subNode, subItems) => {
      subNode.optimisticActions?.deleteMany(subItems, secondaryActionParams);
    });

    this._runAction((a, p) => a.deleteMany(items, p), primaryActionParams);
  }

  deleteOne<R>(item: T, params: NestedDeleteManyParams<T, R>) {
    this.deleteMany([item], params);
  }

  deleteAll<R>(params: UpdateAllParams<R>) {
    this._runAction((a, p) => a.deleteAll(p), params);
  }

  updateMany<R>(items: T[], params: NestedUpdateManyParams<T, R>) {
    const { primaryActionParams, secondaryActionParams } =
      this._getActionsParams(params);

    if (params?.propagate) {
      this._propagate(items, (subNode, subItems) => {
        subNode.optimisticActions?.updateMany(subItems, secondaryActionParams);
      });
    }

    this._runAction((a, p) => a.updateMany(items, p), primaryActionParams);
  }

  updateOne<R>(item: T, params: NestedUpdateManyParams<T, R>) {
    this.updateMany([item], params);
  }
}
