import { Fragment, Slice } from "./../../../tiptap/pm/model.js";
import { NodeSelection, Selection, SelectionRange, TextSelection } from "./../../../tiptap/pm/state.js";
import { Decoration, DecorationSet } from "./../../../tiptap/pm/view.js";
import { DATA_CELL_SELECTED } from "../../data-hooks.js";
import { TableMap } from "../tablemap.js";
import styles from "./table-scss.js";
function drawCellSelection(state) {
  const selection = state.selection;
  if (!selection.$headCell && !(selection instanceof RicosCellSelection)) {
    return null;
  }
  const cells = [];
  let isSingleCellSelection = false;
  if (selection instanceof RicosCellSelection) {
    isSingleCellSelection = selection.isSingleCell();
  } else if (selection.$anchorCell && selection.$headCell) {
    isSingleCellSelection = selection.$anchorCell.pos === selection.$headCell.pos;
  }
  selection.forEachCell((node, pos) => {
    cells.push(
      Decoration.node(pos, pos + node.nodeSize, {
        class: isSingleCellSelection ? styles.selectedCell : styles.selectedCells,
        [DATA_CELL_SELECTED]: "true"
      })
    );
  });
  return DecorationSet.create(state.doc, cells);
}
function isCellBoundarySelection({ $from, $to }) {
  if ($from.pos === $to.pos || $from.pos < $from.pos - 6) {
    return false;
  }
  let afterFrom = $from.pos, beforeTo = $to.pos, depth = $from.depth;
  for (; depth >= 0; depth--, afterFrom++) {
    if ($from.after(depth + 1) < $from.end(depth)) {
      break;
    }
  }
  for (let d = $to.depth; d >= 0; d--, beforeTo--) {
    if ($to.before(d + 1) > $to.start(d)) {
      break;
    }
  }
  return afterFrom === beforeTo && /row|table/.test($from.node(depth).type.spec.tableRole);
}
function isTextSelectionAcrossCells({ $from, $to }) {
  let fromCellBoundaryNode;
  let toCellBoundaryNode;
  for (let i = $from.depth; i > 0; i--) {
    const node = $from.node(i);
    if (node.type.spec.tableRole === "cell" || node.type.spec.tableRole === "header_cell") {
      fromCellBoundaryNode = node;
      break;
    }
  }
  for (let i = $to.depth; i > 0; i--) {
    const node = $to.node(i);
    if (node.type.spec.tableRole === "cell" || node.type.spec.tableRole === "header_cell") {
      toCellBoundaryNode = node;
      break;
    }
  }
  return fromCellBoundaryNode !== toCellBoundaryNode && $to.parentOffset === 0;
}
function normalizeSelection(state, tr, allowTableNodeSelection = false) {
  const sel = (tr || state).selection;
  const doc = (tr || state).doc;
  let normalize, role;
  if (sel instanceof NodeSelection && (role = sel.node.type.spec.tableRole)) {
    if (role === "cell" || role === "header_cell") {
      normalize = RicosCellSelection.create(doc, sel.from);
    } else if (role === "row") {
      const $cell = doc.resolve(sel.from + 1);
      const table = $cell.node(-1);
      const tableStart = $cell.start(-1);
      const map = TableMap.get(table);
      const rect = { left: 0, right: map.width, top: $cell.index(-1), bottom: $cell.index(-1) + 1 };
      const cellPositions = map.cellsInRect(rect);
      const cells = cellPositions.map((pos) => doc.resolve(tableStart + pos));
      normalize = new RicosCellSelection(cells, doc);
    } else if (!allowTableNodeSelection) {
      const map = TableMap.get(sel.node);
      const start = sel.from + 1;
      const firstCell = doc.resolve(start + 1);
      const tableStart = firstCell.start(-1);
      const rect = { left: 0, right: map.width, top: 0, bottom: map.height };
      const cellPositions = map.cellsInRect(rect);
      const cells = cellPositions.map((pos) => doc.resolve(tableStart + pos));
      normalize = new RicosCellSelection(cells, doc);
    }
  } else if (sel instanceof TextSelection && isCellBoundarySelection(sel)) {
    normalize = TextSelection.create(doc, sel.from);
  } else if (sel instanceof TextSelection && isTextSelectionAcrossCells(sel)) {
    normalize = TextSelection.create(doc, sel.$from.start(), sel.$from.end());
  }
  if (normalize) {
    (tr || (tr = state.tr)).setSelection(normalize);
  }
  return tr;
}
function pointsAtCell($pos) {
  return $pos.parent.type.spec.tableRole === "row" && ($pos.nodeAfter?.type.spec.tableRole === "cell" || $pos.nodeAfter?.type.spec.tableRole === "header_cell");
}
function inSameTable($a, $b) {
  return $a.depth === $b.depth && $a.pos >= $b.start(-1) && $a.pos <= $b.end(-1);
}
class RicosCellSelection extends Selection {
  constructor(cells, doc) {
    if (cells.length === 0) {
      throw new Error("RicosCellSelection requires at least one cell");
    }
    const table = cells[0].node(-1);
    const tableStart = cells[0].start(-1);
    const resolvedDoc = doc || cells[0].node(0);
    const ranges = cells.map((cellPos) => {
      const pos = cellPos.pos - tableStart;
      const cell = table.nodeAt(pos);
      if (!cell) {
        throw RangeError(`No cell with offset ${pos} found`);
      }
      const from = tableStart + pos + 1;
      return new SelectionRange(
        resolvedDoc.resolve(from),
        resolvedDoc.resolve(from + cell.content.size)
      );
    });
    super(ranges[0].$from, ranges[0].$to, ranges);
    this.cellPositions = new Set(cells.map((cellPos) => cellPos.pos));
    this.$anchorCell = cells[0];
    this.$headCell = cells[cells.length - 1];
  }
  map(doc, mapping) {
    const mappedCells = [];
    for (const pos of this.cellPositions) {
      const mappedPos = mapping.map(pos);
      const $pos = doc.resolve(mappedPos);
      if (pointsAtCell($pos)) {
        mappedCells.push($pos);
      }
    }
    if (mappedCells.length === 0) {
      const $anchor = doc.resolve(mapping.map(this.$anchorCell.pos));
      return TextSelection.between($anchor, $anchor);
    }
    const firstCell = mappedCells[0];
    const allInSameTable = mappedCells.every((cell) => inSameTable(firstCell, cell));
    if (!allInSameTable) {
      return TextSelection.between(firstCell, firstCell);
    }
    return new RicosCellSelection(mappedCells, doc);
  }
  content() {
    const table = this.$anchorCell.node(-1);
    const map = TableMap.get(table);
    const rect = this.getRect();
    const rows = [];
    for (let row = rect.top; row < rect.bottom; row++) {
      const rowContent = [];
      const rowNode = table.child(row);
      for (let col = rect.left; col < rect.right; col++) {
        const relativePos = map.map[row * map.width + col];
        const placement = map.findCell(relativePos);
        if (placement.row === row && placement.col === col) {
          const cell = table.nodeAt(relativePos);
          if (cell) {
            rowContent.push(cell);
          }
        }
      }
      rows.push(rowNode.type.create(rowNode.attrs, Fragment.from(rowContent)));
    }
    const newTable = table.type.create(table.attrs, Fragment.from(rows));
    return new Slice(Fragment.from(newTable), 1, 1);
  }
  replace(tr, content = Slice.empty) {
    if (content === Slice.empty || content.size === 0) {
      const table = this.$anchorCell.node(-1);
      const tableStart = this.$anchorCell.start(-1);
      const map = TableMap.get(table);
      const totalCells = map.cellsInRect({
        left: 0,
        right: map.width,
        top: 0,
        bottom: map.height
      }).length;
      if (this.cellPositions.size === totalCells) {
        const tablePos = tableStart - 1;
        tr.delete(tablePos, tablePos + table.nodeSize);
        const sel2 = Selection.findFrom(tr.doc.resolve(tablePos), 1);
        if (sel2) {
          tr.setSelection(sel2);
        }
        return;
      }
    }
    const mapFrom = tr.steps.length;
    const ranges = this.ranges;
    for (let i = 0; i < ranges.length; i++) {
      const { $from, $to } = ranges[i];
      const mapping = tr.mapping.slice(mapFrom);
      tr.replace(mapping.map($from.pos), mapping.map($to.pos), i ? Slice.empty : content);
    }
    const sel = Selection.findFrom(tr.doc.resolve(tr.mapping.slice(mapFrom).map(this.to)), -1);
    if (sel) {
      tr.setSelection(sel);
    }
  }
  replaceWith(tr, node) {
    this.replace(tr, new Slice(Fragment.from(node), 0, 0));
  }
  forEachCell(f) {
    const table = this.$anchorCell.node(-1);
    const tableStart = this.$anchorCell.start(-1);
    for (const cellPos of this.cellPositions) {
      const relativePos = cellPos - tableStart;
      const cell = table.nodeAt(relativePos);
      if (cell) {
        f(cell, cellPos);
      }
    }
  }
  eq(other) {
    if (!(other instanceof RicosCellSelection)) {
      return false;
    }
    if (this.cellPositions.size !== other.cellPositions.size) {
      return false;
    }
    for (const pos of this.cellPositions) {
      if (!other.cellPositions.has(pos)) {
        return false;
      }
    }
    return true;
  }
  toJSON() {
    const positions = Array.from(this.cellPositions).sort((a, b) => a - b);
    return {
      type: "cell",
      anchor: positions[0],
      head: positions[positions.length - 1]
    };
  }
  get jsonID() {
    return "cell";
  }
  getSelectionIndexes() {
    const table = this.$anchorCell.node(-1);
    const map = TableMap.get(table);
    const tableStart = this.$anchorCell.start(-1);
    const selectedRowIndexes = /* @__PURE__ */ new Set();
    const selectedColumnIndexes = /* @__PURE__ */ new Set();
    for (const cellPos of this.cellPositions) {
      const relativePos = cellPos - tableStart;
      const placement = map.findCell(relativePos);
      for (let rowIndex = placement.row; rowIndex < placement.row + placement.rowspan; rowIndex += 1) {
        selectedRowIndexes.add(rowIndex);
      }
      for (let columnIndex = placement.col; columnIndex < placement.col + placement.colspan; columnIndex += 1) {
        selectedColumnIndexes.add(columnIndex);
      }
    }
    return {
      map,
      tableStart,
      selectedRowIndexes,
      selectedColumnIndexes
    };
  }
  isColSelection() {
    const { map, tableStart, selectedColumnIndexes } = this.getSelectionIndexes();
    const selectedCellPositions = this.cellPositions;
    if (selectedColumnIndexes.size === 0) {
      return false;
    }
    for (const columnIndex of selectedColumnIndexes) {
      const columnCells = map.cellsInRect({
        left: columnIndex,
        right: columnIndex + 1,
        top: 0,
        bottom: map.height
      });
      const isColumnComplete = columnCells.every((cellPos) => {
        return selectedCellPositions.has(tableStart + cellPos);
      });
      if (!isColumnComplete) {
        return false;
      }
    }
    return true;
  }
  isRowSelection() {
    const { map, tableStart, selectedRowIndexes } = this.getSelectionIndexes();
    const selectedCellPositions = this.cellPositions;
    if (selectedRowIndexes.size === 0) {
      return false;
    }
    for (const rowIndex of selectedRowIndexes) {
      const rowCells = map.cellsInRect({
        left: 0,
        right: map.width,
        top: rowIndex,
        bottom: rowIndex + 1
      });
      const isRowComplete = rowCells.every((cellPos) => {
        return selectedCellPositions.has(tableStart + cellPos);
      });
      if (!isRowComplete) {
        return false;
      }
    }
    return true;
  }
  isTableSelection() {
    const { map, tableStart } = this.getSelectionIndexes();
    const selectedCellPositions = this.cellPositions;
    const tableCells = map.cellsInRect({
      left: 0,
      right: map.width,
      top: 0,
      bottom: map.height
    });
    return tableCells.every((cellPos) => {
      return selectedCellPositions.has(tableStart + cellPos);
    });
  }
  getRect() {
    const cells = [];
    const doc = this.$anchorCell.node(0);
    for (const cellPos of this.cellPositions) {
      cells.push(doc.resolve(cellPos));
    }
    return getRectBetweenCells(cells);
  }
  static create(doc, ...cellPositions) {
    if (cellPositions.length === 0) {
      throw new Error("At least one cell position is required");
    }
    const cells = cellPositions.map((pos) => {
      const $pos = doc.resolve(pos);
      if (!pointsAtCell($pos)) {
        throw new Error(`Position ${pos} does not point at a cell`);
      }
      return $pos;
    });
    return new RicosCellSelection(cells, doc);
  }
  // Toggle a cell in/out of the selection
  static toggleCell(currentSelection, cellPos, doc) {
    const $cell = doc.resolve(cellPos);
    if (!pointsAtCell($cell)) {
      throw new Error(`Position ${cellPos} does not point at a cell`);
    }
    if (currentSelection instanceof RicosCellSelection) {
      const newPositions = new Set(currentSelection.cellPositions);
      if (newPositions.has(cellPos)) {
        newPositions.delete(cellPos);
        if (newPositions.size === 0) {
          return new RicosCellSelection([$cell], doc);
        }
      } else {
        newPositions.add(cellPos);
      }
      const cells = Array.from(newPositions).map((pos) => doc.resolve(pos));
      return new RicosCellSelection(cells, doc);
    }
    return new RicosCellSelection([$cell], doc);
  }
  // Get count of selected cells
  getCellCount() {
    return this.cellPositions.size;
  }
  // Check if this is a single cell selection
  isSingleCell() {
    return this.cellPositions.size === 1;
  }
}
RicosCellSelection.prototype.visible = false;
const getRectBetweenCells = (cells) => {
  if (cells.length === 0) {
    throw new Error("getRectBetweenCells requires at least one cell");
  }
  const table = cells[0].node(-1);
  const tableStart = cells[0].start(-1);
  const map = TableMap.get(table);
  let bounds = null;
  for (const cell of cells) {
    const relativePos = cell.pos - tableStart;
    const p = map.findCell(relativePos);
    if (!bounds) {
      bounds = { top: p.row, left: p.col, bottom: p.row + p.rowspan, right: p.col + p.colspan };
    } else {
      bounds.top = Math.min(bounds.top, p.row);
      bounds.left = Math.min(bounds.left, p.col);
      bounds.bottom = Math.max(bounds.bottom, p.row + p.rowspan);
      bounds.right = Math.max(bounds.right, p.col + p.colspan);
    }
  }
  if (!bounds) {
    throw new Error("Failed to compute bounds");
  }
  const visited = /* @__PURE__ */ new Set();
  const queue = [];
  for (let r = bounds.top; r < bounds.bottom; r++) {
    for (let c = bounds.left; c < bounds.right; c++) {
      const pos = map.map[r * map.width + c];
      if (!visited.has(pos)) {
        visited.add(pos);
        queue.push(pos);
      }
    }
  }
  while (queue.length) {
    const pos = queue.shift();
    const p = map.findCell(pos);
    const newTop = Math.min(bounds.top, p.row);
    const newLeft = Math.min(bounds.left, p.col);
    const newBottom = Math.max(bounds.bottom, p.row + p.rowspan);
    const newRight = Math.max(bounds.right, p.col + p.colspan);
    const expanded = newTop < bounds.top || newLeft < bounds.left || newBottom > bounds.bottom || newRight > bounds.right;
    if (!expanded) {
      continue;
    }
    bounds = { top: newTop, left: newLeft, bottom: newBottom, right: newRight };
    for (let r = bounds.top; r < bounds.bottom; r++) {
      for (let c = bounds.left; c < bounds.right; c++) {
        const p2 = map.map[r * map.width + c];
        if (!visited.has(p2)) {
          visited.add(p2);
          queue.push(p2);
        }
      }
    }
    const isFullTable = bounds.top === 0 && bounds.left === 0 && bounds.bottom === map.height && bounds.right === map.width;
    if (isFullTable) {
      return bounds;
    }
  }
  return bounds;
};
const getCellsInExpandedRect = (doc, tableStart, map, initialRect) => {
  const initialCellPositions = map.cellsOverlappingRect(initialRect);
  const initialCells = initialCellPositions.map((pos) => doc.resolve(tableStart + pos));
  const expandedRect = getRectBetweenCells(initialCells);
  const cellPositions = map.cellsOverlappingRect(expandedRect);
  return cellPositions.map((pos) => doc.resolve(tableStart + pos));
};
export {
  RicosCellSelection,
  drawCellSelection,
  getCellsInExpandedRect,
  getRectBetweenCells,
  inSameTable,
  normalizeSelection
};
//# sourceMappingURL=cellselection.js.map
