import { RTETableRowBlock } from '../dto/table-row-block';
import { RTETableBlock } from '../dto/table-block';
import { InsertChildrenAfterAction } from '../../../dto/actions/insert-children-after-block';
import { getCellSpan, getAllTableRows, getActualColumnPosition, getParentBlockByType, setSpanAttributes, createNewCell, } from '../utils/utils';
import { generateUUID } from '../../../../../utils/misc';
import { ActionHandleStrategy } from '../../../dto/action-handle-strategies/action-handle-strategy.abstract';
/**
 * Strategy to handle horizontal split of a table cell (splits top/bottom).
 */
export class SplitCellHorizontallyStrategy extends ActionHandleStrategy {
  /**
   * @inheritDoc
   */
  handleAction(_action, target) {
    const row = target.parent;
    const section = row.parent;
    const table = getParentBlockByType(row, RTETableBlock);
    const allRows = getAllTableRows(table);
    const currentRowIndex = allRows.indexOf(row);
    const { colSpan, rowSpan } = getCellSpan(target);
    const cellColumnPosition = getActualColumnPosition(target, allRows);
    // When rowSpan=1, we need to insert a new row and both cells have rowSpan=1
    // When rowSpan>1, we split the existing rowSpan
    const topRowSpan = Math.ceil(rowSpan / 2);
    const bottomRowSpan = Math.max(1, rowSpan - topRowSpan);
    const needsNewRow = rowSpan === 1;
    // Update original cell's rowSpan (top cell)
    setSpanAttributes(target, colSpan, topRowSpan, table);
    // Determine where the bottom cell should be inserted
    const bottomCellRowIndex = currentRowIndex + topRowSpan;
    // Create the bottom cell with the same col span and type as the original
    const bottomCell = createNewCell(target.type);
    setSpanAttributes(bottomCell, colSpan, bottomRowSpan, table);
    if (needsNewRow) {
      // Need to create a new row for the bottom cell
      const newRow = this.createNewRowWithCell(section, bottomCell);
      // Find the correct position in the section to insert the new row
      const rowIndexInSection = section.children.indexOf(row);
      section.apply(new InsertChildrenAfterAction(section.children[rowIndexInSection], newRow));
      // Update allRows
      allRows.splice(bottomCellRowIndex, 0, newRow);
      // Update all cells from all previous rows that span through the insertion point
      // (except the split cell itself)
      this.updateCellsSpanningThroughInsertionPoint(allRows, bottomCellRowIndex, target, table);
    }
    else {
      // Row exists - insert the bottom cell at the correct position
      const targetRow = allRows[bottomCellRowIndex];
      const insertIndex = this.findInsertPosition(targetRow, cellColumnPosition, allRows, bottomCellRowIndex);
      targetRow.children.splice(insertIndex, 0, bottomCell);
      bottomCell.parent = targetRow;
    }
  }
  /**
   * Find the correct insertion position for a cell at a given column position in a row.
   * Accounts for cells from previous rows with rowspan extending into the target row.
   *
   * @param {RTETableRowBlock} targetRow - The row to insert into.
   * @param {number} targetColumnPosition - The target column position.
   * @param {RTETableRowBlock[]} allRows - All rows in the table.
   * @param {number} rowIndex - The index of the target row.
   * @returns {number} The physical cell index to insert at.
   */
  findInsertPosition(targetRow, targetColumnPosition, allRows, rowIndex) {
    // Calculate columns occupied by cells from previous rows extending into this row
    let columnsOccupiedByPreviousRows = 0;
    for (let r = 0; r < rowIndex; r++) {
      const prevRow = allRows[r];
      for (const prevCell of prevRow.children) {
        const { colSpan, rowSpan } = getCellSpan(prevCell);
        // Check if this cell extends into the target row
        if (r + rowSpan > rowIndex) {
          const prevCellColumnPosition = getActualColumnPosition(prevCell, allRows);
          if (prevCellColumnPosition < targetColumnPosition) {
            columnsOccupiedByPreviousRows += colSpan;
          }
        }
      }
    }
    // Find the insertion position in the target row
    let currentColumn = columnsOccupiedByPreviousRows;
    for (let i = 0; i < targetRow.children.length; i++) {
      if (currentColumn >= targetColumnPosition) {
        return i;
      }
      const cell = targetRow.children[i];
      const { colSpan } = getCellSpan(cell);
      currentColumn += colSpan;
    }
    return targetRow.children.length;
  }
  /**
   * Create a new row with only the bottom cell.
   * Other columns will be covered by cells from the original row with increased rowSpan.
   *
   * @param {RTETableBodyBlock | RTETableHeadBlock} section - The section (thead or tbody).
   * @param {RTETableCellBlock | RTETableHeadCellBlock} bottomCell - The bottom cell to insert.
   * @returns {RTETableRowBlock} The newly created row.
   */
  createNewRowWithCell(section, bottomCell) {
    const rowTemplate = {
      id: generateUUID(),
      type: 'table-row',
      children: [],
    };
    const newRow = RTETableRowBlock.from(rowTemplate);
    newRow.parent = section;
    // Add the bottom cell to the row
    newRow.children.push(bottomCell);
    bottomCell.parent = newRow;
    return newRow;
  }
  /**
   * Update all cells from all previous rows that span through the newly inserted row.
   * When a new row is inserted at `insertionRowIndex`, any cell from a previous row
   * that has rowSpan extending to or beyond that position needs rowSpan increased by 1.
   *
   * @param {RTETableRowBlock[]} allRows - All rows in the table (after insertion).
   * @param {number} insertionRowIndex - The index where the new row was inserted.
   * @param {RTETableCellBlock | RTETableHeadCellBlock} splitCell - The cell that was split (skip this one).
   * @param {RTETableBlock} table - The table block.
   */
  updateCellsSpanningThroughInsertionPoint(allRows, insertionRowIndex, splitCell, table) {
    // Check all rows before the insertion point (not including the inserted row itself)
    for (let r = 0; r < insertionRowIndex; r++) {
      const row = allRows[r];
      for (const cell of row.children) {
        // Skip the cell that was split - it already has the correct rowSpan
        if (cell === splitCell) {
          continue;
        }
        const { colSpan, rowSpan } = getCellSpan(cell);
        // Calculate where this cell ends
        const cellRowEnd = r + rowSpan;
        // If this cell extends to or past where we inserted the row,
        // it needs to span over the new row too
        if (cellRowEnd >= insertionRowIndex) {
          setSpanAttributes(cell, colSpan, rowSpan + 1, table);
        }
      }
    }
  }
}
