import { RTETableBlock } from '../dto/table-block';
import { RTETableCellBlock } from '../dto/table-cell-block';
import { RTETableBodyBlock } from '../dto/table-body-block';
import { RTETableHeadBlock } from '../dto/table-head-block';
import { RTETableHeadCellBlock } from '../dto/table-head-cell-block';
import { ReplaceTableCellAction } from '../actions/replace-table-cell-action';
import { SplitTableCellAction } from '../actions/split-table-cell-action';
import { TableColumnStrategy } from './table-column-strategy.abstract';
import { getAllTableRows, buildTableGrid, getParentBlockByType, findFirstBodyColIndex, getSelectedCellsBoundary, } from '../utils/utils';
import { UpdateCursorPositionAction } from '../../../dto/actions/update-cursor-position-action';
/**
 * Strategy to handle the toggle of header state of a table column.
 */
export class ToggleHeaderTableColumnStrategy extends TableColumnStrategy {
  /**
   * Check if all cells in the column containing the selected cell are head cells.
   *
   * @param {(RTETableCellBlock | RTETableHeadCellBlock)[]} selectedCellRange - The range of selected table cell blocks.
   * @returns {boolean} True if all cells in the column containing the selected cell are head cells, false otherwise.
   */
  static isFullHeaderSelection(selectedCellRange) {
    if (selectedCellRange.length === 0) {
      return false;
    }
    const firstSelectedCell = selectedCellRange[0];
    const table = getParentBlockByType(firstSelectedCell, RTETableBlock);
    if (table) {
      const allTableRows = getAllTableRows(table);
      const tableGrid = buildTableGrid(allTableRows);
      const tableCellsById = new Map();
      allTableRows.forEach((row) => {
        row.children.forEach((cell) => tableCellsById.set(cell.id, cell));
      });
      const [minCol, maxCol] = getSelectedCellsBoundary(selectedCellRange, table, allTableRows, tableGrid);
      for (const gridRow of tableGrid) {
        for (let col = minCol; col <= maxCol; col++) {
          const cell = tableCellsById.get(gridRow[col]);
          if (cell && !(cell instanceof RTETableHeadCellBlock)) {
            return false;
          }
        }
      }
      return true;
    }
    return false;
  }
  /**
   * @inheritDoc
   */
  handleAction(action, target) {
    const headerOn = action.headerOn;
    const allTableRows = getAllTableRows(target);
    const tableGrid = buildTableGrid(allTableRows);
    const [minSelectCellCol, maxSelectCellCol] = this.getBoundaryCellColumnRange(action.tableCellHeaderRange, allTableRows, tableGrid);
    const shouldSplitHeadCells = headerOn || (!headerOn && minSelectCellCol > 0);
    if (shouldSplitHeadCells) {
      this.toggleHeadRows(target, allTableRows, tableGrid, headerOn, maxSelectCellCol, minSelectCellCol);
    }
    this.toggleBodyRows(target, allTableRows, tableGrid, headerOn, minSelectCellCol, maxSelectCellCol);
    const root = this.getRootContent(target);
    if (root && action.tableCellHeaderRange[0].parent) {
      const focusNode = action.tableCellHeaderRange[0].parent.getFirstNode();
      focusNode && root.apply(new UpdateCursorPositionAction(focusNode, 0));
    }
  }
  /**
   * Toggle header cells within existing table-head rows.
   *
   * @param {RTETableBlock} table - The target table block.
   * @param {RTETableRowBlock[]} allTableRows - All table rows in order.
   * @param {string[][]} tableGrid - Table grid representation.
   * @param {boolean} headerOn - Whether header mode is being enabled.
   * @param {number} maxSelectCellCol - Maximum selected logical column.
   * @param {number} minSelectCellCol - Minimum selected logical column.
   */
  toggleHeadRows(table, allTableRows, tableGrid, headerOn, maxSelectCellCol, minSelectCellCol) {
    var _a;
    const allTableHeadRows = ((_a = table.children.find((head) => head instanceof RTETableHeadBlock)) === null || _a === void 0 ? void 0 : _a.children) || [];
    const headRowLength = allTableHeadRows.length;
    for (let rowIndex = 0; rowIndex < headRowLength; rowIndex++) {
      const row = allTableRows[rowIndex];
      const endColIndex = headerOn ? maxSelectCellCol : minSelectCellCol - 1;
      this.toggleCellType(table, row, tableGrid[rowIndex], 0, endColIndex, () => true, (cell) => RTETableHeadCellBlock.from(cell.toJSON()));
    }
  }
  /**
   * Toggle header cells within body rows.
   *
   * @param {RTETableBlock} table - The target table block.
   * @param {RTETableRowBlock[]} allTableRows - All table rows in order.
   * @param {string[][]} tableGrid - Table grid representation.
   * @param {boolean} headerOn - Whether header mode is being enabled.
   * @param {number} minSelectCellCol - Minimum selected logical column.
   * @param {number} maxSelectCellCol - Maximum selected logical column.
   */
  toggleBodyRows(table, allTableRows, tableGrid, headerOn, minSelectCellCol, maxSelectCellCol) {
    var _a;
    const tableColLength = tableGrid[0].length;
    const allTableBodyRows = ((_a = table.children.find((body) => body instanceof RTETableBodyBlock)) === null || _a === void 0 ? void 0 : _a.children) || [];
    const headRowLength = allTableRows.length - allTableBodyRows.length;
    for (let i = 0; i < allTableBodyRows.length; i++) {
      const rowIndex = i + headRowLength;
      const row = allTableRows[rowIndex];
      const gridRow = tableGrid[rowIndex];
      const startColIndex = findFirstBodyColIndex(gridRow, row, tableColLength);
      if (headerOn) {
        if (startColIndex > -1) {
          this.toggleCellType(table, row, gridRow, startColIndex, maxSelectCellCol, (cell) => !(cell instanceof RTETableHeadCellBlock), (cell) => RTETableHeadCellBlock.from(cell.toJSON()));
        }
        continue;
      }
      const lastHeaderCol = startColIndex === -1 ? tableColLength - 1 : startColIndex - 1;
      this.toggleCellType(table, row, gridRow, minSelectCellCol, lastHeaderCol, (cell) => cell instanceof RTETableHeadCellBlock, (cell) => RTETableCellBlock.from(cell.toJSON()));
    }
  }
  /**
   * Toggle cell types in a row within a logical column range.
   *
   * @param {RTETableBlock} table - The table block containing the target row.
   * @param {RTETableRowBlock} row - The table row block.
   * @param {string[]} gridRow - The grid row array.
   * @param {number} startCol - Start logical column.
   * @param {number} endCol - End logical column.
   * @param {(cell: RTETableCellBlock | RTETableHeadCellBlock) => boolean} shouldConvert - Predicate to determine if a cell should be converted.
   * @param {(cell: RTETableCellBlock | RTETableHeadCellBlock) => RTETableCellBlock | RTETableHeadCellBlock} convert - Function to convert the cell.
   */
  toggleCellType(table, row, gridRow, startCol, endCol, shouldConvert, convert) {
    for (let col = startCol; col <= endCol; col++) {
      const cellId = gridRow[col];
      const cell = row.children.find((c) => c.id === cellId);
      if (cell && shouldConvert(cell)) {
        const cellStartCol = gridRow.indexOf(cellId);
        const targetCellType = convert(cell).type;
        const payload = {
          table,
          cell,
          col,
          cellStartCol,
          endCol,
          targetCellType,
        };
        const cellEndCol = cellStartCol + cell.colSpan - 1;
        const shouldSplit = cellEndCol > endCol || cellStartCol < col;
        if (shouldSplit) {
          row.apply(new SplitTableCellAction(payload));
          if (cellEndCol > endCol) {
            return;
          }
          col = cellEndCol;
          continue;
        }
        row.apply(new ReplaceTableCellAction(payload));
      }
      if (cell) {
        col += cell.colSpan - 1;
      }
    }
  }
}
