import { getElementScale } from "./../../../_shared/get-element-scale.js";
import { Plugin, PluginKey } from "./../../../tiptap/pm/state.js";
import styles from "../../statics/styles/resizer-scss.js";
import { cellAround } from "../commands/queries.js";
import { TableMap } from "../tablemap.js";
import { findTable } from "../utilities/find-table.js";
import { getTableElement } from "./resize-utils.js";
import { isColumnResizingActive, isRowResizingActive } from "./resizing-plugin-keys.js";
const handleDraggingPluginKey = new PluginKey(
  "handleDragging"
);
const isHandleDraggingActive = (state) => {
  const pluginState = handleDraggingPluginKey.getState(state);
  return pluginState?.type === "drag";
};
const DRAG_THRESHOLD = 3;
const SCROLL_EDGE_SIZE = 50;
const SCROLL_SPEED = 3;
const Z_INDEX_DROP_ZONE = 1e4;
const Z_INDEX_GHOST = 9999;
const getColumnBoundaries = (tableElement) => {
  const cells = tableElement.querySelector("tr")?.querySelectorAll("td, th");
  if (!cells) {
    return [];
  }
  const tableRect = tableElement.getBoundingClientRect();
  const boundaries = [{ index: 0, pos: tableRect.left }];
  let colIndex = 0;
  cells.forEach((cell) => {
    const cellRect = cell.getBoundingClientRect();
    const colspan = parseInt(cell.getAttribute("colspan") || "1", 10);
    for (let c = 0; c < colspan; c++) {
      colIndex++;
      boundaries.push({
        index: colIndex,
        pos: cellRect.left + (c + 1) * cellRect.width / colspan
      });
    }
  });
  return boundaries;
};
const getRowBoundaries = (tableElement) => {
  const rows = tableElement.querySelector("tbody")?.querySelectorAll("tr");
  if (!rows) {
    return [];
  }
  const boundaries = [];
  rows.forEach((row, index) => {
    const rowRect = row.getBoundingClientRect();
    if (index === 0) {
      boundaries.push({ index: 0, pos: rowRect.top });
    }
    boundaries.push({ index: index + 1, pos: rowRect.bottom });
  });
  return boundaries;
};
const findClosestDropBoundary = (boundaries, mousePos, fromStart, fromEnd, map, isColumn) => {
  let closestIndex = fromStart;
  let closestPos = 0;
  let minDist = Infinity;
  for (const b of boundaries) {
    if (b.index > fromStart && b.index <= fromEnd + 1) {
      continue;
    }
    if (!isBoundaryValid(map, b.index, isColumn)) {
      continue;
    }
    const dist = Math.abs(mousePos - b.pos);
    if (dist < minDist) {
      minDist = dist;
      closestIndex = b.index;
      closestPos = b.pos;
    }
  }
  return { closestIndex, closestPos };
};
const findScrollContainer = (element) => {
  let current = element.parentElement;
  while (current) {
    const { overflowY, overflowX } = getComputedStyle(current);
    const canScrollY = current.scrollHeight > current.clientHeight;
    const canScrollX = current.scrollWidth > current.clientWidth;
    const hasScrollableOverflow = overflowY === "auto" || overflowY === "scroll" || overflowX === "auto" || overflowX === "scroll";
    if (hasScrollableOverflow && (canScrollY || canScrollX)) {
      return current;
    }
    current = current.parentElement;
  }
  return null;
};
const stopAutoScroll = (state) => {
  if (state.autoScrollRAF !== null) {
    cancelAnimationFrame(state.autoScrollRAF);
    state.autoScrollRAF = null;
  }
};
const isBoundaryValid = (map, boundaryIndex, isColumn) => {
  const size = isColumn ? map.width : map.height;
  const crossSize = isColumn ? map.height : map.width;
  if (boundaryIndex === 0 || boundaryIndex === size) {
    return true;
  }
  for (let i = 0; i < crossSize; i++) {
    const mapIndex = isColumn ? i * map.width + (boundaryIndex - 1) : (boundaryIndex - 1) * map.width + i;
    const placement = map.findCell(map.map[mapIndex]);
    const cellEnd = isColumn ? placement.col + placement.colspan : placement.row + placement.rowspan;
    if (cellEnd > boundaryIndex) {
      return false;
    }
  }
  return true;
};
const updateDropZonePosition = (state, tableElement, map) => {
  const { dragType, fromStart, fromEnd, currentX, currentY } = state.currentDragState;
  const isColumn = dragType === "column";
  const mousePos = isColumn ? currentX : currentY;
  const boundaries = isColumn ? getColumnBoundaries(tableElement) : getRowBoundaries(tableElement);
  if (boundaries.length === 0) {
    return;
  }
  const { closestIndex, closestPos } = findClosestDropBoundary(
    boundaries,
    mousePos,
    fromStart,
    fromEnd,
    map,
    isColumn
  );
  const isValidDrop = closestIndex < fromStart || closestIndex > fromEnd + 1;
  state.dropZoneElement.style.display = isValidDrop ? "block" : "none";
  if (isValidDrop) {
    state.dropZoneElement.style[isColumn ? "left" : "top"] = `${closestPos}px`;
  }
  state.currentDragState = { ...state.currentDragState, dropIndex: closestIndex };
};
const updateDragVisualsPosition = (state) => {
  const { startX, startY, scale, dragType, currentX, currentY } = state.currentDragState;
  const deltaX = (currentX - startX) / scale.scaleX + state.scrollOffsetX;
  const deltaY = (currentY - startY) / scale.scaleY + state.scrollOffsetY;
  state.handleElement.style.transform = dragType === "column" ? `translate(calc(-50% + ${deltaX}px), -50%)` : `translate(-50%, calc(-50% + ${deltaY}px))`;
  const delta = dragType === "column" ? currentX - startX : currentY - startY;
  const prop = dragType === "column" ? "left" : "top";
  const original = parseFloat(
    state.ghostElement.dataset[dragType === "column" ? "startLeft" : "startTop"] || "0"
  );
  state.ghostElement.style[prop] = `${original + delta}px`;
};
const updateDropIndicator = (state, dropIndex, dropPosition, fromStart, fromEnd, dragType) => {
  const isValidDrop = dropIndex < fromStart || dropIndex > fromEnd + 1;
  state.dropZoneElement.style.display = isValidDrop ? "block" : "none";
  if (isValidDrop) {
    state.dropZoneElement.style[dragType === "column" ? "left" : "top"] = `${dropPosition}px`;
  }
};
const performAutoScrollIfNeeded = (state, tableElement, map, event) => {
  if (!state.scrollContainer) {
    return;
  }
  const { dragType } = state.currentDragState;
  const containerRect = state.scrollContainer.getBoundingClientRect();
  const tableRect = tableElement.getBoundingClientRect();
  let scrollX = 0;
  let scrollY = 0;
  if (dragType === "column") {
    const canScrollLeft = tableRect.left < containerRect.left;
    const canScrollRight = tableRect.right > containerRect.right;
    if (event.clientX < containerRect.left + SCROLL_EDGE_SIZE && canScrollLeft) {
      scrollX = -SCROLL_SPEED;
    } else if (event.clientX > containerRect.right - SCROLL_EDGE_SIZE && canScrollRight) {
      scrollX = SCROLL_SPEED;
    }
  }
  if (dragType === "row") {
    const canScrollUp = tableRect.top < containerRect.top;
    const canScrollDown = tableRect.bottom > containerRect.bottom;
    if (event.clientY < containerRect.top + SCROLL_EDGE_SIZE && canScrollUp) {
      scrollY = -SCROLL_SPEED;
    } else if (event.clientY > containerRect.bottom - SCROLL_EDGE_SIZE && canScrollDown) {
      scrollY = SCROLL_SPEED;
    }
  }
  if (scrollX === 0 && scrollY === 0) {
    stopAutoScroll(state);
    return;
  }
  const container = state.scrollContainer;
  const table = tableElement;
  const tableMap = map;
  const performScroll = () => {
    const currentTableRect = table.getBoundingClientRect();
    const currentContainerRect = container.getBoundingClientRect();
    const shouldScrollX = scrollX < 0 ? currentTableRect.left < currentContainerRect.left : currentTableRect.right > currentContainerRect.right;
    const shouldScrollY = scrollY < 0 ? currentTableRect.top < currentContainerRect.top : currentTableRect.bottom > currentContainerRect.bottom;
    if (scrollX !== 0 && shouldScrollX || scrollY !== 0 && shouldScrollY) {
      const actualScrollX = shouldScrollX ? scrollX : 0;
      const actualScrollY = shouldScrollY ? scrollY : 0;
      container.scrollBy(actualScrollX, actualScrollY);
      state.scrollOffsetX += actualScrollX;
      state.scrollOffsetY += actualScrollY;
      updateDropZonePosition(state, table, tableMap);
      updateDragVisualsPosition(state);
      state.autoScrollRAF = requestAnimationFrame(performScroll);
    } else {
      stopAutoScroll(state);
    }
  };
  stopAutoScroll(state);
  state.autoScrollRAF = requestAnimationFrame(performScroll);
};
const handleDraggingPlugin = ({ editor }) => {
  let dragState = { type: "idle" };
  let capturedElement = null;
  let capturedPointerId = null;
  let globalCleanup = null;
  const cleanup = () => {
    if (dragState.type === "dragging") {
      stopAutoScroll(dragState);
      dragState.dropZoneElement.remove();
      dragState.ghostElement.remove();
    }
    if (dragState.type !== "idle") {
      if (dragState.handleElement.isConnected) {
        dragState.handleElement.style.transform = dragState.originalHandleTransform;
        delete dragState.handleElement.dataset.dragging;
      }
    }
    dragState = { type: "idle" };
  };
  const releasePointerCapture = () => {
    if (capturedElement && capturedPointerId !== null) {
      try {
        capturedElement.releasePointerCapture(capturedPointerId);
      } catch {
      }
      capturedElement = null;
      capturedPointerId = null;
    }
  };
  return new Plugin({
    key: handleDraggingPluginKey,
    state: {
      init: () => ({ type: "idle" }),
      apply(tr, currentState) {
        const meta = tr.getMeta(handleDraggingPluginKey);
        return meta ?? currentState;
      }
    },
    props: {
      attributes(editorState) {
        const state = handleDraggingPluginKey.getState(editorState);
        if (!state || state.type === "idle") {
          return {};
        }
        return {
          class: state.dragType === "column" ? styles.reorderCursorCol : styles.reorderCursorRow
        };
      },
      handleDOMEvents: {
        pointerdown(view, event) {
          if (event.button !== 0) {
            return false;
          }
          if (isColumnResizingActive(view) || isRowResizingActive(view) || !editor.isEditable) {
            return false;
          }
          const target = event.target;
          const columnHandle = target.closest('[data-hook="column-handle"]');
          const rowHandle = target.closest('[data-hook="row-handle"]');
          const handle = columnHandle || rowHandle;
          if (!handle) {
            return false;
          }
          handle.dataset.dragging = "true";
          const table = findTable(view.state.selection);
          if (!table) {
            delete handle.dataset.dragging;
            return false;
          }
          const tableMap = TableMap.get(table.node);
          const tableStart = table.start;
          const isColumn = !!columnHandle;
          let cellRect;
          try {
            const { selection } = view.state;
            if ("getRect" in selection && typeof selection.getRect === "function") {
              cellRect = selection.getRect();
            } else {
              const $cell = cellAround(selection.$from);
              if (!$cell) {
                delete handle.dataset.dragging;
                return false;
              }
              const cellPos = $cell.pos - tableStart;
              const placement = tableMap.findCell(cellPos);
              cellRect = {
                left: placement.col,
                top: placement.row,
                right: placement.col + placement.colspan,
                bottom: placement.row + placement.rowspan
              };
            }
          } catch {
            delete handle.dataset.dragging;
            return false;
          }
          if (isColumn) {
            editor.commands.selectColumns(cellRect.left, cellRect.right - 1, tableStart);
          } else {
            editor.commands.selectRows(cellRect.top, cellRect.bottom - 1, tableStart);
          }
          const newSelection = editor.state.selection;
          let expandedRect;
          if ("getRect" in newSelection && typeof newSelection.getRect === "function") {
            expandedRect = newSelection.getRect();
          } else {
            delete handle.dataset.dragging;
            return false;
          }
          const fromStart = isColumn ? expandedRect.left : expandedRect.top;
          const fromEnd = isColumn ? expandedRect.right - 1 : expandedRect.bottom - 1;
          try {
            target.setPointerCapture(event.pointerId);
            capturedElement = target;
            capturedPointerId = event.pointerId;
          } catch {
          }
          const handleGlobalPointerUp = (e) => {
            if (e.pointerId === event.pointerId) {
              if (dragState.type !== "idle") {
                finishDrag(view, dragState.currentDragState, editor, cleanup);
              }
              cleanupGlobalListeners();
            }
          };
          const cleanupGlobalListeners = () => {
            document.removeEventListener("pointerup", handleGlobalPointerUp, true);
            document.removeEventListener("pointercancel", handleGlobalPointerUp, true);
            globalCleanup = null;
          };
          document.addEventListener("pointerup", handleGlobalPointerUp, true);
          document.addEventListener("pointercancel", handleGlobalPointerUp, true);
          globalCleanup = cleanupGlobalListeners;
          const scale = getElementScale(handle);
          const dragType = isColumn ? "column" : "row";
          const pmDragState = {
            type: "drag",
            dragType,
            tablePosition: tableStart,
            fromStart,
            fromEnd,
            dropIndex: fromStart,
            startX: event.clientX,
            startY: event.clientY,
            currentX: event.clientX,
            currentY: event.clientY,
            scale
          };
          const tableEl = getTableElement(view, tableStart);
          dragState = {
            type: "pending",
            handleElement: handle,
            originalHandleTransform: handle.style.transform,
            currentDragState: pmDragState,
            scrollContainer: tableEl ? findScrollContainer(tableEl) : null,
            scrollOffsetX: 0,
            scrollOffsetY: 0
          };
          view.dispatch(view.state.tr.setMeta(handleDraggingPluginKey, pmDragState));
          event.preventDefault();
          event.stopPropagation();
          return true;
        },
        pointermove(view, event) {
          if (isColumnResizingActive(view) || isRowResizingActive(view) || !editor.isEditable || dragState.type === "idle") {
            return false;
          }
          if (dragState.type === "pending") {
            const { startX, startY, dragType } = dragState.currentDragState;
            const distance = dragType === "column" ? Math.abs(event.clientX - startX) : Math.abs(event.clientY - startY);
            if (distance < DRAG_THRESHOLD) {
              event.preventDefault();
              return true;
            }
            const dragging = createDragVisuals(view, dragState);
            if (!dragging) {
              event.preventDefault();
              return true;
            }
            dragState = dragging;
          }
          updateDrag(dragState, view, event);
          event.preventDefault();
          event.stopPropagation();
          return true;
        },
        pointerup(view) {
          if (isColumnResizingActive(view) || isRowResizingActive(view) || !editor.isEditable || dragState.type === "idle") {
            return false;
          }
          releasePointerCapture();
          return finishDrag(view, dragState.currentDragState, editor, cleanup);
        },
        pointercancel(view) {
          if (!editor.isEditable || dragState.type === "idle") {
            return false;
          }
          releasePointerCapture();
          cancelDrag(view, cleanup);
          return true;
        }
      }
    },
    view() {
      return {
        destroy() {
          releasePointerCapture();
          cleanup();
          globalCleanup?.();
        }
      };
    }
  });
};
const createDragVisuals = (view, pending) => {
  const { dragType, fromStart, fromEnd, tablePosition } = pending.currentDragState;
  const tableElement = getTableElement(view, tablePosition);
  if (!tableElement) {
    return null;
  }
  const tableRect = tableElement.getBoundingClientRect();
  const isColumn = dragType === "column";
  const dropZoneElement = document.createElement("div");
  dropZoneElement.className = isColumn ? styles.columnDropIndicator : styles.rowDropIndicator;
  dropZoneElement.style.cssText = `display:none;position:fixed;z-index:${Z_INDEX_DROP_ZONE};${isColumn ? `top:${tableRect.top}px;height:${tableRect.height}px` : `left:${tableRect.left}px;width:${tableRect.width}px`}`;
  document.body.appendChild(dropZoneElement);
  const ghostElement = document.createElement("div");
  ghostElement.className = styles.dragGhost;
  ghostElement.style.cssText = `position:fixed;z-index:${Z_INDEX_GHOST};pointer-events:none`;
  const bounds = getGhostBounds(tableElement, dragType, fromStart, fromEnd, tableRect);
  if (bounds) {
    Object.assign(ghostElement.style, {
      left: `${bounds.left}px`,
      top: `${bounds.top}px`,
      width: `${bounds.width}px`,
      height: `${bounds.height}px`
    });
    ghostElement.dataset.startLeft = String(bounds.left);
    ghostElement.dataset.startTop = String(bounds.top);
  }
  document.body.appendChild(ghostElement);
  return {
    type: "dragging",
    handleElement: pending.handleElement,
    originalHandleTransform: pending.originalHandleTransform,
    currentDragState: pending.currentDragState,
    dropZoneElement,
    ghostElement,
    scrollContainer: pending.scrollContainer,
    autoScrollRAF: null,
    scrollOffsetX: pending.scrollOffsetX,
    scrollOffsetY: pending.scrollOffsetY
  };
};
const getGhostBounds = (tableElement, dragType, fromStart, fromEnd, tableRect) => {
  const tbody = tableElement.querySelector("tbody");
  if (!tbody) {
    return null;
  }
  if (dragType === "column") {
    const firstRow = tbody.querySelector("tr");
    if (!firstRow) {
      return null;
    }
    const cells = firstRow.querySelectorAll("td, th");
    let startX = tableRect.left;
    let endX = tableRect.left;
    let colIndex = 0;
    cells.forEach((cell) => {
      const cellRect = cell.getBoundingClientRect();
      const colspan = parseInt(cell.getAttribute("colspan") || "1", 10);
      const colWidth = cellRect.width / colspan;
      for (let c = 0; c < colspan; c++) {
        if (colIndex === fromStart) {
          startX = cellRect.left + c * colWidth;
        }
        if (colIndex === fromEnd) {
          endX = cellRect.left + (c + 1) * colWidth;
        }
        colIndex++;
      }
    });
    return { left: startX, top: tableRect.top, width: endX - startX, height: tableRect.height };
  } else {
    const rows = tbody.querySelectorAll("tr");
    let startY = tableRect.top;
    let endY = tableRect.top;
    rows.forEach((row, index) => {
      const rowRect = row.getBoundingClientRect();
      if (index === fromStart) {
        startY = rowRect.top;
      }
      if (index === fromEnd) {
        endY = rowRect.bottom;
      }
    });
    return { left: tableRect.left, top: startY, width: tableRect.width, height: endY - startY };
  }
};
const updateDrag = (state, view, event) => {
  const { dragType, fromStart, fromEnd, tablePosition } = state.currentDragState;
  const table = view.state.doc.nodeAt(tablePosition - 1);
  if (!table) {
    return;
  }
  const map = TableMap.get(table);
  const tableElement = getTableElement(view, tablePosition);
  if (!tableElement) {
    return;
  }
  const { dropIndex, dropPosition } = calculateDropPosition(
    event,
    state.currentDragState,
    tableElement,
    map
  );
  state.currentDragState = {
    ...state.currentDragState,
    currentX: event.clientX,
    currentY: event.clientY,
    dropIndex
  };
  updateDragVisualsPosition(state);
  updateDropIndicator(state, dropIndex, dropPosition, fromStart, fromEnd, dragType);
  performAutoScrollIfNeeded(state, tableElement, map, event);
};
const calculateDropPosition = (event, state, tableElement, map) => {
  const { dragType, fromStart, fromEnd } = state;
  const isColumn = dragType === "column";
  const mousePos = isColumn ? event.clientX : event.clientY;
  const boundaries = isColumn ? getColumnBoundaries(tableElement) : getRowBoundaries(tableElement);
  if (boundaries.length === 0) {
    return { dropIndex: fromStart, dropPosition: 0 };
  }
  const { closestIndex, closestPos } = findClosestDropBoundary(
    boundaries,
    mousePos,
    fromStart,
    fromEnd,
    map,
    isColumn
  );
  return { dropIndex: closestIndex, dropPosition: closestPos };
};
const finishDrag = (view, state, editor, cleanup) => {
  const { dragType, fromStart, fromEnd, dropIndex, tablePosition } = state;
  cleanup();
  view.dispatch(view.state.tr.setMeta(handleDraggingPluginKey, { type: "idle" }));
  const isValidDrop = dropIndex < fromStart || dropIndex > fromEnd + 1;
  if (!isValidDrop) {
    return true;
  }
  const count = fromEnd - fromStart + 1;
  const finalStart = dropIndex <= fromStart ? dropIndex : dropIndex - count;
  const finalEnd = finalStart + count - 1;
  if (dragType === "column") {
    editor.chain().reorderColumns({ start: fromStart, end: fromEnd }, dropIndex).selectColumns(finalStart, finalEnd, tablePosition).focus().run();
  } else {
    editor.chain().reorderRows({ start: fromStart, end: fromEnd }, dropIndex).selectRows(finalStart, finalEnd, tablePosition).focus().run();
  }
  return true;
};
const cancelDrag = (view, cleanup) => {
  cleanup();
  view.dispatch(view.state.tr.setMeta(handleDraggingPluginKey, { type: "idle" }));
};
export {
  handleDraggingPlugin,
  handleDraggingPluginKey,
  isHandleDraggingActive
};
//# sourceMappingURL=handle-dragging-plugin.js.map
