import { RemoveChildrenAction } from '../../../dto/actions/remove-child-action';
import { UpdateCursorPositionAction } from '../../../dto/actions/update-cursor-position-action';
import { RTETableBodyBlock } from '../dto/table-body-block';
import { RTETableHeadBlock } from '../dto/table-head-block';
import { TableRowCommonUtilitiesStrategy } from './table-row-strategy.abstract';
/**
 * Strategy class for handling the deletion of table rows.
 */
export class DeleteTableRowStrategy extends TableRowCommonUtilitiesStrategy {
  /**
   * @inheritDoc
   */
  handleAction(action, target) {
    const { rowsToBeRemoved } = action;
    const futureFocusRow = this.getFutureFocusRow(rowsToBeRemoved[0], rowsToBeRemoved[rowsToBeRemoved.length - 1], target);
    const rowsBySection = this.splitRowsBySection(rowsToBeRemoved);
    this.removeRows(rowsBySection[0]);
    this.removeRows(rowsBySection[1]);
    if (futureFocusRow) {
      const root = this.getRootContent(target);
      if (root) {
        const focusNode = futureFocusRow.getLastNode();
        root.apply(new UpdateCursorPositionAction(focusNode, focusNode.getRangeEndOffset()));
      }
    }
  }
  /**
   * Removes the specified rows from their parent table section.
   * Handles rowspan adjustments when deleting rows that contain or are part of merged cells.
   *
   * Scenarios handled:
   * 1. If all rows in a section are being removed, the entire section is removed.
   * 2. If a cell with rowspan > 1 starts before the deleted rows and spans into them,
   *    the rowspan is adjusted to exclude the deleted rows.
   * 3. If a cell with rowspan > 1 starts in the first deleted row and spans beyond,
   *    the cell is moved to the next remaining row with adjusted rowspan.
   * 4. If a cell with rowspan is entirely within the deleted rows, it's removed with the rows.
   *
   * @param {RTETableRowBlock[]} rowsToBeRemoved - The rows to be removed.
   */
  removeRows(rowsToBeRemoved) {
    if (rowsToBeRemoved.length === 0)
      return;
    const parentSection = rowsToBeRemoved[0].parent;
    const allRows = parentSection.children;
    const removalIndices = this.buildSelectedIndices(rowsToBeRemoved, allRows);
    if (removalIndices.size > 0) {
      // If removing all rows from the section, remove the entire section
      if (allRows.length === rowsToBeRemoved.length) {
        parentSection.parent.apply(new RemoveChildrenAction(parentSection));
        return;
      }
      const firstRemovalIndex = Math.min(...removalIndices);
      const lastRemovalIndex = Math.max(...removalIndices);
      // Handle rowspan adjustments and cell movements
      const rowCellStartCols = this.getRowCellStartCols(allRows);
      const cellsToMove = this.collectCellsToMove(allRows, rowCellStartCols, removalIndices, firstRemovalIndex, lastRemovalIndex);
      this.insertMovedCells(cellsToMove, allRows, rowCellStartCols);
      // Finally, remove the rows
      rowsToBeRemoved.forEach((row) => {
        row.parent.apply(new RemoveChildrenAction(row));
      });
    }
  }
  /**
   * Collect cells that need to be moved to the next remaining row.
   *
   * @param {RTETableRowBlock[]} allRows - All rows in the section.
   * @param {number[][]} rowCellStartCols - Logical start columns per row.
   * @param {Set<number>} removalIndices - Indices of rows to remove.
   * @param {number} firstRemovalIndex - First row index to remove.
   * @param {number} lastRemovalIndex - Last row index to remove.
   * @returns {Map<number, CellToMove[]>} Map of target row index to cells to move.
   */
  collectCellsToMove(allRows, rowCellStartCols, removalIndices, firstRemovalIndex, lastRemovalIndex) {
    const cellsToMove = new Map();
    const rowCount = allRows.length;
    for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
      const row = allRows[rowIndex];
      const cellCount = row.children.length;
      for (let cellIndex = 0; cellIndex < cellCount; cellIndex++) {
        const cell = row.children[cellIndex];
        const cellRowSpan = cell.rowSpan;
        // Skip cells without rowspan
        if (cellRowSpan <= TableRowCommonUtilitiesStrategy.DEFAULT_CELLSPAN)
          continue;
        const cellEndIndex = rowIndex + cellRowSpan - 1;
        // If the cell's row span does not intersect with the removal range, skip it
        if (!this.intersectsRemovalRange(rowIndex, cellEndIndex, firstRemovalIndex, lastRemovalIndex)) {
          continue;
        }
        const removedRowsCount = this.countRemovedRows(rowIndex, cellEndIndex, removalIndices);
        const newRowSpan = cellRowSpan - removedRowsCount;
        // Scenario 1: Cell starts before deletion range and spans into it, we just need to update the rowspan
        if (rowIndex < firstRemovalIndex && cellEndIndex >= firstRemovalIndex) {
          this.updateCellSpan(cell, 'rowspan', newRowSpan);
          continue;
        }
        // Scenario 2: Cell starts in deleted row and spans beyond, we need to move it to the next remaining row and update rowspan
        if (rowIndex >= firstRemovalIndex && cellEndIndex > lastRemovalIndex) {
          const targetRowIndex = lastRemovalIndex + 1;
          if (targetRowIndex < rowCount && !removalIndices.has(targetRowIndex)) {
            const logicalStartCol = rowCellStartCols[rowIndex][cellIndex];
            if (!cellsToMove.has(targetRowIndex)) {
              cellsToMove.set(targetRowIndex, []);
            }
            cellsToMove.get(targetRowIndex).push({
              cell: this.cloneCellWithRowSpan(cell, newRowSpan),
              newRowSpan,
              cellIndex,
              logicalStartCol,
            });
          }
        }
      }
    }
    return cellsToMove;
  }
  /**
   * Clones a table cell with a new rowspan value.
   *
   * @param {RTETableCellBlock | RTETableHeadCellBlock} cell - The cell to clone.
   * @param {number} newRowSpan - The new rowspan value for the cloned cell.
   * @returns {RTETableCellBlock | RTETableHeadCellBlock} The cloned cell with updated rowspan.
   */
  cloneCellWithRowSpan(cell, newRowSpan) {
    // Clone the cell (parent will be updated when inserted)
    const clonedCell = cell.clone(cell.parent);
    // Update the rowspan of the cloned cell
    this.updateCellSpan(clonedCell, 'rowspan', newRowSpan);
    return clonedCell;
  }
  /**
   * Get the future focus row after deletion.
   *
   * @param {RTETableRowBlock} startRow - The first row to be deleted.
   * @param {RTETableRowBlock} endRow - The last row to be deleted.
   * @param {RTETableBlock} table - The table containing the rows.
   * @returns {Nullable<RTETableRowBlock>} The row to be focused after deletion.
   */
  getFutureFocusRow(startRow, endRow, table) {
    const allRows = table.children
      .filter((item) => item instanceof RTETableHeadBlock || item instanceof RTETableBodyBlock)
      .flatMap((block) => block.children);
    const startIndex = allRows.indexOf(startRow);
    const endIndex = allRows.indexOf(endRow);
    if (allRows[startIndex - 1]) {
      return allRows[startIndex - 1];
    }
    else if (allRows[endIndex + 1]) {
      return allRows[endIndex + 1];
    }
  }
}
