import { ActionHandleStrategy } from '../../../dto/action-handle-strategies/action-handle-strategy.abstract';
import { RemoveChildrenAction } from '../../../dto/actions/remove-child-action';
import { getFirstNode, getCellSpan, buildTableGrid, setSpanAttributes } from '../utils/utils';
import { UpdateCursorPositionAction } from '../../../dto/actions/update-cursor-position-action';
import { MergeCellsOperationType } from '../setting-event-handlers/merge-cells-operation';
/**
 * Strategy to handle the merging of table cells.
 */
export class MergeCellsStrategy extends ActionHandleStrategy {
  /**
   * Handles the merge cells action.
   *
   * @param {MergeCellsAction} action - The action containing merge details.
   * @param {RTETableBlock} target - The target table block.
   */
  handleAction(action, target) {
    const { targetCell, mergeCells, colSpan, rowSpan, direction } = action;
    this.mergeContentToTargetCell(targetCell, mergeCells);
    setSpanAttributes(targetCell, colSpan, rowSpan, target);
    this.deleteOtherSelectedCells(mergeCells, targetCell);
    if (direction !== MergeCellsOperationType.MERGE_CELL_LEFT &&
      direction !== MergeCellsOperationType.MERGE_CELL_RIGHT &&
      rowSpan > 1) {
      this.clearRowSpanIfUniformAcrossRow(targetCell, target, rowSpan);
    }
    const root = this.getRootContent(target);
    const nextFocusNode = getFirstNode(targetCell);
    if (root && nextFocusNode) {
      root.apply(new UpdateCursorPositionAction(nextFocusNode));
    }
  }
  /**
   * Merge content from all selected cells to the target cell.
   *
   * @param {RTETableCellBlock | RTETableHeadCellBlock} targetCell - The target cell.
   * @param {RTETableCellBlock[] | RTETableHeadCellBlock[]} selectedCells - All selected cells.
   */
  mergeContentToTargetCell(targetCell, selectedCells) {
    const allChildren = selectedCells.flatMap((cell) => cell.children);
    const valueCells = allChildren.filter((child) => child.isNotEmpty());
    const allChildrenBlocks = valueCells.length > 0 ? valueCells : [allChildren[0]];
    targetCell.children = allChildrenBlocks.map((childBlock) => childBlock.clone(targetCell));
  }
  /**
   * Delete all selected cells except the target cell.
   *
   * @param {RTETableCellBlock[] | RTETableHeadCellBlock[]} selectedCells - All selected cells.
   * @param {RTETableCellBlock | RTETableHeadCellBlock} targetCell - The target cell (should not be deleted).
   */
  deleteOtherSelectedCells(selectedCells, targetCell) {
    selectedCells.forEach((cell) => {
      if (cell !== targetCell) {
        const row = cell.parent;
        row.apply(new RemoveChildrenAction(cell));
      }
    });
  }
  /**
   * Remove rowSpan from all cells in the target row when every cell shares the same rowSpan.
   * This avoids keeping stale row spans after a merge operation that no longer spans rows.
   *
   * @param {RTETableCellBlock | RTETableHeadCellBlock} targetCell - The target cell to set attributes on.
   * @param {RTETableBlock} table - The table block.
   * @param {number} targetCellRowSpan - The target cell rowSpan.
   */
  clearRowSpanIfUniformAcrossRow(targetCell, table, targetCellRowSpan) {
    const targetRow = targetCell.parent;
    const allRows = targetRow.parent.children;
    const grid = buildTableGrid(allRows);
    const rowIndexes = [];
    for (let r = 0; r < grid.length; r++) {
      if (grid[r].includes(targetCell.id)) {
        rowIndexes.push(r);
      }
    }
    if (rowIndexes.length > 1) {
      const windowStart = rowIndexes[0];
      const windowEnd = windowStart + targetCellRowSpan - 1;
      const uniformIds = new Set();
      let minRowSpan = Number.POSITIVE_INFINITY;
      for (let c = 0; c < grid[0].length; c++) {
        let firstId = grid[windowStart][c];
        let count = 1;
        let isRowSpan = false;
        for (let r = windowStart + 1; r <= windowEnd; r++) {
          if (grid[r][c] === firstId) {
            count++;
            continue;
          }
          if (count > 1) {
            isRowSpan = true;
            minRowSpan = Math.min(minRowSpan, count);
            uniformIds.add(firstId);
          }
          count = 1;
          firstId = grid[r][c];
        }
        if (count < 2 && !isRowSpan) {
          uniformIds.clear();
          break;
        }
        if (count > 1) {
          minRowSpan = Math.min(minRowSpan, count);
          uniformIds.add(firstId);
        }
      }
      if (uniformIds.size > 0) {
        allRows.forEach((row) => {
          row.children.forEach((cell) => {
            if (uniformIds.has(cell.id)) {
              const { rowSpan, colSpan } = getCellSpan(cell);
              const reducedRowSpan = rowSpan - minRowSpan + 1;
              setSpanAttributes(cell, colSpan, reducedRowSpan, table);
            }
          });
        });
      }
    }
  }
}
