import { UniDriver } from '@wix/wix-ui-test-utils/unidriver';
import { ToolbarCollectionErrorStateUniDriver } from '../ErrorState/ToolbarCollectionErrorState.uni.driver';
import { CollectionToolbarUniDriver } from '../CollectionToolbar/CollectionToolbar.uni.driver';
import { AppliedFiltersTagListUniDriver } from '../AppliedFiltersTagList/AppliedFiltersTagList.uni.driver';
import TableUniDriver from '../Table/Table.uni.driver';
import { NestedTableParentCellUniDriver } from './NestedTableParentCell.uni.driver';
import { NestedTableFlatModeMainCellUniDriver } from './NestedTableFlatModeMainCell.uni.driver';
import { NestedTableRowPlaceholderStatesUniDriver } from './NestedTableRowPlaceholderStates.uni.driver';
import { CollectionItemActionsUniDriver } from '../CollectionItemActions/CollectionItemActions.uni.driver';
import { SummaryBarUniDriver } from '../SummaryBar/SummaryBar.uni.driver';
import { DragAndDropUniDriver } from '../DragAndDrop/DragAndDrop.uni.driver';
import { NestedTableGhostRowUniDriver } from '../NestedTableDragAndDrop/NestedTableGhostRow.uni.driver';
import { NestedTableDragOverlayUniDriver } from '../NestedTableDragAndDrop/NestedTableDragOverlay.uni.driver';
import { NestedTableRowUniDriver } from './NestedTableRow.uni.driver';
import { ExpandCollapseAllButtonUniDriver } from './ExpandCollapseAllButton.uni.driver';

export interface DragAndDropFullFlowOptions {
  horizontalPlacement?: 'child' | 'sibling' | 'parentSibling';
  scrollDirectionToTarget?: 'up' | 'down';
}

export function NestedTableUniDriver(base: UniDriver, body: UniDriver) {
  const toolbarBase = () => base.$('[data-hook="collection-toolbar"]');

  const toolbar = () => CollectionToolbarUniDriver(toolbarBase(), body);

  const table = TableUniDriver(base, body);

  const loadingStateExists = () =>
    base.$('[data-hook="placeholder-states-loading-state"]').exists();
  const getSummaryBar = () => SummaryBarUniDriver(base, body);

  const dragAndDropFullFlow = async (
    sourceRowBase: UniDriver,
    targetRowBase: UniDriver | null,
    options: DragAndDropFullFlowOptions = {},
  ) => {
    const { horizontalPlacement = 'sibling', scrollDirectionToTarget } =
      options;

    const shiftLevel =
      horizontalPlacement === 'child'
        ? 1
        : horizontalPlacement === 'parentSibling'
        ? -1
        : 0;

    const dragAndDropDriver = DragAndDropUniDriver(base);

    const sourceNestedTableRow = NestedTableRowUniDriver(sourceRowBase, body);
    const sourceLevel = await sourceNestedTableRow.getLevel();
    const sourceKey = await sourceNestedTableRow.getKey();
    const sourceIndex = await table.getRenderedItemIndexByKey(
      sourceKey as string,
    );
    const indentationWidth = await sourceNestedTableRow.getIndentationWidth();

    await dragAndDropDriver.dragStart(sourceRowBase);

    if (targetRowBase == null) {
      await table.scrollToTop();
    } else if (scrollDirectionToTarget === 'down') {
      await table.scrollDownTo(targetRowBase);
    } else if (scrollDirectionToTarget === 'up') {
      await table.scrollUpTo(targetRowBase);
    }

    const getTarget = async (targetRowBase: UniDriver) => {
      const targetNestedTableRow = NestedTableRowUniDriver(targetRowBase, body);
      const targetLevel = await targetNestedTableRow.getLevel();
      const targetKey = await targetNestedTableRow.getKey();
      const targetIndex = await table.getRenderedItemIndexByKey(
        targetKey as string,
      );
      const indentationDiff = targetLevel - sourceLevel;

      const overIndex =
        targetIndex > sourceIndex ? targetIndex : targetIndex + 1;

      const deltaX = (indentationDiff + shiftLevel) * indentationWidth;

      return {
        targetLevel,
        targetIndex,
        indentationDiff,
        overIndex,
        deltaX,
      };
    };

    const target = targetRowBase
      ? await getTarget(targetRowBase)
      : {
          overIndex: 0,
          deltaX: 0,
        };

    const overRowBase = table.getRowByIndex(target.overIndex);

    await dragAndDropDriver.moveOver(overRowBase, {
      delta: {
        x: target.deltaX,
      },
    });

    await dragAndDropDriver.dragEnd(overRowBase);
  };

  return {
    ...table,
    toolbar: () => toolbar(),
    loadingStateExists,
    getRootErrorStateByDataHook: (
      dataHook = 'placeholder-states-error-state',
    ) =>
      ToolbarCollectionErrorStateUniDriver(
        base.$(`[data-hook="${dataHook}"]`),
        body,
      ),
    subToolbar: () =>
      AppliedFiltersTagListUniDriver(
        base.$('[data-hook="collection-table-sub-toolbar"]'),
        body,
      ),

    isRowExpandable: async (rowKey: string) =>
      (await table._getRowByKey(rowKey).attr('class'))?.includes('expandable'),

    getMainColumnAt: (row: number) =>
      NestedTableParentCellUniDriver(
        table._getCellByRowIndexAndCellKey(row, 'nested-mode-main-column'),
        body,
      ),

    _getDragAndDropDriver: () => DragAndDropUniDriver(base),

    /**
     * Drag and drop a row by its key to a target row by its key. Possible options:
     * - sourceKey: The ID of the item being moved.
     * - targetKey: The ID of the target item (the one the moved item will be placed vertically after). If null - move the item to the top of the table (i.e no "target" item)
     * - options.placement: Defines the position of the moved item relative to the target:
     *   - 'child': Places the moved item as a child of the target.
     *   - 'sibling': Places the moved item as a sibling immediately after the target. - This is the default if options.placement is not provided by the developer
     *   - 'parentSibling': Places the moved item as a sibling of the target's parent.
     * - options.scrollDirectionToTarget: Scroll to the target item before trying to drop. If not provided, no scroll will be done.
     *   - 'down'
     *   - 'up'
     */
    dragAndDropRowByKey: async (
      sourceKey: string,
      targetKey: string | null,
      options?: DragAndDropFullFlowOptions,
    ) => {
      return dragAndDropFullFlow(
        table._getRowByKey(sourceKey),
        targetKey ? table._getRowByKey(targetKey) : null,
        options,
      );
    },

    /**
     * Drag and drop a row by its index to a target row by its index. Possible options:
     * - sourceKey: The ID of the item being moved.
     * - targetKey: The ID of the target item (the one the moved item will be placed vertically after). If null - move the item to the top of the table (i.e no "target" item)
     * - options.placement: Defines the position of the moved item relative to the target:
     *   - 'child': Places the moved item as a child of the target.
     *   - 'sibling': Places the moved item as a sibling immediately after the target. - This is the default if options.placement is not provided by the developer
     *   - 'parentSibling': Places the moved item as a sibling of the target's parent.
     * - options.scrollDirectionToTarget: Scroll to the target item before trying to drop. If not provided, will be determined by the indexes.
     *   - 'down'
     *   - 'up'
     */
    dragAndDropRowAt: async (
      sourceIndex: number,
      targetIndex: number | null,
      options: DragAndDropFullFlowOptions,
    ) => {
      return dragAndDropFullFlow(
        table.getRow(sourceIndex),
        targetIndex ? table.getRow(targetIndex) : null,
        {
          ...options,
          scrollDirectionToTarget:
            options.scrollDirectionToTarget ??
            (targetIndex != null
              ? targetIndex > sourceIndex
                ? 'down'
                : 'up'
              : undefined),
        },
      );
    },

    getActionCellByKey: (rowKey: string) =>
      CollectionItemActionsUniDriver(table._getRowByKey(rowKey), body),

    getMainColumnByKey: (rowKey: string) =>
      NestedTableParentCellUniDriver(
        table._getCellByKey(rowKey, 'nested-mode-main-column'),
        body,
      ),

    getRenderedRowsByParentKey: (parentId: string | null) =>
      (parentId
        ? base.$$(
            `tbody tr[data-table-row="dataTableRow"][data-parent-key="${parentId}"]`,
          )
        : base.$$(
            `tbody tr[data-table-row="dataTableRow"]:not([data-parent-key])`,
          )
      ).map(async (row) => NestedTableRowUniDriver(row, body)),

    getRenderedRowsByLevel: (level: number) =>
      base
        .$$(`tbody tr[data-table-row="dataTableRow"][data-level="${level}"]`)
        .map(async (row) => NestedTableRowUniDriver(row, body)),

    getRenderedRowByKey: (rowKey: string) =>
      NestedTableRowUniDriver(table._getRowByKey(rowKey), body),

    getRowParentKey: (rowKey: string) =>
      table._getRowByKey(rowKey).attr('data-parent-key'),

    getRowIndexInParent: async (rowKey: string) =>
      Number(await table._getRowByKey(rowKey).attr('data-index-in-parent')),

    getFlatModeMainColumnByKey: (rowKey: string) =>
      NestedTableFlatModeMainCellUniDriver(
        table._getCellByKey(rowKey, 'flat-mode-main-column'),
        body,
      ),

    getFlatModeMainColumnAt: (row: number) =>
      NestedTableFlatModeMainCellUniDriver(
        table._getCellByRowIndexAndCellKey(row, 'flat-mode-main-column'),
        body,
      ),

    getPlaceholderStatesByRowKey: (rowKey: string) =>
      NestedTableRowPlaceholderStatesUniDriver(
        table._getRowByKey(`placeholder-states-${rowKey}`),
        body,
      ),
    /**
     * Gets collection summary bar
     */
    getSummaryBar,

    getGhostRow: () =>
      NestedTableGhostRowUniDriver(
        base.$('[data-hook="ghost-indentation-cell"]'),
        body,
      ),

    getDragOverlay: () =>
      NestedTableDragOverlayUniDriver(
        body.$('[data-hook="nested-table-dnd-overlay-wrapper"]'),
        body,
      ),
    getExpandCollapseAllButton: () =>
      ExpandCollapseAllButtonUniDriver(base, body),
  };
}

export default NestedTableUniDriver;
