import { RemoveChildrenAction } from '../../../dto/actions/remove-child-action';
import { getAllTableRows, getFirstNode, buildTableGrid, buildCellStartColumnMap, findCellBlockById, getCellSpan, getTableColumnCount, setSpanAttributes, } from '../utils/utils';
import { UpdateCursorPositionAction } from '../../../dto/actions/update-cursor-position-action';
import { TableColumnStrategy } from './table-column-strategy.abstract';
/**
 * Strategy to handle the deletion of table column from the parent.
 */
export class DeleteTableColumnStrategy extends TableColumnStrategy {
  /**
   * Deletes the specified table column and updates the cursor position.
   *
   * @param {DeleteTableColumnAction} action - The action containing the table column to delete.
   * @param {RTETableBlock} target - The target table block from which the column will be deleted.
   */
  handleAction(action, target) {
    const allTableRows = getAllTableRows(target);
    const tableGrid = buildTableGrid(allTableRows);
    const [startCellCol, endCellCol] = this.getBoundaryCellColumnRange(action.tableCellDeleteRange, allTableRows, tableGrid);
    const anchorCell = action.tableCellDeleteRange[action.tableCellDeleteRange.length - 1];
    const anchorRow = anchorCell.parent;
    const anchorRowIndex = Math.max(allTableRows.indexOf(anchorRow), 0);
    const { coveredCellIds, coveredCells, adjustmentCellInfo } = this.getDeleteCellsInfo(allTableRows, startCellCol, endCellCol, tableGrid);
    const nextFocusableCell = this.findNextFocusableCell(allTableRows, tableGrid, startCellCol, endCellCol, coveredCellIds, anchorRowIndex);
    this.deleteCells(coveredCells, adjustmentCellInfo, target);
    const root = this.getRootContent(target);
    const nextFocusNode = getFirstNode(nextFocusableCell);
    if (root && nextFocusNode) {
      root.apply(new UpdateCursorPositionAction(nextFocusNode));
    }
  }
  /**
   * Analyze overlap between each cell and the delete range in a single pass.
   * Returns fully covered cells (to delete) and partially covered cells (to adjust).
   *
   * @param {RTETableRowBlock[]} allRows - All rows in the table.
   * @param {number} startCol - Start logical column to delete (inclusive).
   * @param {number} endCol - End logical column to delete (inclusive).
   * @param {string[][]} tableGrid - The 2D grid representation of the table.
   * @returns {DeleteCellInfo} Overlap analysis for deletion and span adjustments.
   */
  getDeleteCellsInfo(allRows, startCol, endCol, tableGrid) {
    const coveredCells = [];
    const coveredCellIds = new Set();
    const adjustmentCellInfo = [];
    const cellStartColMap = buildCellStartColumnMap(tableGrid);
    allRows.forEach((row) => {
      row.children.forEach((cell) => {
        const cellStartCol = cellStartColMap.get(cell.id);
        if (cellStartCol === undefined) {
          return;
        }
        const { colSpan, rowSpan } = getCellSpan(cell);
        const overlap = this.getColumnOverlapCount(cellStartCol, colSpan, startCol, endCol);
        if (overlap <= 0) {
          return;
        }
        if (overlap === colSpan) {
          coveredCells.push(cell);
          coveredCellIds.add(cell.id);
          return;
        }
        const nextColSpan = Math.max(1, colSpan - overlap);
        adjustmentCellInfo.push({ cell, nextColSpan, rowSpan });
      });
    });
    return { coveredCells, coveredCellIds, adjustmentCellInfo };
  }
  /**
   * Find the next focusable cell based on logical column position.
   *
   * @param {RTETableRowBlock[]} allTableRows - All table rows.
   * @param {string[][]} tableGrid - The 2D grid representation.
   * @param {number} startLogicalCol - Start of delete range.
   * @param {number} endLogicalCol - End of delete range.
   * @param {Set<string>} cellIdsToDelete - delete cell ids
   * @param {number} anchorRowIndex - the anchor row index
   * @returns {Nullable<RTETableCellBlock | RTETableHeadCellBlock>} The next focusable cell.
   */
  findNextFocusableCell(allTableRows, tableGrid, startLogicalCol, endLogicalCol, cellIdsToDelete, anchorRowIndex) {
    for (let offset = 0; offset < allTableRows.length; offset++) {
      const rowIndex = (anchorRowIndex + offset) % allTableRows.length;
      const gridRow = tableGrid[rowIndex];
      const candidateCellId = this.findFocusableCellIdInGridRow(gridRow, startLogicalCol, endLogicalCol, cellIdsToDelete);
      if (!candidateCellId) {
        continue;
      }
      const candidateCell = findCellBlockById(allTableRows, candidateCellId);
      if (candidateCell) {
        return candidateCell;
      }
    }
    return null;
  }
  /**
   * Find the next focusable cell ID in a grid row, skipping deleted cells.
   *
   * @param {string[]} gridRow - The grid row array.
   * @param {number} startCol - Start logical column.
   * @param {number} endCol - End logical column.
   * @param {Set<string>} cellIdsToDelete - Set of cell IDs to skip.
   * @returns {Nullable<string>} The next focusable cell ID, or null if none found.
   */
  findFocusableCellIdInGridRow(gridRow, startCol, endCol, cellIdsToDelete) {
    // Prefer the first cell strictly to the right of the deleted range.
    for (let col = endCol + 1; col < gridRow.length; col++) {
      const cellId = gridRow[col];
      if (cellId && !cellIdsToDelete.has(cellId)) {
        return cellId;
      }
    }
    // Fallback to the closest cell to the left of the deleted range.
    for (let col = startCol - 1; col >= 0; col--) {
      const cellId = gridRow[col];
      if (cellId && !cellIdsToDelete.has(cellId)) {
        return cellId;
      }
    }
    return null;
  }
  /**
   * Deletes cells by adjusting partial cells and removing fully covered cells.
   *
   * @param {(RTETableCellBlock | RTETableHeadCellBlock)[]} coveredCells - Cells that should be fully removed.
   * @param {AdjustmentCellInfo[]} partialAdjustments - Cells that need colspan reduction.
   * @param {RTETableBlock} table - The table block.
   */
  deleteCells(coveredCells, partialAdjustments, table) {
    coveredCells.forEach((cell) => {
      cell.parent.apply(new RemoveChildrenAction(cell));
    });
    const adjustedCellIds = new Set();
    partialAdjustments.forEach(({ cell, nextColSpan, rowSpan }) => {
      adjustedCellIds.add(cell.id);
      setSpanAttributes(cell, nextColSpan, rowSpan, table);
    });
    this.normalizeRemainingCellSpans(table, adjustedCellIds);
  }
  /**
   * Re-apply span attributes after deletion so remaining cells are normalized
   * against the new table width. When colspan equals table column count, don't set rowspan.
   *
   * @param {RTETableBlock} table - The table block.
   * @param {Set<string>} skipCellIds - The table skip cell`s id.
   */
  normalizeRemainingCellSpans(table, skipCellIds) {
    const remainingRows = getAllTableRows(table);
    remainingRows.forEach((row) => {
      row.children.forEach((cell) => {
        const { colSpan, rowSpan } = getCellSpan(cell);
        if (skipCellIds.has(cell.id)) {
          const tableColumnCount = getTableColumnCount(table);
          const needsFinalNormalization = rowSpan > 1 && colSpan === tableColumnCount;
          if (!needsFinalNormalization) {
            return;
          }
        }
        setSpanAttributes(cell, colSpan, rowSpan, table);
      });
    });
  }
  /**
   * Get overlap count of two logical column ranges.
   *
   * @param {number} cellStartCol - Logical start column of the cell.
   * @param {number} cellColSpan - Colspan value of the cell.
   * @param {number} deleteStartCol - Start logical column of delete range.
   * @param {number} deleteEndCol - End logical column of delete range.
   * @returns {number} The number of overlapping logical columns.
   */
  getColumnOverlapCount(cellStartCol, cellColSpan, deleteStartCol, deleteEndCol) {
    const cellEndCol = cellStartCol + cellColSpan - 1;
    const overlapStart = Math.max(cellStartCol, deleteStartCol);
    const overlapEnd = Math.min(cellEndCol, deleteEndCol);
    return overlapStart <= overlapEnd ? overlapEnd - overlapStart + 1 : 0;
  }
}
