var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);

// src/core/graph-store.ts
import { atom } from "jotai";
import Graph from "graphology";
var graphOptions = {
  type: "directed",
  multi: true,
  allowSelfLoops: true
};
var currentGraphIdAtom = atom(null);
var graphAtom = atom(new Graph(graphOptions));
var graphUpdateVersionAtom = atom(0);
var edgeCreationAtom = atom({
  isCreating: false,
  sourceNodeId: null,
  sourceNodePosition: null,
  targetPosition: null,
  hoveredTargetNodeId: null,
  sourceHandle: null,
  targetHandle: null,
  sourcePort: null,
  targetPort: null,
  snappedTargetPosition: null
});
var draggingNodeIdAtom = atom(null);
var preDragNodeAttributesAtom = atom(null);

// src/core/graph-position.ts
import { atom as atom3 } from "jotai";
import { atomFamily } from "jotai-family";
import Graph2 from "graphology";

// src/utils/debug.ts
import debugFactory from "debug";
var NAMESPACE = "canvas";
function createDebug(module) {
  const base = debugFactory(`${NAMESPACE}:${module}`);
  const warn = debugFactory(`${NAMESPACE}:${module}:warn`);
  const error = debugFactory(`${NAMESPACE}:${module}:error`);
  warn.enabled = true;
  error.enabled = true;
  warn.log = console.warn.bind(console);
  error.log = console.error.bind(console);
  const debugFn = Object.assign(base, {
    warn,
    error
  });
  return debugFn;
}
var debug = {
  graph: {
    node: createDebug("graph:node"),
    edge: createDebug("graph:edge"),
    sync: createDebug("graph:sync")
  },
  ui: {
    selection: createDebug("ui:selection"),
    drag: createDebug("ui:drag"),
    resize: createDebug("ui:resize")
  },
  sync: {
    status: createDebug("sync:status"),
    mutations: createDebug("sync:mutations"),
    queue: createDebug("sync:queue")
  },
  viewport: createDebug("viewport")
};

// src/utils/mutation-queue.ts
var pendingNodeMutations = /* @__PURE__ */ new Map();
function clearAllPendingMutations() {
  pendingNodeMutations.clear();
}

// src/core/perf.ts
import { atom as atom2 } from "jotai";
var perfEnabledAtom = atom2(false);
var _enabled = false;
function setPerfEnabled(enabled) {
  _enabled = enabled;
}
if (typeof window !== "undefined") {
  window.__canvasPerf = setPerfEnabled;
}
function canvasMark(name) {
  if (!_enabled) return _noop;
  const markName = `canvas:${name}`;
  try {
    performance.mark(markName);
  } catch {
    return _noop;
  }
  return () => {
    try {
      performance.measure(`canvas:${name}`, markName);
    } catch {
    }
  };
}
function _noop() {
}
function canvasWrap(name, fn) {
  const end = canvasMark(name);
  try {
    return fn();
  } finally {
    end();
  }
}

// src/core/graph-position.ts
var debug2 = createDebug("graph:position");
var _positionCacheByGraph = /* @__PURE__ */ new WeakMap();
function getPositionCache(graph) {
  let cache = _positionCacheByGraph.get(graph);
  if (!cache) {
    cache = /* @__PURE__ */ new Map();
    _positionCacheByGraph.set(graph, cache);
  }
  return cache;
}
var nodePositionUpdateCounterAtom = atom3(0);
var nodePositionAtomFamily = atomFamily((nodeId) => atom3((get) => {
  get(nodePositionUpdateCounterAtom);
  const graph = get(graphAtom);
  if (!graph.hasNode(nodeId)) {
    return {
      x: 0,
      y: 0
    };
  }
  const x = graph.getNodeAttribute(nodeId, "x");
  const y = graph.getNodeAttribute(nodeId, "y");
  const cache = getPositionCache(graph);
  const prev = cache.get(nodeId);
  if (prev && prev.x === x && prev.y === y) {
    return prev;
  }
  const pos = {
    x,
    y
  };
  cache.set(nodeId, pos);
  return pos;
}));
var updateNodePositionAtom = atom3(null, (get, set, {
  nodeId,
  position
}) => {
  const end = canvasMark("drag-frame");
  const graph = get(graphAtom);
  if (graph.hasNode(nodeId)) {
    debug2("Updating node %s position to %o", nodeId, position);
    graph.setNodeAttribute(nodeId, "x", position.x);
    graph.setNodeAttribute(nodeId, "y", position.y);
    set(nodePositionUpdateCounterAtom, (c) => c + 1);
  }
  end();
});
var cleanupNodePositionAtom = atom3(null, (get, _set, nodeId) => {
  nodePositionAtomFamily.remove(nodeId);
  const graph = get(graphAtom);
  getPositionCache(graph).delete(nodeId);
  debug2("Removed position atom for node: %s", nodeId);
});
var cleanupAllNodePositionsAtom = atom3(null, (get, _set) => {
  const graph = get(graphAtom);
  const nodeIds = graph.nodes();
  nodeIds.forEach((nodeId) => {
    nodePositionAtomFamily.remove(nodeId);
  });
  _positionCacheByGraph.delete(graph);
  debug2("Removed %d position atoms", nodeIds.length);
});
var clearGraphOnSwitchAtom = atom3(null, (get, set) => {
  debug2("Clearing graph for switch");
  set(cleanupAllNodePositionsAtom);
  clearAllPendingMutations();
  const emptyGraph = new Graph2(graphOptions);
  set(graphAtom, emptyGraph);
  set(graphUpdateVersionAtom, (v) => v + 1);
});

// src/core/graph-derived.ts
import { atom as atom8 } from "jotai";
import { atomFamily as atomFamily2 } from "jotai-family";

// src/core/viewport-store.ts
import { atom as atom5 } from "jotai";

// src/core/selection-store.ts
import { atom as atom4 } from "jotai";
var debug3 = createDebug("selection");
var selectedNodeIdsAtom = atom4(/* @__PURE__ */ new Set());
var selectedEdgeIdAtom = atom4(null);
var handleNodePointerDownSelectionAtom = atom4(null, (get, set, {
  nodeId,
  isShiftPressed
}) => {
  const currentSelection = get(selectedNodeIdsAtom);
  debug3("handleNodePointerDownSelection: nodeId=%s, shift=%s, current=%o", nodeId, isShiftPressed, Array.from(currentSelection));
  set(selectedEdgeIdAtom, null);
  if (isShiftPressed) {
    const newSelection = new Set(currentSelection);
    if (newSelection.has(nodeId)) {
      newSelection.delete(nodeId);
    } else {
      newSelection.add(nodeId);
    }
    debug3("Shift-click, setting selection to: %o", Array.from(newSelection));
    set(selectedNodeIdsAtom, newSelection);
  } else {
    if (!currentSelection.has(nodeId)) {
      debug3("Node not in selection, selecting: %s", nodeId);
      set(selectedNodeIdsAtom, /* @__PURE__ */ new Set([nodeId]));
    } else {
      debug3("Node already selected, preserving multi-select");
    }
  }
});
var selectSingleNodeAtom = atom4(null, (get, set, nodeId) => {
  debug3("selectSingleNode: %s", nodeId);
  set(selectedEdgeIdAtom, null);
  if (nodeId === null || nodeId === void 0) {
    debug3("Clearing selection");
    set(selectedNodeIdsAtom, /* @__PURE__ */ new Set());
  } else {
    const currentSelection = get(selectedNodeIdsAtom);
    if (currentSelection.has(nodeId) && currentSelection.size === 1) {
      return;
    }
    set(selectedNodeIdsAtom, /* @__PURE__ */ new Set([nodeId]));
  }
});
var toggleNodeInSelectionAtom = atom4(null, (get, set, nodeId) => {
  const currentSelection = get(selectedNodeIdsAtom);
  const newSelection = new Set(currentSelection);
  if (newSelection.has(nodeId)) {
    newSelection.delete(nodeId);
  } else {
    newSelection.add(nodeId);
  }
  set(selectedNodeIdsAtom, newSelection);
});
var clearSelectionAtom = atom4(null, (_get, set) => {
  debug3("clearSelection");
  set(selectedNodeIdsAtom, /* @__PURE__ */ new Set());
});
var addNodesToSelectionAtom = atom4(null, (get, set, nodeIds) => {
  const currentSelection = get(selectedNodeIdsAtom);
  const newSelection = new Set(currentSelection);
  for (const nodeId of nodeIds) {
    newSelection.add(nodeId);
  }
  set(selectedNodeIdsAtom, newSelection);
});
var removeNodesFromSelectionAtom = atom4(null, (get, set, nodeIds) => {
  const currentSelection = get(selectedNodeIdsAtom);
  const newSelection = new Set(currentSelection);
  for (const nodeId of nodeIds) {
    newSelection.delete(nodeId);
  }
  set(selectedNodeIdsAtom, newSelection);
});
var selectEdgeAtom = atom4(null, (get, set, edgeId) => {
  set(selectedEdgeIdAtom, edgeId);
  if (edgeId !== null) {
    set(selectedNodeIdsAtom, /* @__PURE__ */ new Set());
  }
});
var clearEdgeSelectionAtom = atom4(null, (_get, set) => {
  set(selectedEdgeIdAtom, null);
});
var focusedNodeIdAtom = atom4(null);
var setFocusedNodeAtom = atom4(null, (_get, set, nodeId) => {
  set(focusedNodeIdAtom, nodeId);
});
var hasFocusedNodeAtom = atom4((get) => get(focusedNodeIdAtom) !== null);
var selectedNodesCountAtom = atom4((get) => get(selectedNodeIdsAtom).size);
var hasSelectionAtom = atom4((get) => get(selectedNodeIdsAtom).size > 0);

// src/utils/layout.ts
var FitToBoundsMode = /* @__PURE__ */ (function(FitToBoundsMode2) {
  FitToBoundsMode2["Graph"] = "graph";
  FitToBoundsMode2["Selection"] = "selection";
  return FitToBoundsMode2;
})({});
var calculateBounds = (nodes) => {
  if (nodes.length === 0) {
    return {
      x: 0,
      y: 0,
      width: 0,
      height: 0
    };
  }
  const minX = Math.min(...nodes.map((node) => node.x));
  const minY = Math.min(...nodes.map((node) => node.y));
  const maxX = Math.max(...nodes.map((node) => node.x + node.width));
  const maxY = Math.max(...nodes.map((node) => node.y + node.height));
  return {
    x: minX,
    y: minY,
    width: maxX - minX,
    height: maxY - minY
  };
};

// src/core/viewport-store.ts
var zoomAtom = atom5(1);
var panAtom = atom5({
  x: 0,
  y: 0
});
var viewportRectAtom = atom5(null);
var screenToWorldAtom = atom5((get) => {
  return (screenX, screenY) => {
    const pan = get(panAtom);
    const zoom = get(zoomAtom);
    const rect = get(viewportRectAtom);
    if (!rect) {
      return {
        x: screenX,
        y: screenY
      };
    }
    const relativeX = screenX - rect.left;
    const relativeY = screenY - rect.top;
    return {
      x: (relativeX - pan.x) / zoom,
      y: (relativeY - pan.y) / zoom
    };
  };
});
var worldToScreenAtom = atom5((get) => {
  return (worldX, worldY) => {
    const pan = get(panAtom);
    const zoom = get(zoomAtom);
    const rect = get(viewportRectAtom);
    if (!rect) {
      return {
        x: worldX,
        y: worldY
      };
    }
    return {
      x: worldX * zoom + pan.x + rect.left,
      y: worldY * zoom + pan.y + rect.top
    };
  };
});
var setZoomAtom = atom5(null, (get, set, {
  zoom,
  centerX,
  centerY
}) => {
  const currentZoom = get(zoomAtom);
  const pan = get(panAtom);
  const rect = get(viewportRectAtom);
  const newZoom = Math.max(0.1, Math.min(5, zoom));
  if (centerX !== void 0 && centerY !== void 0 && rect) {
    const relativeX = centerX - rect.left;
    const relativeY = centerY - rect.top;
    const worldX = (relativeX - pan.x) / currentZoom;
    const worldY = (relativeY - pan.y) / currentZoom;
    const newPanX = relativeX - worldX * newZoom;
    const newPanY = relativeY - worldY * newZoom;
    set(panAtom, {
      x: newPanX,
      y: newPanY
    });
  }
  set(zoomAtom, newZoom);
});
var resetViewportAtom = atom5(null, (_get, set) => {
  set(zoomAtom, 1);
  set(panAtom, {
    x: 0,
    y: 0
  });
});
var fitToBoundsAtom = atom5(null, (get, set, {
  mode,
  padding = 20
}) => {
  const normalizedMode = typeof mode === "string" ? mode === "graph" ? FitToBoundsMode.Graph : FitToBoundsMode.Selection : mode;
  const viewportSize = get(viewportRectAtom);
  if (!viewportSize || viewportSize.width <= 0 || viewportSize.height <= 0) return;
  get(nodePositionUpdateCounterAtom);
  let bounds;
  if (normalizedMode === FitToBoundsMode.Graph) {
    const graph = get(graphAtom);
    const nodes = graph.nodes().map((node) => {
      const attrs = graph.getNodeAttributes(node);
      return {
        x: attrs.x,
        y: attrs.y,
        width: attrs.width || 500,
        height: attrs.height || 500
      };
    });
    bounds = calculateBounds(nodes);
  } else {
    const selectedIds = get(selectedNodeIdsAtom);
    const allNodes = get(uiNodesAtom);
    const selectedNodes = allNodes.filter((n) => selectedIds.has(n.id)).map((n) => ({
      x: n.position.x,
      y: n.position.y,
      width: n.width ?? 500,
      height: n.height ?? 500
    }));
    bounds = calculateBounds(selectedNodes);
  }
  if (bounds.width <= 0 || bounds.height <= 0) return;
  const maxHPad = Math.max(0, viewportSize.width / 2 - 1);
  const maxVPad = Math.max(0, viewportSize.height / 2 - 1);
  const safePadding = Math.max(0, Math.min(padding, maxHPad, maxVPad));
  const effW = Math.max(1, viewportSize.width - 2 * safePadding);
  const effH = Math.max(1, viewportSize.height - 2 * safePadding);
  const scale = Math.min(effW / bounds.width, effH / bounds.height);
  if (scale <= 0 || !isFinite(scale)) return;
  set(zoomAtom, scale);
  const scaledW = bounds.width * scale;
  const scaledH = bounds.height * scale;
  const startX = safePadding + (effW - scaledW) / 2;
  const startY = safePadding + (effH - scaledH) / 2;
  set(panAtom, {
    x: startX - bounds.x * scale,
    y: startY - bounds.y * scale
  });
});
var centerOnNodeAtom = atom5(null, (get, set, nodeId) => {
  const nodes = get(uiNodesAtom);
  const node = nodes.find((n) => n.id === nodeId);
  if (!node) return;
  const {
    x,
    y,
    width = 200,
    height = 100
  } = node;
  const zoom = get(zoomAtom);
  const centerX = x + width / 2;
  const centerY = y + height / 2;
  const rect = get(viewportRectAtom);
  const halfWidth = rect ? rect.width / 2 : 400;
  const halfHeight = rect ? rect.height / 2 : 300;
  set(panAtom, {
    x: halfWidth - centerX * zoom,
    y: halfHeight - centerY * zoom
  });
});
var ZOOM_TRANSITION_THRESHOLD = 3.5;
var ZOOM_EXIT_THRESHOLD = 2;
var zoomFocusNodeIdAtom = atom5(null);
var zoomTransitionProgressAtom = atom5(0);
var isZoomTransitioningAtom = atom5((get) => {
  const progress = get(zoomTransitionProgressAtom);
  return progress > 0 && progress < 1;
});
var zoomAnimationTargetAtom = atom5(null);
var animateZoomToNodeAtom = atom5(null, (get, set, {
  nodeId,
  targetZoom,
  duration = 300
}) => {
  const nodes = get(uiNodesAtom);
  const node = nodes.find((n) => n.id === nodeId);
  if (!node) return;
  const {
    x,
    y,
    width = 200,
    height = 100
  } = node;
  const centerX = x + width / 2;
  const centerY = y + height / 2;
  const rect = get(viewportRectAtom);
  const halfWidth = rect ? rect.width / 2 : 400;
  const halfHeight = rect ? rect.height / 2 : 300;
  const finalZoom = targetZoom ?? get(zoomAtom);
  const targetPan = {
    x: halfWidth - centerX * finalZoom,
    y: halfHeight - centerY * finalZoom
  };
  set(zoomFocusNodeIdAtom, nodeId);
  set(zoomAnimationTargetAtom, {
    targetZoom: finalZoom,
    targetPan,
    startZoom: get(zoomAtom),
    startPan: {
      ...get(panAtom)
    },
    duration,
    startTime: performance.now()
  });
});
var animateFitToBoundsAtom = atom5(null, (get, set, {
  mode,
  padding = 20,
  duration = 300
}) => {
  const viewportSize = get(viewportRectAtom);
  if (!viewportSize || viewportSize.width <= 0 || viewportSize.height <= 0) return;
  get(nodePositionUpdateCounterAtom);
  let bounds;
  if (mode === "graph") {
    const graph = get(graphAtom);
    const nodes = graph.nodes().map((node) => {
      const attrs = graph.getNodeAttributes(node);
      return {
        x: attrs.x,
        y: attrs.y,
        width: attrs.width || 500,
        height: attrs.height || 500
      };
    });
    bounds = calculateBounds(nodes);
  } else {
    const selectedIds = get(selectedNodeIdsAtom);
    const allNodes = get(uiNodesAtom);
    const selectedNodes = allNodes.filter((n) => selectedIds.has(n.id)).map((n) => ({
      x: n.position.x,
      y: n.position.y,
      width: n.width ?? 500,
      height: n.height ?? 500
    }));
    bounds = calculateBounds(selectedNodes);
  }
  if (bounds.width <= 0 || bounds.height <= 0) return;
  const safePadding = Math.max(0, Math.min(padding, viewportSize.width / 2 - 1, viewportSize.height / 2 - 1));
  const effW = Math.max(1, viewportSize.width - 2 * safePadding);
  const effH = Math.max(1, viewportSize.height - 2 * safePadding);
  const scale = Math.min(effW / bounds.width, effH / bounds.height);
  if (scale <= 0 || !isFinite(scale)) return;
  const scaledW = bounds.width * scale;
  const scaledH = bounds.height * scale;
  const startX = safePadding + (effW - scaledW) / 2;
  const startY = safePadding + (effH - scaledH) / 2;
  const targetPan = {
    x: startX - bounds.x * scale,
    y: startY - bounds.y * scale
  };
  set(zoomAnimationTargetAtom, {
    targetZoom: scale,
    targetPan,
    startZoom: get(zoomAtom),
    startPan: {
      ...get(panAtom)
    },
    duration,
    startTime: performance.now()
  });
});

// src/core/group-store.ts
import { atom as atom7 } from "jotai";

// src/core/history-store.ts
import { atom as atom6 } from "jotai";

// src/core/history-actions.ts
function applyDelta(graph, delta) {
  switch (delta.type) {
    case "move-node": {
      if (!graph.hasNode(delta.nodeId)) return false;
      graph.setNodeAttribute(delta.nodeId, "x", delta.to.x);
      graph.setNodeAttribute(delta.nodeId, "y", delta.to.y);
      return false;
    }
    case "resize-node": {
      if (!graph.hasNode(delta.nodeId)) return false;
      graph.setNodeAttribute(delta.nodeId, "width", delta.to.width);
      graph.setNodeAttribute(delta.nodeId, "height", delta.to.height);
      return false;
    }
    case "add-node": {
      if (graph.hasNode(delta.nodeId)) return false;
      graph.addNode(delta.nodeId, delta.attributes);
      return true;
    }
    case "remove-node": {
      if (!graph.hasNode(delta.nodeId)) return false;
      graph.dropNode(delta.nodeId);
      return true;
    }
    case "add-edge": {
      if (graph.hasEdge(delta.edgeId)) return false;
      if (!graph.hasNode(delta.source) || !graph.hasNode(delta.target)) return false;
      graph.addEdgeWithKey(delta.edgeId, delta.source, delta.target, delta.attributes);
      return true;
    }
    case "remove-edge": {
      if (!graph.hasEdge(delta.edgeId)) return false;
      graph.dropEdge(delta.edgeId);
      return true;
    }
    case "update-node-attr": {
      if (!graph.hasNode(delta.nodeId)) return false;
      graph.setNodeAttribute(delta.nodeId, delta.key, delta.to);
      return false;
    }
    case "batch": {
      let structuralChange = false;
      for (const d of delta.deltas) {
        if (applyDelta(graph, d)) structuralChange = true;
      }
      return structuralChange;
    }
    case "full-snapshot": {
      graph.clear();
      for (const node of delta.nodes) {
        graph.addNode(node.id, node.attributes);
      }
      for (const edge of delta.edges) {
        if (graph.hasNode(edge.source) && graph.hasNode(edge.target)) {
          graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge.attributes);
        }
      }
      return true;
    }
  }
}
function invertDelta(delta) {
  switch (delta.type) {
    case "move-node":
      return {
        ...delta,
        from: delta.to,
        to: delta.from
      };
    case "resize-node":
      return {
        ...delta,
        from: delta.to,
        to: delta.from
      };
    case "add-node":
      return {
        type: "remove-node",
        nodeId: delta.nodeId,
        attributes: delta.attributes,
        connectedEdges: []
      };
    case "remove-node": {
      const batch = [{
        type: "add-node",
        nodeId: delta.nodeId,
        attributes: delta.attributes
      }, ...delta.connectedEdges.map((e) => ({
        type: "add-edge",
        edgeId: e.id,
        source: e.source,
        target: e.target,
        attributes: e.attributes
      }))];
      return batch.length === 1 ? batch[0] : {
        type: "batch",
        deltas: batch
      };
    }
    case "add-edge":
      return {
        type: "remove-edge",
        edgeId: delta.edgeId,
        source: delta.source,
        target: delta.target,
        attributes: delta.attributes
      };
    case "remove-edge":
      return {
        type: "add-edge",
        edgeId: delta.edgeId,
        source: delta.source,
        target: delta.target,
        attributes: delta.attributes
      };
    case "update-node-attr":
      return {
        ...delta,
        from: delta.to,
        to: delta.from
      };
    case "batch":
      return {
        type: "batch",
        deltas: delta.deltas.map(invertDelta).reverse()
      };
    case "full-snapshot":
      return delta;
  }
}
function createSnapshot(graph, label) {
  const nodes = [];
  const edges = [];
  graph.forEachNode((nodeId, attributes) => {
    nodes.push({
      id: nodeId,
      attributes: {
        ...attributes
      }
    });
  });
  graph.forEachEdge((edgeId, attributes, source, target) => {
    edges.push({
      id: edgeId,
      source,
      target,
      attributes: {
        ...attributes
      }
    });
  });
  return {
    timestamp: Date.now(),
    label,
    nodes,
    edges
  };
}

// src/core/history-store.ts
var debug4 = createDebug("history");
var MAX_HISTORY_SIZE = 50;
var historyStateAtom = atom6({
  past: [],
  future: [],
  isApplying: false
});
var canUndoAtom = atom6((get) => {
  const history = get(historyStateAtom);
  return history.past.length > 0 && !history.isApplying;
});
var canRedoAtom = atom6((get) => {
  const history = get(historyStateAtom);
  return history.future.length > 0 && !history.isApplying;
});
var undoCountAtom = atom6((get) => get(historyStateAtom).past.length);
var redoCountAtom = atom6((get) => get(historyStateAtom).future.length);
var pushDeltaAtom = atom6(null, (get, set, delta) => {
  const history = get(historyStateAtom);
  if (history.isApplying) return;
  const {
    label,
    ...cleanDelta
  } = delta;
  const entry = {
    forward: cleanDelta,
    reverse: invertDelta(cleanDelta),
    timestamp: Date.now(),
    label
  };
  const newPast = [...history.past, entry];
  if (newPast.length > MAX_HISTORY_SIZE) newPast.shift();
  set(historyStateAtom, {
    past: newPast,
    future: [],
    // Clear redo stack
    isApplying: false
  });
  debug4("Pushed delta: %s (past: %d)", label || delta.type, newPast.length);
});
var pushHistoryAtom = atom6(null, (get, set, label) => {
  const history = get(historyStateAtom);
  if (history.isApplying) return;
  const graph = get(graphAtom);
  const snapshot = createSnapshot(graph, label);
  const forward = {
    type: "full-snapshot",
    nodes: snapshot.nodes,
    edges: snapshot.edges
  };
  const entry = {
    forward,
    reverse: forward,
    // For full snapshots, reverse IS the current state
    timestamp: Date.now(),
    label
  };
  const newPast = [...history.past, entry];
  if (newPast.length > MAX_HISTORY_SIZE) newPast.shift();
  set(historyStateAtom, {
    past: newPast,
    future: [],
    isApplying: false
  });
  debug4("Pushed snapshot: %s (past: %d)", label || "unnamed", newPast.length);
});
var undoAtom = atom6(null, (get, set) => {
  const history = get(historyStateAtom);
  if (history.past.length === 0 || history.isApplying) return false;
  set(historyStateAtom, {
    ...history,
    isApplying: true
  });
  try {
    const graph = get(graphAtom);
    const newPast = [...history.past];
    const entry = newPast.pop();
    let forwardForRedo = entry.forward;
    if (entry.reverse.type === "full-snapshot") {
      const currentSnapshot = createSnapshot(graph, "current");
      forwardForRedo = {
        type: "full-snapshot",
        nodes: currentSnapshot.nodes,
        edges: currentSnapshot.edges
      };
    }
    const structuralChange = applyDelta(graph, entry.reverse);
    if (structuralChange) {
      set(graphAtom, graph);
      set(graphUpdateVersionAtom, (v) => v + 1);
    }
    set(nodePositionUpdateCounterAtom, (c) => c + 1);
    const redoEntry = {
      forward: forwardForRedo,
      reverse: entry.reverse,
      timestamp: entry.timestamp,
      label: entry.label
    };
    set(historyStateAtom, {
      past: newPast,
      future: [redoEntry, ...history.future],
      isApplying: false
    });
    debug4("Undo: %s (past: %d, future: %d)", entry.label, newPast.length, history.future.length + 1);
    return true;
  } catch (error) {
    debug4.error("Undo failed: %O", error);
    set(historyStateAtom, {
      ...history,
      isApplying: false
    });
    return false;
  }
});
var redoAtom = atom6(null, (get, set) => {
  const history = get(historyStateAtom);
  if (history.future.length === 0 || history.isApplying) return false;
  set(historyStateAtom, {
    ...history,
    isApplying: true
  });
  try {
    const graph = get(graphAtom);
    const newFuture = [...history.future];
    const entry = newFuture.shift();
    let reverseForUndo = entry.reverse;
    if (entry.forward.type === "full-snapshot") {
      const currentSnapshot = createSnapshot(graph, "current");
      reverseForUndo = {
        type: "full-snapshot",
        nodes: currentSnapshot.nodes,
        edges: currentSnapshot.edges
      };
    }
    const structuralChange = applyDelta(graph, entry.forward);
    if (structuralChange) {
      set(graphAtom, graph);
      set(graphUpdateVersionAtom, (v) => v + 1);
    }
    set(nodePositionUpdateCounterAtom, (c) => c + 1);
    const undoEntry = {
      forward: entry.forward,
      reverse: reverseForUndo,
      timestamp: entry.timestamp,
      label: entry.label
    };
    set(historyStateAtom, {
      past: [...history.past, undoEntry],
      future: newFuture,
      isApplying: false
    });
    debug4("Redo: %s (past: %d, future: %d)", entry.label, history.past.length + 1, newFuture.length);
    return true;
  } catch (error) {
    debug4.error("Redo failed: %O", error);
    set(historyStateAtom, {
      ...history,
      isApplying: false
    });
    return false;
  }
});
var clearHistoryAtom = atom6(null, (_get, set) => {
  set(historyStateAtom, {
    past: [],
    future: [],
    isApplying: false
  });
  debug4("History cleared");
});
var historyLabelsAtom = atom6((get) => {
  const history = get(historyStateAtom);
  return {
    past: history.past.map((e) => e.label || "Unnamed"),
    future: history.future.map((e) => e.label || "Unnamed")
  };
});

// src/core/group-store.ts
var collapsedGroupsAtom = atom7(/* @__PURE__ */ new Set());
var toggleGroupCollapseAtom = atom7(null, (get, set, groupId) => {
  const current = get(collapsedGroupsAtom);
  const next = new Set(current);
  if (next.has(groupId)) {
    next.delete(groupId);
  } else {
    next.add(groupId);
  }
  set(collapsedGroupsAtom, next);
});
var collapseGroupAtom = atom7(null, (get, set, groupId) => {
  const current = get(collapsedGroupsAtom);
  if (!current.has(groupId)) {
    const next = new Set(current);
    next.add(groupId);
    set(collapsedGroupsAtom, next);
  }
});
var expandGroupAtom = atom7(null, (get, set, groupId) => {
  const current = get(collapsedGroupsAtom);
  if (current.has(groupId)) {
    const next = new Set(current);
    next.delete(groupId);
    set(collapsedGroupsAtom, next);
  }
});
var nodeChildrenAtom = atom7((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  return (parentId) => {
    const children = [];
    graph.forEachNode((nodeId, attrs) => {
      if (attrs.parentId === parentId) {
        children.push(nodeId);
      }
    });
    return children;
  };
});
var nodeParentAtom = atom7((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  return (nodeId) => {
    if (!graph.hasNode(nodeId)) return void 0;
    return graph.getNodeAttribute(nodeId, "parentId");
  };
});
var isGroupNodeAtom = atom7((get) => {
  const getChildren = get(nodeChildrenAtom);
  return (nodeId) => getChildren(nodeId).length > 0;
});
var groupChildCountAtom = atom7((get) => {
  const getChildren = get(nodeChildrenAtom);
  return (groupId) => getChildren(groupId).length;
});
var setNodeParentAtom = atom7(null, (get, set, {
  nodeId,
  parentId
}) => {
  const graph = get(graphAtom);
  if (!graph.hasNode(nodeId)) return;
  if (parentId) {
    if (parentId === nodeId) return;
    let current = parentId;
    while (current) {
      if (current === nodeId) return;
      if (!graph.hasNode(current)) break;
      current = graph.getNodeAttribute(current, "parentId");
    }
  }
  graph.setNodeAttribute(nodeId, "parentId", parentId);
  set(graphUpdateVersionAtom, (v) => v + 1);
});
var moveNodesToGroupAtom = atom7(null, (get, set, {
  nodeIds,
  groupId
}) => {
  for (const nodeId of nodeIds) {
    set(setNodeParentAtom, {
      nodeId,
      parentId: groupId
    });
  }
});
var removeFromGroupAtom = atom7(null, (get, set, nodeId) => {
  set(setNodeParentAtom, {
    nodeId,
    parentId: void 0
  });
});
var groupSelectedNodesAtom = atom7(null, (get, set, {
  nodeIds,
  groupNodeId
}) => {
  set(pushHistoryAtom, `Group ${nodeIds.length} nodes`);
  const graph = get(graphAtom);
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
  for (const nodeId of nodeIds) {
    if (!graph.hasNode(nodeId)) continue;
    const attrs = graph.getNodeAttributes(nodeId);
    minX = Math.min(minX, attrs.x);
    minY = Math.min(minY, attrs.y);
    maxX = Math.max(maxX, attrs.x + (attrs.width || 200));
    maxY = Math.max(maxY, attrs.y + (attrs.height || 100));
  }
  const padding = 20;
  if (graph.hasNode(groupNodeId)) {
    graph.setNodeAttribute(groupNodeId, "x", minX - padding);
    graph.setNodeAttribute(groupNodeId, "y", minY - padding - 30);
    graph.setNodeAttribute(groupNodeId, "width", maxX - minX + 2 * padding);
    graph.setNodeAttribute(groupNodeId, "height", maxY - minY + 2 * padding + 30);
  }
  for (const nodeId of nodeIds) {
    if (nodeId !== groupNodeId && graph.hasNode(nodeId)) {
      graph.setNodeAttribute(nodeId, "parentId", groupNodeId);
    }
  }
  set(graphUpdateVersionAtom, (v) => v + 1);
  set(nodePositionUpdateCounterAtom, (c) => c + 1);
});
var ungroupNodesAtom = atom7(null, (get, set, groupId) => {
  set(pushHistoryAtom, "Ungroup nodes");
  const graph = get(graphAtom);
  graph.forEachNode((nodeId, attrs) => {
    if (attrs.parentId === groupId) {
      graph.setNodeAttribute(nodeId, "parentId", void 0);
    }
  });
  set(graphUpdateVersionAtom, (v) => v + 1);
});
var nestNodesOnDropAtom = atom7(null, (get, set, {
  nodeIds,
  targetId
}) => {
  set(pushHistoryAtom, "Nest nodes");
  for (const nodeId of nodeIds) {
    if (nodeId === targetId) continue;
    set(setNodeParentAtom, {
      nodeId,
      parentId: targetId
    });
  }
  set(autoResizeGroupAtom, targetId);
});
function getNodeDescendants(graph, groupId) {
  const descendants = [];
  const stack = [groupId];
  while (stack.length > 0) {
    const current = stack.pop();
    graph.forEachNode((nodeId, attrs) => {
      if (attrs.parentId === current) {
        descendants.push(nodeId);
        stack.push(nodeId);
      }
    });
  }
  return descendants;
}
var collapsedEdgeRemapAtom = atom7((get) => {
  const collapsed = get(collapsedGroupsAtom);
  if (collapsed.size === 0) return /* @__PURE__ */ new Map();
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  const remap = /* @__PURE__ */ new Map();
  for (const nodeId of graph.nodes()) {
    let current = nodeId;
    let outermost = null;
    while (true) {
      if (!graph.hasNode(current)) break;
      const parent = graph.getNodeAttribute(current, "parentId");
      if (!parent) break;
      if (collapsed.has(parent)) outermost = parent;
      current = parent;
    }
    if (outermost) remap.set(nodeId, outermost);
  }
  return remap;
});
var autoResizeGroupAtom = atom7(null, (get, set, groupId) => {
  const graph = get(graphAtom);
  if (!graph.hasNode(groupId)) return;
  const children = [];
  graph.forEachNode((nodeId, attrs) => {
    if (attrs.parentId === groupId) {
      children.push(nodeId);
    }
  });
  if (children.length === 0) return;
  const padding = 20;
  const headerHeight = 30;
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
  for (const childId of children) {
    const attrs = graph.getNodeAttributes(childId);
    minX = Math.min(minX, attrs.x);
    minY = Math.min(minY, attrs.y);
    maxX = Math.max(maxX, attrs.x + (attrs.width || 200));
    maxY = Math.max(maxY, attrs.y + (attrs.height || 100));
  }
  graph.setNodeAttribute(groupId, "x", minX - padding);
  graph.setNodeAttribute(groupId, "y", minY - padding - headerHeight);
  graph.setNodeAttribute(groupId, "width", maxX - minX + 2 * padding);
  graph.setNodeAttribute(groupId, "height", maxY - minY + 2 * padding + headerHeight);
  set(nodePositionUpdateCounterAtom, (c) => c + 1);
});
function isNodeCollapsed(nodeId, getParentId, collapsed) {
  let current = nodeId;
  while (true) {
    const parentId = getParentId(current);
    if (!parentId) return false;
    if (collapsed.has(parentId)) return true;
    current = parentId;
  }
}

// src/core/graph-derived.ts
var highestZIndexAtom = atom8((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  let maxZ = 0;
  graph.forEachNode((_node, attributes) => {
    if (attributes.zIndex > maxZ) {
      maxZ = attributes.zIndex;
    }
  });
  return maxZ;
});
var _prevUiNodesByGraph = /* @__PURE__ */ new WeakMap();
var uiNodesAtom = atom8((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  const currentDraggingId = get(draggingNodeIdAtom);
  const collapsed = get(collapsedGroupsAtom);
  const nodes = [];
  graph.forEachNode((nodeId, attributes) => {
    if (collapsed.size > 0) {
      let current = nodeId;
      let hidden = false;
      while (true) {
        if (!graph.hasNode(current)) break;
        const pid = graph.getNodeAttributes(current).parentId;
        if (!pid) break;
        if (collapsed.has(pid)) {
          hidden = true;
          break;
        }
        current = pid;
      }
      if (hidden) return;
    }
    const position = get(nodePositionAtomFamily(nodeId));
    nodes.push({
      ...attributes,
      id: nodeId,
      position,
      isDragging: nodeId === currentDraggingId
    });
  });
  const prev = _prevUiNodesByGraph.get(graph) ?? [];
  if (nodes.length === prev.length && nodes.every((n, i) => n.id === prev[i].id && n.position === prev[i].position && n.isDragging === prev[i].isDragging)) {
    return prev;
  }
  _prevUiNodesByGraph.set(graph, nodes);
  return nodes;
});
var nodeKeysAtom = atom8((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  return graph.nodes();
});
var nodeFamilyAtom = atomFamily2((nodeId) => atom8((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  if (!graph.hasNode(nodeId)) {
    return null;
  }
  const attributes = graph.getNodeAttributes(nodeId);
  const position = get(nodePositionAtomFamily(nodeId));
  const currentDraggingId = get(draggingNodeIdAtom);
  return {
    ...attributes,
    id: nodeId,
    position,
    isDragging: nodeId === currentDraggingId
  };
}), (a, b) => a === b);
var edgeKeysAtom = atom8((get) => {
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  return graph.edges();
});
var edgeKeysWithTempEdgeAtom = atom8((get) => {
  const keys = get(edgeKeysAtom);
  const edgeCreation = get(edgeCreationAtom);
  if (edgeCreation.isCreating) {
    return [...keys, "temp-creating-edge"];
  }
  return keys;
});
var _edgeCacheByGraph = /* @__PURE__ */ new WeakMap();
function getEdgeCache(graph) {
  let cache = _edgeCacheByGraph.get(graph);
  if (!cache) {
    cache = /* @__PURE__ */ new Map();
    _edgeCacheByGraph.set(graph, cache);
  }
  return cache;
}
var edgeFamilyAtom = atomFamily2((key) => atom8((get) => {
  get(graphUpdateVersionAtom);
  if (key === "temp-creating-edge") {
    const edgeCreationState = get(edgeCreationAtom);
    const graph2 = get(graphAtom);
    if (edgeCreationState.isCreating && edgeCreationState.sourceNodeId && edgeCreationState.targetPosition) {
      const sourceNodeAttrs = graph2.getNodeAttributes(edgeCreationState.sourceNodeId);
      const sourceNodePosition = get(nodePositionAtomFamily(edgeCreationState.sourceNodeId));
      const pan = get(panAtom);
      const zoom = get(zoomAtom);
      const viewportRect = get(viewportRectAtom);
      if (sourceNodeAttrs && viewportRect) {
        const mouseX = edgeCreationState.targetPosition.x - viewportRect.left;
        const mouseY = edgeCreationState.targetPosition.y - viewportRect.top;
        const worldTargetX = (mouseX - pan.x) / zoom;
        const worldTargetY = (mouseY - pan.y) / zoom;
        const tempEdge = {
          key: "temp-creating-edge",
          sourceId: edgeCreationState.sourceNodeId,
          targetId: "temp-cursor",
          sourcePosition: sourceNodePosition,
          targetPosition: {
            x: worldTargetX,
            y: worldTargetY
          },
          sourceNodeSize: sourceNodeAttrs.size,
          sourceNodeWidth: sourceNodeAttrs.width,
          sourceNodeHeight: sourceNodeAttrs.height,
          targetNodeSize: 0,
          targetNodeWidth: 0,
          targetNodeHeight: 0,
          type: "dashed",
          color: "#FF9800",
          weight: 2,
          label: void 0,
          dbData: {
            id: "temp-creating-edge",
            graph_id: get(currentGraphIdAtom) || "",
            source_node_id: edgeCreationState.sourceNodeId,
            target_node_id: "temp-cursor",
            edge_type: "temp",
            filter_condition: null,
            ui_properties: null,
            data: null,
            created_at: (/* @__PURE__ */ new Date()).toISOString(),
            updated_at: (/* @__PURE__ */ new Date()).toISOString()
          }
        };
        return tempEdge;
      }
    }
    return null;
  }
  const graph = get(graphAtom);
  if (!graph.hasEdge(key)) {
    getEdgeCache(graph).delete(key);
    return null;
  }
  const sourceId = graph.source(key);
  const targetId = graph.target(key);
  const attributes = graph.getEdgeAttributes(key);
  const remap = get(collapsedEdgeRemapAtom);
  const effectiveSourceId = remap.get(sourceId) ?? sourceId;
  const effectiveTargetId = remap.get(targetId) ?? targetId;
  if (!graph.hasNode(effectiveSourceId) || !graph.hasNode(effectiveTargetId)) {
    getEdgeCache(graph).delete(key);
    return null;
  }
  const sourceAttributes = graph.getNodeAttributes(effectiveSourceId);
  const targetAttributes = graph.getNodeAttributes(effectiveTargetId);
  const sourcePosition = get(nodePositionAtomFamily(effectiveSourceId));
  const targetPosition = get(nodePositionAtomFamily(effectiveTargetId));
  if (sourceAttributes && targetAttributes) {
    const next = {
      ...attributes,
      key,
      sourceId: effectiveSourceId,
      targetId: effectiveTargetId,
      sourcePosition,
      targetPosition,
      sourceNodeSize: sourceAttributes.size,
      targetNodeSize: targetAttributes.size,
      sourceNodeWidth: sourceAttributes.width ?? sourceAttributes.size,
      sourceNodeHeight: sourceAttributes.height ?? sourceAttributes.size,
      targetNodeWidth: targetAttributes.width ?? targetAttributes.size,
      targetNodeHeight: targetAttributes.height ?? targetAttributes.size
    };
    const edgeCache = getEdgeCache(graph);
    const prev = edgeCache.get(key);
    if (prev && prev.sourcePosition === next.sourcePosition && prev.targetPosition === next.targetPosition && prev.sourceId === next.sourceId && prev.targetId === next.targetId && prev.type === next.type && prev.color === next.color && prev.weight === next.weight && prev.label === next.label && prev.sourceNodeSize === next.sourceNodeSize && prev.targetNodeSize === next.targetNodeSize && prev.sourceNodeWidth === next.sourceNodeWidth && prev.sourceNodeHeight === next.sourceNodeHeight && prev.targetNodeWidth === next.targetNodeWidth && prev.targetNodeHeight === next.targetNodeHeight) {
      return prev;
    }
    edgeCache.set(key, next);
    return next;
  }
  getEdgeCache(graph).delete(key);
  return null;
}), (a, b) => a === b);

// src/core/graph-mutations.ts
import { atom as atom12 } from "jotai";
import Graph3 from "graphology";

// src/core/graph-mutations-edges.ts
import { atom as atom10 } from "jotai";

// src/core/reduced-motion-store.ts
import { atom as atom9 } from "jotai";
var prefersReducedMotionAtom = atom9(typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false);
var watchReducedMotionAtom = atom9(null, (_get, set) => {
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
  const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
  const handler = (e) => {
    set(prefersReducedMotionAtom, e.matches);
  };
  set(prefersReducedMotionAtom, mql.matches);
  mql.addEventListener("change", handler);
  return () => mql.removeEventListener("change", handler);
});

// src/core/graph-mutations-edges.ts
var debug5 = createDebug("graph:mutations:edges");
var addEdgeToLocalGraphAtom = atom10(null, (get, set, newEdge) => {
  const graph = get(graphAtom);
  if (graph.hasNode(newEdge.source_node_id) && graph.hasNode(newEdge.target_node_id)) {
    const uiProps = newEdge.ui_properties || {};
    const attributes = {
      type: typeof uiProps.style === "string" ? uiProps.style : "solid",
      color: typeof uiProps.color === "string" ? uiProps.color : "#999",
      label: newEdge.edge_type ?? void 0,
      weight: typeof uiProps.weight === "number" ? uiProps.weight : 1,
      dbData: newEdge
    };
    if (!graph.hasEdge(newEdge.id)) {
      try {
        debug5("Adding edge %s to local graph", newEdge.id);
        graph.addEdgeWithKey(newEdge.id, newEdge.source_node_id, newEdge.target_node_id, attributes);
        set(graphAtom, graph.copy());
        set(graphUpdateVersionAtom, (v) => v + 1);
      } catch (e) {
        debug5("Failed to add edge %s: %o", newEdge.id, e);
      }
    }
  }
});
var removeEdgeFromLocalGraphAtom = atom10(null, (get, set, edgeId) => {
  const graph = get(graphAtom);
  if (graph.hasEdge(edgeId)) {
    graph.dropEdge(edgeId);
    set(graphAtom, graph.copy());
    set(graphUpdateVersionAtom, (v) => v + 1);
  }
});
var swapEdgeAtomicAtom = atom10(null, (get, set, {
  tempEdgeId,
  newEdge
}) => {
  const graph = get(graphAtom);
  if (graph.hasEdge(tempEdgeId)) {
    graph.dropEdge(tempEdgeId);
  }
  if (graph.hasNode(newEdge.source_node_id) && graph.hasNode(newEdge.target_node_id)) {
    const uiProps = newEdge.ui_properties || {};
    const attributes = {
      type: typeof uiProps.style === "string" ? uiProps.style : "solid",
      color: typeof uiProps.color === "string" ? uiProps.color : "#999",
      label: newEdge.edge_type ?? void 0,
      weight: typeof uiProps.weight === "number" ? uiProps.weight : 1,
      dbData: newEdge
    };
    if (!graph.hasEdge(newEdge.id)) {
      try {
        debug5("Atomically swapping temp edge %s with real edge %s", tempEdgeId, newEdge.id);
        graph.addEdgeWithKey(newEdge.id, newEdge.source_node_id, newEdge.target_node_id, attributes);
      } catch (e) {
        debug5("Failed to add edge %s: %o", newEdge.id, e);
      }
    }
  }
  set(graphAtom, graph.copy());
  set(graphUpdateVersionAtom, (v) => v + 1);
});
var departingEdgesAtom = atom10(/* @__PURE__ */ new Map());
var EDGE_ANIMATION_DURATION = 300;
var removeEdgeWithAnimationAtom = atom10(null, (get, set, edgeKey) => {
  const edgeState = get(edgeFamilyAtom(edgeKey));
  if (edgeState) {
    const departing = new Map(get(departingEdgesAtom));
    departing.set(edgeKey, edgeState);
    set(departingEdgesAtom, departing);
    set(removeEdgeFromLocalGraphAtom, edgeKey);
    const duration = get(prefersReducedMotionAtom) ? 0 : EDGE_ANIMATION_DURATION;
    setTimeout(() => {
      const current = new Map(get(departingEdgesAtom));
      current.delete(edgeKey);
      set(departingEdgesAtom, current);
    }, duration);
  }
});
var editingEdgeLabelAtom = atom10(null);
var updateEdgeLabelAtom = atom10(null, (get, set, {
  edgeKey,
  label
}) => {
  const graph = get(graphAtom);
  if (graph.hasEdge(edgeKey)) {
    graph.setEdgeAttribute(edgeKey, "label", label || void 0);
    set(graphUpdateVersionAtom, (v) => v + 1);
    set(nodePositionUpdateCounterAtom, (c) => c + 1);
  }
});

// src/core/graph-mutations-advanced.ts
import { atom as atom11 } from "jotai";
var debug6 = createDebug("graph:mutations:advanced");
var dropTargetNodeIdAtom = atom11(null);
var splitNodeAtom = atom11(null, (get, set, {
  nodeId,
  position1,
  position2
}) => {
  const graph = get(graphAtom);
  if (!graph.hasNode(nodeId)) return;
  const attrs = graph.getNodeAttributes(nodeId);
  const graphId = get(currentGraphIdAtom) || attrs.dbData.graph_id;
  set(pushHistoryAtom, "Split node");
  graph.setNodeAttribute(nodeId, "x", position1.x);
  graph.setNodeAttribute(nodeId, "y", position1.y);
  const edges = [];
  graph.forEachEdge(nodeId, (_key, eAttrs, source, target) => {
    edges.push({
      source,
      target,
      attrs: eAttrs
    });
  });
  const cloneId = `split-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  const cloneDbNode = {
    ...attrs.dbData,
    id: cloneId,
    graph_id: graphId,
    ui_properties: {
      ...attrs.dbData.ui_properties || {},
      x: position2.x,
      y: position2.y
    },
    created_at: (/* @__PURE__ */ new Date()).toISOString(),
    updated_at: (/* @__PURE__ */ new Date()).toISOString()
  };
  set(addNodeToLocalGraphAtom, cloneDbNode);
  for (const edge of edges) {
    const newSource = edge.source === nodeId ? cloneId : edge.source;
    const newTarget = edge.target === nodeId ? cloneId : edge.target;
    set(addEdgeToLocalGraphAtom, {
      ...edge.attrs.dbData,
      id: `split-e-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
      source_node_id: newSource,
      target_node_id: newTarget
    });
  }
  set(graphUpdateVersionAtom, (v) => v + 1);
  set(nodePositionUpdateCounterAtom, (c) => c + 1);
  debug6("Split node %s \u2192 clone %s", nodeId, cloneId);
});
var mergeNodesAtom = atom11(null, (get, set, {
  nodeIds
}) => {
  if (nodeIds.length < 2) return;
  const graph = get(graphAtom);
  const [survivorId, ...doomed] = nodeIds;
  if (!graph.hasNode(survivorId)) return;
  set(pushHistoryAtom, `Merge ${nodeIds.length} nodes`);
  const doomedSet = new Set(doomed);
  for (const doomedId of doomed) {
    if (!graph.hasNode(doomedId)) continue;
    const edges = [];
    graph.forEachEdge(doomedId, (_key, eAttrs, source, target) => {
      edges.push({
        source,
        target,
        attrs: eAttrs
      });
    });
    for (const edge of edges) {
      const newSource = doomedSet.has(edge.source) ? survivorId : edge.source;
      const newTarget = doomedSet.has(edge.target) ? survivorId : edge.target;
      if (newSource === newTarget) continue;
      set(addEdgeToLocalGraphAtom, {
        ...edge.attrs.dbData,
        id: `merge-e-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
        source_node_id: newSource,
        target_node_id: newTarget
      });
    }
    set(optimisticDeleteNodeAtom, {
      nodeId: doomedId
    });
  }
  set(graphUpdateVersionAtom, (v) => v + 1);
  debug6("Merged nodes %o \u2192 survivor %s", nodeIds, survivorId);
});

// src/core/graph-mutations.ts
var debug7 = createDebug("graph:mutations");
var startNodeDragAtom = atom12(null, (get, set, {
  nodeId
}) => {
  const graph = get(graphAtom);
  if (!graph.hasNode(nodeId)) return;
  const currentAttributes = graph.getNodeAttributes(nodeId);
  set(preDragNodeAttributesAtom, JSON.parse(JSON.stringify(currentAttributes)));
  const currentHighestZIndex = get(highestZIndexAtom);
  const newZIndex = currentHighestZIndex + 1;
  graph.setNodeAttribute(nodeId, "zIndex", newZIndex);
  set(draggingNodeIdAtom, nodeId);
});
var endNodeDragAtom = atom12(null, (get, set, _payload) => {
  const currentDraggingId = get(draggingNodeIdAtom);
  if (currentDraggingId) {
    debug7("Node %s drag ended", currentDraggingId);
    const graph = get(graphAtom);
    if (graph.hasNode(currentDraggingId)) {
      const parentId = graph.getNodeAttribute(currentDraggingId, "parentId");
      if (parentId) {
        set(autoResizeGroupAtom, parentId);
      }
    }
  }
  set(draggingNodeIdAtom, null);
  set(preDragNodeAttributesAtom, null);
});
var optimisticDeleteNodeAtom = atom12(null, (get, set, {
  nodeId
}) => {
  const graph = get(graphAtom);
  if (graph.hasNode(nodeId)) {
    graph.dropNode(nodeId);
    set(cleanupNodePositionAtom, nodeId);
    set(graphAtom, graph.copy());
    debug7("Optimistically deleted node %s", nodeId);
  }
});
var optimisticDeleteEdgeAtom = atom12(null, (get, set, {
  edgeKey
}) => {
  const graph = get(graphAtom);
  if (graph.hasEdge(edgeKey)) {
    graph.dropEdge(edgeKey);
    set(graphAtom, graph.copy());
    debug7("Optimistically deleted edge %s", edgeKey);
  }
});
var addNodeToLocalGraphAtom = atom12(null, (get, set, newNode) => {
  const graph = get(graphAtom);
  if (graph.hasNode(newNode.id)) {
    debug7("Node %s already exists, skipping", newNode.id);
    return;
  }
  const uiProps = newNode.ui_properties || {};
  const attributes = {
    x: typeof uiProps.x === "number" ? uiProps.x : Math.random() * 800,
    y: typeof uiProps.y === "number" ? uiProps.y : Math.random() * 600,
    size: typeof uiProps.size === "number" ? uiProps.size : 15,
    width: typeof uiProps.width === "number" ? uiProps.width : 500,
    height: typeof uiProps.height === "number" ? uiProps.height : 500,
    color: typeof uiProps.color === "string" ? uiProps.color : "#ccc",
    label: newNode.label || newNode.node_type || newNode.id,
    zIndex: typeof uiProps.zIndex === "number" ? uiProps.zIndex : 0,
    dbData: newNode
  };
  debug7("Adding node %s to local graph at (%d, %d)", newNode.id, attributes.x, attributes.y);
  graph.addNode(newNode.id, attributes);
  set(graphAtom, graph.copy());
  set(graphUpdateVersionAtom, (v) => v + 1);
  set(nodePositionUpdateCounterAtom, (c) => c + 1);
});
var loadGraphFromDbAtom = atom12(null, (get, set, fetchedNodes, fetchedEdges) => {
  debug7("========== START SYNC ==========");
  debug7("Fetched nodes: %d, edges: %d", fetchedNodes.length, fetchedEdges.length);
  const currentGraphId = get(currentGraphIdAtom);
  if (fetchedNodes.length > 0 && fetchedNodes[0].graph_id !== currentGraphId) {
    debug7("Skipping sync - data belongs to different graph");
    return;
  }
  const existingGraph = get(graphAtom);
  const isDragging = get(draggingNodeIdAtom) !== null;
  if (isDragging) {
    debug7("Skipping sync - drag in progress");
    return;
  }
  const existingNodeIds = new Set(existingGraph.nodes());
  const fetchedNodeIds = new Set(fetchedNodes.map((n) => n.id));
  const hasAnyCommonNodes = Array.from(existingNodeIds).some((id) => fetchedNodeIds.has(id));
  let graph;
  if (hasAnyCommonNodes && existingNodeIds.size > 0) {
    debug7("Merging DB data into existing graph");
    graph = existingGraph.copy();
  } else {
    debug7("Creating fresh graph (graph switch detected)");
    graph = new Graph3(graphOptions);
  }
  const fetchedEdgeIds = new Set(fetchedEdges.map((e) => e.id));
  if (hasAnyCommonNodes && existingNodeIds.size > 0) {
    graph.forEachNode((nodeId) => {
      if (!fetchedNodeIds.has(nodeId)) {
        debug7("Removing deleted node: %s", nodeId);
        graph.dropNode(nodeId);
        nodePositionAtomFamily.remove(nodeId);
      }
    });
  }
  fetchedNodes.forEach((node) => {
    const uiProps = node.ui_properties || {};
    const newX = typeof uiProps.x === "number" ? uiProps.x : Math.random() * 800;
    const newY = typeof uiProps.y === "number" ? uiProps.y : Math.random() * 600;
    if (graph.hasNode(node.id)) {
      const currentAttrs = graph.getNodeAttributes(node.id);
      const attributes = {
        x: newX,
        y: newY,
        size: typeof uiProps.size === "number" ? uiProps.size : currentAttrs.size,
        width: typeof uiProps.width === "number" ? uiProps.width : currentAttrs.width ?? 500,
        height: typeof uiProps.height === "number" ? uiProps.height : currentAttrs.height ?? 500,
        color: typeof uiProps.color === "string" ? uiProps.color : currentAttrs.color,
        label: node.label || node.node_type || node.id,
        zIndex: typeof uiProps.zIndex === "number" ? uiProps.zIndex : currentAttrs.zIndex,
        dbData: node
      };
      graph.replaceNodeAttributes(node.id, attributes);
    } else {
      const attributes = {
        x: newX,
        y: newY,
        size: typeof uiProps.size === "number" ? uiProps.size : 15,
        width: typeof uiProps.width === "number" ? uiProps.width : 500,
        height: typeof uiProps.height === "number" ? uiProps.height : 500,
        color: typeof uiProps.color === "string" ? uiProps.color : "#ccc",
        label: node.label || node.node_type || node.id,
        zIndex: typeof uiProps.zIndex === "number" ? uiProps.zIndex : 0,
        dbData: node
      };
      graph.addNode(node.id, attributes);
    }
  });
  graph.forEachEdge((edgeId) => {
    if (!fetchedEdgeIds.has(edgeId)) {
      debug7("Removing deleted edge: %s", edgeId);
      graph.dropEdge(edgeId);
    }
  });
  fetchedEdges.forEach((edge) => {
    if (graph.hasNode(edge.source_node_id) && graph.hasNode(edge.target_node_id)) {
      const uiProps = edge.ui_properties || {};
      const attributes = {
        type: typeof uiProps.style === "string" ? uiProps.style : "solid",
        color: typeof uiProps.color === "string" ? uiProps.color : "#999",
        label: edge.edge_type ?? void 0,
        weight: typeof uiProps.weight === "number" ? uiProps.weight : 1,
        dbData: edge
      };
      if (graph.hasEdge(edge.id)) {
        graph.replaceEdgeAttributes(edge.id, attributes);
      } else {
        try {
          graph.addEdgeWithKey(edge.id, edge.source_node_id, edge.target_node_id, attributes);
        } catch (e) {
          debug7("Failed to add edge %s: %o", edge.id, e);
        }
      }
    }
  });
  set(graphAtom, graph);
  set(graphUpdateVersionAtom, (v) => v + 1);
  debug7("========== SYNC COMPLETE ==========");
  debug7("Final graph: %d nodes, %d edges", graph.order, graph.size);
});

// src/core/sync-store.ts
import { atom as atom13 } from "jotai";
var debug8 = createDebug("sync");
var syncStatusAtom = atom13("synced");
var pendingMutationsCountAtom = atom13(0);
var isOnlineAtom = atom13(typeof navigator !== "undefined" ? navigator.onLine : true);
var lastSyncErrorAtom = atom13(null);
var lastSyncTimeAtom = atom13(Date.now());
var mutationQueueAtom = atom13([]);
var syncStateAtom = atom13((get) => ({
  status: get(syncStatusAtom),
  pendingMutations: get(pendingMutationsCountAtom),
  lastError: get(lastSyncErrorAtom),
  lastSyncTime: get(lastSyncTimeAtom),
  isOnline: get(isOnlineAtom),
  queuedMutations: get(mutationQueueAtom).length
}));
var startMutationAtom = atom13(null, (get, set) => {
  const currentCount = get(pendingMutationsCountAtom);
  const newCount = currentCount + 1;
  set(pendingMutationsCountAtom, newCount);
  debug8("Mutation started. Pending count: %d -> %d", currentCount, newCount);
  if (newCount > 0 && get(syncStatusAtom) !== "syncing") {
    set(syncStatusAtom, "syncing");
    debug8("Status -> syncing");
  }
});
var completeMutationAtom = atom13(null, (get, set, success = true) => {
  const currentCount = get(pendingMutationsCountAtom);
  const newCount = Math.max(0, currentCount - 1);
  set(pendingMutationsCountAtom, newCount);
  debug8("Mutation completed (success: %s). Pending count: %d -> %d", success, currentCount, newCount);
  if (success) {
    set(lastSyncTimeAtom, Date.now());
    if (newCount === 0) {
      set(lastSyncErrorAtom, null);
    }
  }
  if (newCount === 0) {
    const isOnline = get(isOnlineAtom);
    const hasError = get(lastSyncErrorAtom) !== null;
    if (hasError) {
      set(syncStatusAtom, "error");
      debug8("Status -> error");
    } else if (!isOnline) {
      set(syncStatusAtom, "offline");
      debug8("Status -> offline");
    } else {
      set(syncStatusAtom, "synced");
      debug8("Status -> synced");
    }
  }
});
var trackMutationErrorAtom = atom13(null, (_get, set, error) => {
  set(lastSyncErrorAtom, error);
  debug8("Mutation failed: %s", error);
});
var setOnlineStatusAtom = atom13(null, (get, set, isOnline) => {
  set(isOnlineAtom, isOnline);
  const pendingCount = get(pendingMutationsCountAtom);
  const hasError = get(lastSyncErrorAtom) !== null;
  const queueLength = get(mutationQueueAtom).length;
  if (pendingCount === 0) {
    if (hasError || queueLength > 0) {
      set(syncStatusAtom, "error");
    } else {
      set(syncStatusAtom, isOnline ? "synced" : "offline");
    }
  }
});
var queueMutationAtom = atom13(null, (get, set, mutation) => {
  const queue = get(mutationQueueAtom);
  const newMutation = {
    ...mutation,
    id: crypto.randomUUID(),
    timestamp: Date.now(),
    retryCount: 0,
    maxRetries: mutation.maxRetries ?? 3
  };
  const newQueue = [...queue, newMutation];
  set(mutationQueueAtom, newQueue);
  debug8("Queued mutation: %s. Queue size: %d", mutation.type, newQueue.length);
  if (get(pendingMutationsCountAtom) === 0) {
    set(syncStatusAtom, "error");
  }
  return newMutation.id;
});
var dequeueMutationAtom = atom13(null, (get, set, mutationId) => {
  const queue = get(mutationQueueAtom);
  const newQueue = queue.filter((m) => m.id !== mutationId);
  set(mutationQueueAtom, newQueue);
  debug8("Dequeued mutation: %s. Queue size: %d", mutationId, newQueue.length);
  if (newQueue.length === 0 && get(pendingMutationsCountAtom) === 0 && get(lastSyncErrorAtom) === null) {
    set(syncStatusAtom, get(isOnlineAtom) ? "synced" : "offline");
  }
});
var incrementRetryCountAtom = atom13(null, (get, set, mutationId) => {
  const queue = get(mutationQueueAtom);
  const newQueue = queue.map((m) => m.id === mutationId ? {
    ...m,
    retryCount: m.retryCount + 1
  } : m);
  set(mutationQueueAtom, newQueue);
});
var getNextQueuedMutationAtom = atom13((get) => {
  const queue = get(mutationQueueAtom);
  return queue.find((m) => m.retryCount < m.maxRetries) ?? null;
});
var clearMutationQueueAtom = atom13(null, (get, set) => {
  set(mutationQueueAtom, []);
  debug8("Cleared mutation queue");
  if (get(pendingMutationsCountAtom) === 0 && get(lastSyncErrorAtom) === null) {
    set(syncStatusAtom, get(isOnlineAtom) ? "synced" : "offline");
  }
});

// src/core/interaction-store.ts
import { atom as atom14 } from "jotai";
var inputModeAtom = atom14({
  type: "normal"
});
var keyboardInteractionModeAtom = atom14("navigate");
var interactionFeedbackAtom = atom14(null);
var pendingInputResolverAtom = atom14(null);
var resetInputModeAtom = atom14(null, (_get, set) => {
  set(inputModeAtom, {
    type: "normal"
  });
  set(interactionFeedbackAtom, null);
  set(pendingInputResolverAtom, null);
});
var resetKeyboardInteractionModeAtom = atom14(null, (_get, set) => {
  set(keyboardInteractionModeAtom, "navigate");
});
var setKeyboardInteractionModeAtom = atom14(null, (_get, set, mode) => {
  set(keyboardInteractionModeAtom, mode);
});
var startPickNodeAtom = atom14(null, (_get, set, options) => {
  set(inputModeAtom, {
    type: "pickNode",
    ...options
  });
});
var startPickNodesAtom = atom14(null, (_get, set, options) => {
  set(inputModeAtom, {
    type: "pickNodes",
    ...options
  });
});
var startPickPointAtom = atom14(null, (_get, set, options) => {
  set(inputModeAtom, {
    type: "pickPoint",
    ...options
  });
});
var provideInputAtom = atom14(null, (get, set, value) => {
  set(pendingInputResolverAtom, value);
});
var updateInteractionFeedbackAtom = atom14(null, (get, set, feedback) => {
  const current = get(interactionFeedbackAtom);
  set(interactionFeedbackAtom, {
    ...current,
    ...feedback
  });
});
var isPickingModeAtom = atom14((get) => {
  const mode = get(inputModeAtom);
  return mode.type !== "normal";
});
var isPickNodeModeAtom = atom14((get) => {
  const mode = get(inputModeAtom);
  return mode.type === "pickNode" || mode.type === "pickNodes";
});

// src/core/locked-node-store.ts
import { atom as atom15 } from "jotai";
var lockedNodeIdAtom = atom15(null);
var lockedNodeDataAtom = atom15((get) => {
  const id = get(lockedNodeIdAtom);
  if (!id) return null;
  const nodes = get(uiNodesAtom);
  return nodes.find((n) => n.id === id) || null;
});
var lockedNodePageIndexAtom = atom15(0);
var lockedNodePageCountAtom = atom15(1);
var lockNodeAtom = atom15(null, (_get, set, payload) => {
  set(lockedNodeIdAtom, payload.nodeId);
  set(lockedNodePageIndexAtom, 0);
});
var unlockNodeAtom = atom15(null, (_get, set) => {
  set(lockedNodeIdAtom, null);
});
var nextLockedPageAtom = atom15(null, (get, set) => {
  const current = get(lockedNodePageIndexAtom);
  const pageCount = get(lockedNodePageCountAtom);
  set(lockedNodePageIndexAtom, (current + 1) % pageCount);
});
var prevLockedPageAtom = atom15(null, (get, set) => {
  const current = get(lockedNodePageIndexAtom);
  const pageCount = get(lockedNodePageCountAtom);
  set(lockedNodePageIndexAtom, (current - 1 + pageCount) % pageCount);
});
var goToLockedPageAtom = atom15(null, (get, set, index) => {
  const pageCount = get(lockedNodePageCountAtom);
  if (index >= 0 && index < pageCount) {
    set(lockedNodePageIndexAtom, index);
  }
});
var hasLockedNodeAtom = atom15((get) => get(lockedNodeIdAtom) !== null);

// src/core/node-type-registry.tsx
import { c as _c } from "react/compiler-runtime";
import React from "react";
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
var nodeTypeRegistry = /* @__PURE__ */ new Map();
function registerNodeType(nodeType, component) {
  nodeTypeRegistry.set(nodeType, component);
}
function registerNodeTypes(types) {
  for (const [nodeType, component] of Object.entries(types)) {
    nodeTypeRegistry.set(nodeType, component);
  }
}
function unregisterNodeType(nodeType) {
  return nodeTypeRegistry.delete(nodeType);
}
function getNodeTypeComponent(nodeType) {
  if (!nodeType) return void 0;
  return nodeTypeRegistry.get(nodeType);
}
function hasNodeTypeComponent(nodeType) {
  if (!nodeType) return false;
  return nodeTypeRegistry.has(nodeType);
}
function getRegisteredNodeTypes() {
  return Array.from(nodeTypeRegistry.keys());
}
function clearNodeTypeRegistry() {
  nodeTypeRegistry.clear();
}
var FallbackNodeTypeComponent = (t0) => {
  const $ = _c(11);
  const {
    nodeData
  } = t0;
  let t1;
  if ($[0] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
    t1 = {
      padding: "12px",
      display: "flex",
      flexDirection: "column",
      alignItems: "center",
      justifyContent: "center",
      height: "100%",
      color: "#666",
      fontSize: "12px"
    };
    $[0] = t1;
  } else {
    t1 = $[0];
  }
  const t2 = nodeData.dbData.node_type || "none";
  let t3;
  if ($[1] !== t2) {
    t3 = /* @__PURE__ */ _jsxs("div", {
      children: ["Unknown type: ", t2]
    });
    $[1] = t2;
    $[2] = t3;
  } else {
    t3 = $[2];
  }
  let t4;
  if ($[3] === /* @__PURE__ */ Symbol.for("react.memo_cache_sentinel")) {
    t4 = {
      marginTop: "4px",
      opacity: 0.7
    };
    $[3] = t4;
  } else {
    t4 = $[3];
  }
  let t5;
  if ($[4] !== nodeData.id) {
    t5 = nodeData.id.substring(0, 8);
    $[4] = nodeData.id;
    $[5] = t5;
  } else {
    t5 = $[5];
  }
  let t6;
  if ($[6] !== t5) {
    t6 = /* @__PURE__ */ _jsx("div", {
      style: t4,
      children: t5
    });
    $[6] = t5;
    $[7] = t6;
  } else {
    t6 = $[7];
  }
  let t7;
  if ($[8] !== t3 || $[9] !== t6) {
    t7 = /* @__PURE__ */ _jsxs("div", {
      style: t1,
      children: [t3, t6]
    });
    $[8] = t3;
    $[9] = t6;
    $[10] = t7;
  } else {
    t7 = $[10];
  }
  return t7;
};

// src/core/toast-store.ts
import { atom as atom16 } from "jotai";
var canvasToastAtom = atom16(null);
var showToastAtom = atom16(null, (_get, set, message) => {
  const id = `toast-${Date.now()}`;
  set(canvasToastAtom, {
    id,
    message,
    timestamp: Date.now()
  });
  setTimeout(() => {
    set(canvasToastAtom, (current) => current?.id === id ? null : current);
  }, 2e3);
});

// src/core/snap-store.ts
import { atom as atom17 } from "jotai";
var snapEnabledAtom = atom17(false);
var snapGridSizeAtom = atom17(20);
var snapTemporaryDisableAtom = atom17(false);
var isSnappingActiveAtom = atom17((get) => {
  return get(snapEnabledAtom) && !get(snapTemporaryDisableAtom);
});
function snapToGrid(pos, gridSize) {
  return {
    x: Math.round(pos.x / gridSize) * gridSize,
    y: Math.round(pos.y / gridSize) * gridSize
  };
}
function conditionalSnap(pos, gridSize, isActive) {
  return isActive ? snapToGrid(pos, gridSize) : pos;
}
function getSnapGuides(pos, gridSize, tolerance = 5) {
  const snappedX = Math.round(pos.x / gridSize) * gridSize;
  const snappedY = Math.round(pos.y / gridSize) * gridSize;
  return {
    x: Math.abs(pos.x - snappedX) < tolerance ? snappedX : null,
    y: Math.abs(pos.y - snappedY) < tolerance ? snappedY : null
  };
}
var toggleSnapAtom = atom17(null, (get, set) => {
  set(snapEnabledAtom, !get(snapEnabledAtom));
});
var setGridSizeAtom = atom17(null, (_get, set, size) => {
  set(snapGridSizeAtom, Math.max(5, Math.min(200, size)));
});
var snapAlignmentEnabledAtom = atom17(true);
var toggleAlignmentGuidesAtom = atom17(null, (get, set) => {
  set(snapAlignmentEnabledAtom, !get(snapAlignmentEnabledAtom));
});
var alignmentGuidesAtom = atom17({
  verticalGuides: [],
  horizontalGuides: []
});
var clearAlignmentGuidesAtom = atom17(null, (_get, set) => {
  set(alignmentGuidesAtom, {
    verticalGuides: [],
    horizontalGuides: []
  });
});
function findAlignmentGuides(dragged, others, tolerance = 5) {
  const verticals = /* @__PURE__ */ new Set();
  const horizontals = /* @__PURE__ */ new Set();
  const dragCX = dragged.x + dragged.width / 2;
  const dragCY = dragged.y + dragged.height / 2;
  const dragRight = dragged.x + dragged.width;
  const dragBottom = dragged.y + dragged.height;
  for (const other of others) {
    const otherCX = other.x + other.width / 2;
    const otherCY = other.y + other.height / 2;
    const otherRight = other.x + other.width;
    const otherBottom = other.y + other.height;
    if (Math.abs(dragCX - otherCX) < tolerance) verticals.add(otherCX);
    if (Math.abs(dragged.x - other.x) < tolerance) verticals.add(other.x);
    if (Math.abs(dragRight - otherRight) < tolerance) verticals.add(otherRight);
    if (Math.abs(dragged.x - otherRight) < tolerance) verticals.add(otherRight);
    if (Math.abs(dragRight - other.x) < tolerance) verticals.add(other.x);
    if (Math.abs(dragCX - other.x) < tolerance) verticals.add(other.x);
    if (Math.abs(dragCX - otherRight) < tolerance) verticals.add(otherRight);
    if (Math.abs(dragCY - otherCY) < tolerance) horizontals.add(otherCY);
    if (Math.abs(dragged.y - other.y) < tolerance) horizontals.add(other.y);
    if (Math.abs(dragBottom - otherBottom) < tolerance) horizontals.add(otherBottom);
    if (Math.abs(dragged.y - otherBottom) < tolerance) horizontals.add(otherBottom);
    if (Math.abs(dragBottom - other.y) < tolerance) horizontals.add(other.y);
    if (Math.abs(dragCY - other.y) < tolerance) horizontals.add(other.y);
    if (Math.abs(dragCY - otherBottom) < tolerance) horizontals.add(otherBottom);
  }
  return {
    verticalGuides: Array.from(verticals),
    horizontalGuides: Array.from(horizontals)
  };
}

// src/core/event-types.ts
var CanvasEventType = /* @__PURE__ */ (function(CanvasEventType2) {
  CanvasEventType2["NodeClick"] = "node:click";
  CanvasEventType2["NodeDoubleClick"] = "node:double-click";
  CanvasEventType2["NodeTripleClick"] = "node:triple-click";
  CanvasEventType2["NodeRightClick"] = "node:right-click";
  CanvasEventType2["NodeLongPress"] = "node:long-press";
  CanvasEventType2["EdgeClick"] = "edge:click";
  CanvasEventType2["EdgeDoubleClick"] = "edge:double-click";
  CanvasEventType2["EdgeRightClick"] = "edge:right-click";
  CanvasEventType2["BackgroundClick"] = "background:click";
  CanvasEventType2["BackgroundDoubleClick"] = "background:double-click";
  CanvasEventType2["BackgroundRightClick"] = "background:right-click";
  CanvasEventType2["BackgroundLongPress"] = "background:long-press";
  return CanvasEventType2;
})({});
var EVENT_TYPE_INFO = {
  [CanvasEventType.NodeClick]: {
    type: CanvasEventType.NodeClick,
    label: "Click Node",
    description: "Triggered when clicking on a node",
    category: "node"
  },
  [CanvasEventType.NodeDoubleClick]: {
    type: CanvasEventType.NodeDoubleClick,
    label: "Double-click Node",
    description: "Triggered when double-clicking on a node",
    category: "node"
  },
  [CanvasEventType.NodeTripleClick]: {
    type: CanvasEventType.NodeTripleClick,
    label: "Triple-click Node",
    description: "Triggered when triple-clicking on a node",
    category: "node"
  },
  [CanvasEventType.NodeRightClick]: {
    type: CanvasEventType.NodeRightClick,
    label: "Right-click Node",
    description: "Triggered when right-clicking on a node",
    category: "node"
  },
  [CanvasEventType.NodeLongPress]: {
    type: CanvasEventType.NodeLongPress,
    label: "Long-press Node",
    description: "Triggered when long-pressing on a node (mobile/touch)",
    category: "node"
  },
  [CanvasEventType.EdgeClick]: {
    type: CanvasEventType.EdgeClick,
    label: "Click Edge",
    description: "Triggered when clicking on an edge",
    category: "edge"
  },
  [CanvasEventType.EdgeDoubleClick]: {
    type: CanvasEventType.EdgeDoubleClick,
    label: "Double-click Edge",
    description: "Triggered when double-clicking on an edge",
    category: "edge"
  },
  [CanvasEventType.EdgeRightClick]: {
    type: CanvasEventType.EdgeRightClick,
    label: "Right-click Edge",
    description: "Triggered when right-clicking on an edge",
    category: "edge"
  },
  [CanvasEventType.BackgroundClick]: {
    type: CanvasEventType.BackgroundClick,
    label: "Click Background",
    description: "Triggered when clicking on the canvas background",
    category: "background"
  },
  [CanvasEventType.BackgroundDoubleClick]: {
    type: CanvasEventType.BackgroundDoubleClick,
    label: "Double-click Background",
    description: "Triggered when double-clicking on the canvas background",
    category: "background"
  },
  [CanvasEventType.BackgroundRightClick]: {
    type: CanvasEventType.BackgroundRightClick,
    label: "Right-click Background",
    description: "Triggered when right-clicking on the canvas background",
    category: "background"
  },
  [CanvasEventType.BackgroundLongPress]: {
    type: CanvasEventType.BackgroundLongPress,
    label: "Long-press Background",
    description: "Triggered when long-pressing on the canvas background (mobile/touch)",
    category: "background"
  }
};

// src/core/action-types.ts
var ActionCategory = /* @__PURE__ */ (function(ActionCategory2) {
  ActionCategory2["None"] = "none";
  ActionCategory2["Selection"] = "selection";
  ActionCategory2["Viewport"] = "viewport";
  ActionCategory2["Node"] = "node";
  ActionCategory2["Layout"] = "layout";
  ActionCategory2["History"] = "history";
  ActionCategory2["Custom"] = "custom";
  return ActionCategory2;
})({});
var BuiltInActionId = {
  // None
  None: "none",
  // Selection
  SelectNode: "select-node",
  SelectEdge: "select-edge",
  AddToSelection: "add-to-selection",
  ClearSelection: "clear-selection",
  DeleteSelected: "delete-selected",
  // Viewport
  FitToView: "fit-to-view",
  FitAllToView: "fit-all-to-view",
  CenterOnNode: "center-on-node",
  ResetViewport: "reset-viewport",
  // Node
  LockNode: "lock-node",
  UnlockNode: "unlock-node",
  ToggleLock: "toggle-lock",
  OpenContextMenu: "open-context-menu",
  SplitNode: "split-node",
  GroupNodes: "group-nodes",
  MergeNodes: "merge-nodes",
  // Layout
  ApplyForceLayout: "apply-force-layout",
  // History
  Undo: "undo",
  Redo: "redo",
  // Creation
  CreateNode: "create-node"
};

// src/core/settings-state-types.ts
var DEFAULT_MAPPINGS = {
  [CanvasEventType.NodeClick]: BuiltInActionId.None,
  [CanvasEventType.NodeDoubleClick]: BuiltInActionId.FitToView,
  [CanvasEventType.NodeTripleClick]: BuiltInActionId.ToggleLock,
  [CanvasEventType.NodeRightClick]: BuiltInActionId.OpenContextMenu,
  [CanvasEventType.NodeLongPress]: BuiltInActionId.OpenContextMenu,
  [CanvasEventType.EdgeClick]: BuiltInActionId.SelectEdge,
  [CanvasEventType.EdgeDoubleClick]: BuiltInActionId.None,
  [CanvasEventType.EdgeRightClick]: BuiltInActionId.OpenContextMenu,
  [CanvasEventType.BackgroundClick]: BuiltInActionId.ClearSelection,
  [CanvasEventType.BackgroundDoubleClick]: BuiltInActionId.FitAllToView,
  [CanvasEventType.BackgroundRightClick]: BuiltInActionId.None,
  [CanvasEventType.BackgroundLongPress]: BuiltInActionId.CreateNode
};

// src/core/actions-node.ts
function registerSelectionActions() {
  registerAction({
    id: BuiltInActionId.SelectNode,
    label: "Select Node",
    description: "Select this node (replacing current selection)",
    category: ActionCategory.Selection,
    icon: "pointer",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.selectNode(context.nodeId);
      }
    }
  });
  registerAction({
    id: BuiltInActionId.SelectEdge,
    label: "Select Edge",
    description: "Select this edge",
    category: ActionCategory.Selection,
    icon: "git-commit",
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.edgeId) {
        helpers.selectEdge(context.edgeId);
      }
    }
  });
  registerAction({
    id: BuiltInActionId.AddToSelection,
    label: "Add to Selection",
    description: "Add this node to the current selection",
    category: ActionCategory.Selection,
    icon: "plus-square",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.addToSelection(context.nodeId);
      }
    }
  });
  registerAction({
    id: BuiltInActionId.ClearSelection,
    label: "Clear Selection",
    description: "Deselect all nodes",
    category: ActionCategory.Selection,
    icon: "x-square",
    isBuiltIn: true,
    handler: (_context, helpers) => {
      helpers.clearSelection();
    }
  });
  registerAction({
    id: BuiltInActionId.DeleteSelected,
    label: "Delete Selected",
    description: "Delete all selected nodes",
    category: ActionCategory.Selection,
    icon: "trash-2",
    isBuiltIn: true,
    handler: async (_context, helpers) => {
      const selectedIds = helpers.getSelectedNodeIds();
      for (const nodeId of selectedIds) {
        await helpers.deleteNode(nodeId);
      }
    }
  });
}
function registerNodeActions() {
  registerAction({
    id: BuiltInActionId.LockNode,
    label: "Lock Node",
    description: "Prevent this node from being moved",
    category: ActionCategory.Node,
    icon: "lock",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.lockNode(context.nodeId);
      }
    }
  });
  registerAction({
    id: BuiltInActionId.UnlockNode,
    label: "Unlock Node",
    description: "Allow this node to be moved",
    category: ActionCategory.Node,
    icon: "unlock",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.unlockNode(context.nodeId);
      }
    }
  });
  registerAction({
    id: BuiltInActionId.ToggleLock,
    label: "Toggle Lock",
    description: "Toggle whether this node can be moved",
    category: ActionCategory.Node,
    icon: "lock",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.toggleLock(context.nodeId);
      }
    }
  });
  registerAction({
    id: BuiltInActionId.OpenContextMenu,
    label: "Open Context Menu",
    description: "Show the context menu for this node",
    category: ActionCategory.Node,
    icon: "more-vertical",
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (helpers.openContextMenu) {
        helpers.openContextMenu(context.screenPosition, context.nodeId);
      }
    }
  });
  registerAction({
    id: BuiltInActionId.CreateNode,
    label: "Create Node",
    description: "Create a new node at this position",
    category: ActionCategory.Node,
    icon: "plus",
    isBuiltIn: true,
    handler: async (context, helpers) => {
      if (helpers.createNode) {
        await helpers.createNode(context.worldPosition);
      }
    }
  });
  registerAction({
    id: BuiltInActionId.SplitNode,
    label: "Split Node",
    description: "Split a node into two separate nodes",
    category: ActionCategory.Node,
    icon: "split",
    isBuiltIn: true,
    handler: async (context, helpers) => {
      if (helpers.splitNode && context.nodeId) {
        await helpers.splitNode(context.nodeId);
      }
    }
  });
  registerAction({
    id: BuiltInActionId.GroupNodes,
    label: "Group Nodes",
    description: "Group selected nodes into a parent container",
    category: ActionCategory.Node,
    icon: "group",
    isBuiltIn: true,
    handler: async (context, helpers) => {
      if (helpers.groupNodes) {
        await helpers.groupNodes(context.selectedNodeIds ?? helpers.getSelectedNodeIds());
      }
    }
  });
  registerAction({
    id: BuiltInActionId.MergeNodes,
    label: "Merge Nodes",
    description: "Merge selected nodes into one",
    category: ActionCategory.Node,
    icon: "merge",
    isBuiltIn: true,
    handler: async (context, helpers) => {
      if (helpers.mergeNodes) {
        await helpers.mergeNodes(context.selectedNodeIds ?? helpers.getSelectedNodeIds());
      }
    }
  });
}

// src/core/actions-viewport.ts
function registerViewportActions() {
  registerAction({
    id: BuiltInActionId.FitToView,
    label: "Fit to View",
    description: "Zoom and pan to fit this node in view",
    category: ActionCategory.Viewport,
    icon: "maximize-2",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.centerOnNode(context.nodeId);
      }
    }
  });
  registerAction({
    id: BuiltInActionId.FitAllToView,
    label: "Fit All to View",
    description: "Zoom and pan to fit all nodes in view",
    category: ActionCategory.Viewport,
    icon: "maximize",
    isBuiltIn: true,
    handler: (_context, helpers) => {
      helpers.fitToBounds("graph");
    }
  });
  registerAction({
    id: BuiltInActionId.CenterOnNode,
    label: "Center on Node",
    description: "Center the viewport on this node",
    category: ActionCategory.Viewport,
    icon: "crosshair",
    requiresNode: true,
    isBuiltIn: true,
    handler: (context, helpers) => {
      if (context.nodeId) {
        helpers.centerOnNode(context.nodeId);
      }
    }
  });
  registerAction({
    id: BuiltInActionId.ResetViewport,
    label: "Reset Viewport",
    description: "Reset zoom to 100% and center on origin",
    category: ActionCategory.Viewport,
    icon: "home",
    isBuiltIn: true,
    handler: (_context, helpers) => {
      helpers.resetViewport();
    }
  });
}
function registerHistoryActions() {
  registerAction({
    id: BuiltInActionId.Undo,
    label: "Undo",
    description: "Undo the last action",
    category: ActionCategory.History,
    icon: "undo-2",
    isBuiltIn: true,
    handler: (_context, helpers) => {
      if (helpers.canUndo()) {
        helpers.undo();
      }
    }
  });
  registerAction({
    id: BuiltInActionId.Redo,
    label: "Redo",
    description: "Redo the last undone action",
    category: ActionCategory.History,
    icon: "redo-2",
    isBuiltIn: true,
    handler: (_context, helpers) => {
      if (helpers.canRedo()) {
        helpers.redo();
      }
    }
  });
  registerAction({
    id: BuiltInActionId.ApplyForceLayout,
    label: "Apply Force Layout",
    description: "Automatically arrange nodes using force-directed layout",
    category: ActionCategory.Layout,
    icon: "layout-grid",
    isBuiltIn: true,
    handler: async (_context, helpers) => {
      await helpers.applyForceLayout();
    }
  });
}

// src/core/built-in-actions.ts
function registerBuiltInActions() {
  registerAction({
    id: BuiltInActionId.None,
    label: "None",
    description: "Do nothing",
    category: ActionCategory.None,
    icon: "ban",
    isBuiltIn: true,
    handler: () => {
    }
  });
  registerSelectionActions();
  registerNodeActions();
  registerViewportActions();
  registerHistoryActions();
}

// src/core/action-registry.ts
var actionRegistry = /* @__PURE__ */ new Map();
function registerAction(action) {
  actionRegistry.set(action.id, action);
}
function getAction(id) {
  return actionRegistry.get(id);
}
function hasAction(id) {
  return actionRegistry.has(id);
}
function getAllActions() {
  return Array.from(actionRegistry.values());
}
function getActionsByCategory(category) {
  return getAllActions().filter((action) => action.category === category);
}
function unregisterAction(id) {
  return actionRegistry.delete(id);
}
function clearActions() {
  actionRegistry.clear();
}
registerBuiltInActions();
function getActionsByCategories() {
  const categoryLabels = {
    [ActionCategory.None]: "None",
    [ActionCategory.Selection]: "Selection",
    [ActionCategory.Viewport]: "Viewport",
    [ActionCategory.Node]: "Node",
    [ActionCategory.Layout]: "Layout",
    [ActionCategory.History]: "History",
    [ActionCategory.Custom]: "Custom"
  };
  const categoryOrder = [ActionCategory.None, ActionCategory.Selection, ActionCategory.Viewport, ActionCategory.Node, ActionCategory.Layout, ActionCategory.History, ActionCategory.Custom];
  return categoryOrder.map((category) => ({
    category,
    label: categoryLabels[category],
    actions: getActionsByCategory(category)
  })).filter((group) => group.actions.length > 0);
}

// src/core/action-executor.ts
var debug9 = createDebug("actions");
async function executeAction(actionId, context, helpers) {
  if (actionId === BuiltInActionId.None) {
    return {
      success: true,
      actionId
    };
  }
  const action = getAction(actionId);
  if (!action) {
    debug9.warn("Action not found: %s", actionId);
    return {
      success: false,
      actionId,
      error: new Error(`Action not found: ${actionId}`)
    };
  }
  if (action.requiresNode && !context.nodeId) {
    debug9.warn("Action %s requires a node context", actionId);
    return {
      success: false,
      actionId,
      error: new Error(`Action ${actionId} requires a node context`)
    };
  }
  try {
    const result = action.handler(context, helpers);
    if (result instanceof Promise) {
      await result;
    }
    return {
      success: true,
      actionId
    };
  } catch (error) {
    debug9.error("Error executing action %s: %O", actionId, error);
    return {
      success: false,
      actionId,
      error: error instanceof Error ? error : new Error(String(error))
    };
  }
}
function createActionContext(eventType, screenEvent, worldPosition, options) {
  return {
    eventType,
    nodeId: options?.nodeId,
    nodeData: options?.nodeData,
    edgeId: options?.edgeId,
    edgeData: options?.edgeData,
    worldPosition,
    screenPosition: {
      x: screenEvent.clientX,
      y: screenEvent.clientY
    },
    modifiers: {
      shift: false,
      ctrl: false,
      alt: false,
      meta: false
    }
  };
}
function createActionContextFromReactEvent(eventType, event, worldPosition, options) {
  return {
    eventType,
    nodeId: options?.nodeId,
    nodeData: options?.nodeData,
    edgeId: options?.edgeId,
    edgeData: options?.edgeData,
    worldPosition,
    screenPosition: {
      x: event.clientX,
      y: event.clientY
    },
    modifiers: {
      shift: event.shiftKey,
      ctrl: event.ctrlKey,
      alt: event.altKey,
      meta: event.metaKey
    }
  };
}
function createActionContextFromTouchEvent(eventType, touch, worldPosition, options) {
  return {
    eventType,
    nodeId: options?.nodeId,
    nodeData: options?.nodeData,
    edgeId: options?.edgeId,
    edgeData: options?.edgeData,
    worldPosition,
    screenPosition: {
      x: touch.clientX,
      y: touch.clientY
    },
    modifiers: {
      shift: false,
      ctrl: false,
      alt: false,
      meta: false
    }
  };
}
function buildActionHelpers(store, options = {}) {
  return {
    selectNode: (nodeId) => store.set(selectSingleNodeAtom, nodeId),
    addToSelection: (nodeId) => store.set(addNodesToSelectionAtom, [nodeId]),
    clearSelection: () => store.set(clearSelectionAtom),
    getSelectedNodeIds: () => Array.from(store.get(selectedNodeIdsAtom)),
    fitToBounds: (mode, padding) => {
      const fitMode = mode === "graph" ? FitToBoundsMode.Graph : FitToBoundsMode.Selection;
      store.set(fitToBoundsAtom, {
        mode: fitMode,
        padding
      });
    },
    centerOnNode: (nodeId) => store.set(centerOnNodeAtom, nodeId),
    resetViewport: () => store.set(resetViewportAtom),
    lockNode: (nodeId) => store.set(lockNodeAtom, {
      nodeId
    }),
    unlockNode: (_nodeId) => store.set(unlockNodeAtom),
    toggleLock: (nodeId) => {
      const currentLockedId = store.get(lockedNodeIdAtom);
      if (currentLockedId === nodeId) {
        store.set(unlockNodeAtom);
      } else {
        store.set(lockNodeAtom, {
          nodeId
        });
      }
    },
    deleteNode: async (nodeId) => {
      if (options.onDeleteNode) {
        await options.onDeleteNode(nodeId);
      } else {
        debug9.warn("deleteNode called but onDeleteNode callback not provided");
      }
    },
    isNodeLocked: (nodeId) => store.get(lockedNodeIdAtom) === nodeId,
    applyForceLayout: async () => {
      if (options.onApplyForceLayout) {
        await options.onApplyForceLayout();
      } else {
        debug9.warn("applyForceLayout called but onApplyForceLayout callback not provided");
      }
    },
    undo: () => store.set(undoAtom),
    redo: () => store.set(redoAtom),
    canUndo: () => store.get(canUndoAtom),
    canRedo: () => store.get(canRedoAtom),
    selectEdge: (edgeId) => store.set(selectEdgeAtom, edgeId),
    clearEdgeSelection: () => store.set(clearEdgeSelectionAtom),
    openContextMenu: options.onOpenContextMenu,
    createNode: options.onCreateNode
  };
}

// src/core/settings-store.ts
import { atom as atom18 } from "jotai";
import { atomWithStorage } from "jotai/utils";

// src/core/settings-presets.ts
var BUILT_IN_PRESETS = [{
  id: "default",
  name: "Default",
  description: "Standard canvas interactions",
  isBuiltIn: true,
  mappings: DEFAULT_MAPPINGS
}, {
  id: "minimal",
  name: "Minimal",
  description: "Only essential selection and context menu actions",
  isBuiltIn: true,
  mappings: {
    [CanvasEventType.NodeClick]: BuiltInActionId.None,
    [CanvasEventType.NodeDoubleClick]: BuiltInActionId.None,
    [CanvasEventType.NodeTripleClick]: BuiltInActionId.None,
    [CanvasEventType.NodeRightClick]: BuiltInActionId.OpenContextMenu,
    [CanvasEventType.NodeLongPress]: BuiltInActionId.OpenContextMenu,
    [CanvasEventType.EdgeClick]: BuiltInActionId.SelectEdge,
    [CanvasEventType.EdgeDoubleClick]: BuiltInActionId.None,
    [CanvasEventType.EdgeRightClick]: BuiltInActionId.None,
    [CanvasEventType.BackgroundClick]: BuiltInActionId.ClearSelection,
    [CanvasEventType.BackgroundDoubleClick]: BuiltInActionId.None,
    [CanvasEventType.BackgroundRightClick]: BuiltInActionId.None,
    [CanvasEventType.BackgroundLongPress]: BuiltInActionId.None
  }
}, {
  id: "power-user",
  name: "Power User",
  description: "Quick actions for experienced users",
  isBuiltIn: true,
  mappings: {
    [CanvasEventType.NodeClick]: BuiltInActionId.None,
    [CanvasEventType.NodeDoubleClick]: BuiltInActionId.ToggleLock,
    [CanvasEventType.NodeTripleClick]: BuiltInActionId.DeleteSelected,
    [CanvasEventType.NodeRightClick]: BuiltInActionId.OpenContextMenu,
    [CanvasEventType.NodeLongPress]: BuiltInActionId.AddToSelection,
    [CanvasEventType.EdgeClick]: BuiltInActionId.SelectEdge,
    [CanvasEventType.EdgeDoubleClick]: BuiltInActionId.None,
    [CanvasEventType.EdgeRightClick]: BuiltInActionId.OpenContextMenu,
    [CanvasEventType.BackgroundClick]: BuiltInActionId.ClearSelection,
    [CanvasEventType.BackgroundDoubleClick]: BuiltInActionId.CreateNode,
    [CanvasEventType.BackgroundRightClick]: BuiltInActionId.OpenContextMenu,
    [CanvasEventType.BackgroundLongPress]: BuiltInActionId.ApplyForceLayout
  }
}];
function getActionForEvent(mappings, event) {
  return mappings[event] || BuiltInActionId.None;
}

// src/core/settings-store.ts
var debug10 = createDebug("settings");
var DEFAULT_STATE = {
  mappings: DEFAULT_MAPPINGS,
  activePresetId: "default",
  customPresets: [],
  isPanelOpen: false,
  virtualizationEnabled: true
};
var canvasSettingsAtom = atomWithStorage("@blinksgg/canvas/settings", DEFAULT_STATE);
var eventMappingsAtom = atom18((get) => get(canvasSettingsAtom).mappings);
var activePresetIdAtom = atom18((get) => get(canvasSettingsAtom).activePresetId);
var allPresetsAtom = atom18((get) => {
  const state = get(canvasSettingsAtom);
  return [...BUILT_IN_PRESETS, ...state.customPresets];
});
var activePresetAtom = atom18((get) => {
  const presetId = get(activePresetIdAtom);
  if (!presetId) return null;
  const allPresets = get(allPresetsAtom);
  return allPresets.find((p) => p.id === presetId) || null;
});
var isPanelOpenAtom = atom18((get) => get(canvasSettingsAtom).isPanelOpen);
var virtualizationEnabledAtom = atom18((get) => get(canvasSettingsAtom).virtualizationEnabled ?? true);
var hasUnsavedChangesAtom = atom18((get) => {
  const state = get(canvasSettingsAtom);
  const activePreset = get(activePresetAtom);
  if (!activePreset) return true;
  const events = Object.values(CanvasEventType);
  return events.some((event) => state.mappings[event] !== activePreset.mappings[event]);
});
var setEventMappingAtom = atom18(null, (get, set, {
  event,
  actionId
}) => {
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    mappings: {
      ...current.mappings,
      [event]: actionId
    },
    // Clear active preset since mappings have changed
    activePresetId: null
  });
});
var applyPresetAtom = atom18(null, (get, set, presetId) => {
  const allPresets = get(allPresetsAtom);
  const preset = allPresets.find((p) => p.id === presetId);
  if (!preset) {
    debug10.warn("Preset not found: %s", presetId);
    return;
  }
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    mappings: {
      ...preset.mappings
    },
    activePresetId: presetId
  });
});
var saveAsPresetAtom = atom18(null, (get, set, {
  name,
  description
}) => {
  const current = get(canvasSettingsAtom);
  const id = `custom-${Date.now()}`;
  const newPreset = {
    id,
    name,
    description,
    mappings: {
      ...current.mappings
    },
    isBuiltIn: false
  };
  set(canvasSettingsAtom, {
    ...current,
    customPresets: [...current.customPresets, newPreset],
    activePresetId: id
  });
  return id;
});
var updatePresetAtom = atom18(null, (get, set, presetId) => {
  const current = get(canvasSettingsAtom);
  const presetIndex = current.customPresets.findIndex((p) => p.id === presetId);
  if (presetIndex === -1) {
    debug10.warn("Cannot update preset: %s (not found or built-in)", presetId);
    return;
  }
  const updatedPresets = [...current.customPresets];
  updatedPresets[presetIndex] = {
    ...updatedPresets[presetIndex],
    mappings: {
      ...current.mappings
    }
  };
  set(canvasSettingsAtom, {
    ...current,
    customPresets: updatedPresets,
    activePresetId: presetId
  });
});
var deletePresetAtom = atom18(null, (get, set, presetId) => {
  const current = get(canvasSettingsAtom);
  const newCustomPresets = current.customPresets.filter((p) => p.id !== presetId);
  if (newCustomPresets.length === current.customPresets.length) {
    debug10.warn("Cannot delete preset: %s (not found or built-in)", presetId);
    return;
  }
  const newActiveId = current.activePresetId === presetId ? "default" : current.activePresetId;
  const newMappings = newActiveId === "default" ? DEFAULT_MAPPINGS : current.mappings;
  set(canvasSettingsAtom, {
    ...current,
    customPresets: newCustomPresets,
    activePresetId: newActiveId,
    mappings: newMappings
  });
});
var resetSettingsAtom = atom18(null, (get, set) => {
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    mappings: DEFAULT_MAPPINGS,
    activePresetId: "default"
  });
});
var togglePanelAtom = atom18(null, (get, set) => {
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    isPanelOpen: !current.isPanelOpen
  });
});
var setPanelOpenAtom = atom18(null, (get, set, isOpen) => {
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    isPanelOpen: isOpen
  });
});
var setVirtualizationEnabledAtom = atom18(null, (get, set, enabled) => {
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    virtualizationEnabled: enabled
  });
});
var toggleVirtualizationAtom = atom18(null, (get, set) => {
  const current = get(canvasSettingsAtom);
  set(canvasSettingsAtom, {
    ...current,
    virtualizationEnabled: !(current.virtualizationEnabled ?? true)
  });
});

// src/core/canvas-serializer.ts
import Graph4 from "graphology";
var SNAPSHOT_VERSION = 1;
function exportGraph(store, metadata) {
  const graph = store.get(graphAtom);
  const zoom = store.get(zoomAtom);
  const pan = store.get(panAtom);
  const collapsed = store.get(collapsedGroupsAtom);
  const nodes = [];
  const groups = [];
  const seenGroupParents = /* @__PURE__ */ new Set();
  graph.forEachNode((nodeId, attrs) => {
    const a = attrs;
    nodes.push({
      id: nodeId,
      position: {
        x: a.x,
        y: a.y
      },
      dimensions: {
        width: a.width,
        height: a.height
      },
      size: a.size,
      color: a.color,
      zIndex: a.zIndex,
      label: a.label,
      parentId: a.parentId,
      dbData: a.dbData
    });
    if (a.parentId) {
      const key = `${nodeId}:${a.parentId}`;
      if (!seenGroupParents.has(key)) {
        seenGroupParents.add(key);
        groups.push({
          nodeId,
          parentId: a.parentId,
          isCollapsed: collapsed.has(a.parentId)
        });
      }
    }
  });
  const edges = [];
  graph.forEachEdge((key, attrs, source, target) => {
    const a = attrs;
    edges.push({
      key,
      sourceId: source,
      targetId: target,
      attributes: {
        weight: a.weight,
        type: a.type,
        color: a.color,
        label: a.label
      },
      dbData: a.dbData
    });
  });
  return {
    version: SNAPSHOT_VERSION,
    exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
    nodes,
    edges,
    groups,
    viewport: {
      zoom,
      pan: {
        ...pan
      }
    },
    metadata
  };
}
function importGraph(store, snapshot, options = {}) {
  const {
    clearExisting = true,
    offsetPosition,
    remapIds = false
  } = options;
  const idMap = /* @__PURE__ */ new Map();
  if (remapIds) {
    for (const node of snapshot.nodes) {
      idMap.set(node.id, crypto.randomUUID());
    }
    for (const edge of snapshot.edges) {
      idMap.set(edge.key, crypto.randomUUID());
    }
  }
  const remap = (id) => idMap.get(id) ?? id;
  let graph;
  if (clearExisting) {
    graph = new Graph4(graphOptions);
  } else {
    graph = store.get(graphAtom);
  }
  const ox = offsetPosition?.x ?? 0;
  const oy = offsetPosition?.y ?? 0;
  for (const node of snapshot.nodes) {
    const nodeId = remap(node.id);
    const parentId = node.parentId ? remap(node.parentId) : void 0;
    const dbData = remapIds ? {
      ...node.dbData,
      id: nodeId
    } : node.dbData;
    const attrs = {
      x: node.position.x + ox,
      y: node.position.y + oy,
      width: node.dimensions.width,
      height: node.dimensions.height,
      size: node.size,
      color: node.color,
      zIndex: node.zIndex,
      label: node.label,
      parentId,
      dbData
    };
    graph.addNode(nodeId, attrs);
  }
  for (const edge of snapshot.edges) {
    const edgeKey = remap(edge.key);
    const sourceId = remap(edge.sourceId);
    const targetId = remap(edge.targetId);
    if (!graph.hasNode(sourceId) || !graph.hasNode(targetId)) continue;
    const dbData = remapIds ? {
      ...edge.dbData,
      id: edgeKey,
      source_node_id: sourceId,
      target_node_id: targetId
    } : edge.dbData;
    const attrs = {
      weight: edge.attributes.weight,
      type: edge.attributes.type,
      color: edge.attributes.color,
      label: edge.attributes.label,
      dbData
    };
    graph.addEdgeWithKey(edgeKey, sourceId, targetId, attrs);
  }
  store.set(graphAtom, graph);
  store.set(graphUpdateVersionAtom, (v) => v + 1);
  store.set(nodePositionUpdateCounterAtom, (c) => c + 1);
  const collapsedSet = /* @__PURE__ */ new Set();
  for (const group of snapshot.groups) {
    if (group.isCollapsed) {
      collapsedSet.add(remap(group.parentId));
    }
  }
  store.set(collapsedGroupsAtom, collapsedSet);
  store.set(zoomAtom, snapshot.viewport.zoom);
  store.set(panAtom, {
    ...snapshot.viewport.pan
  });
}
function validateSnapshot(data) {
  const errors = [];
  if (!data || typeof data !== "object") {
    return {
      valid: false,
      errors: ["Snapshot must be a non-null object"]
    };
  }
  const obj = data;
  if (obj.version !== SNAPSHOT_VERSION) {
    errors.push(`Expected version ${SNAPSHOT_VERSION}, got ${String(obj.version)}`);
  }
  if (typeof obj.exportedAt !== "string") {
    errors.push('Missing or invalid "exportedAt" (expected ISO string)');
  }
  if (!Array.isArray(obj.nodes)) {
    errors.push('Missing or invalid "nodes" (expected array)');
  } else {
    for (let i = 0; i < obj.nodes.length; i++) {
      const node = obj.nodes[i];
      if (!node || typeof node !== "object") {
        errors.push(`nodes[${i}]: expected object`);
        continue;
      }
      if (typeof node.id !== "string") errors.push(`nodes[${i}]: missing "id"`);
      if (!node.position || typeof node.position !== "object") errors.push(`nodes[${i}]: missing "position"`);
      if (!node.dimensions || typeof node.dimensions !== "object") errors.push(`nodes[${i}]: missing "dimensions"`);
      if (!node.dbData || typeof node.dbData !== "object") errors.push(`nodes[${i}]: missing "dbData"`);
    }
  }
  if (!Array.isArray(obj.edges)) {
    errors.push('Missing or invalid "edges" (expected array)');
  } else {
    for (let i = 0; i < obj.edges.length; i++) {
      const edge = obj.edges[i];
      if (!edge || typeof edge !== "object") {
        errors.push(`edges[${i}]: expected object`);
        continue;
      }
      if (typeof edge.key !== "string") errors.push(`edges[${i}]: missing "key"`);
      if (typeof edge.sourceId !== "string") errors.push(`edges[${i}]: missing "sourceId"`);
      if (typeof edge.targetId !== "string") errors.push(`edges[${i}]: missing "targetId"`);
      if (!edge.dbData || typeof edge.dbData !== "object") errors.push(`edges[${i}]: missing "dbData"`);
    }
  }
  if (!Array.isArray(obj.groups)) {
    errors.push('Missing or invalid "groups" (expected array)');
  }
  if (!obj.viewport || typeof obj.viewport !== "object") {
    errors.push('Missing or invalid "viewport" (expected object)');
  } else {
    const vp = obj.viewport;
    if (typeof vp.zoom !== "number") errors.push('viewport: missing "zoom"');
    if (!vp.pan || typeof vp.pan !== "object") errors.push('viewport: missing "pan"');
  }
  return {
    valid: errors.length === 0,
    errors
  };
}

// src/core/clipboard-store.ts
import { atom as atom19 } from "jotai";
var debug11 = createDebug("clipboard");
var PASTE_OFFSET = {
  x: 50,
  y: 50
};
var clipboardAtom = atom19(null);
var hasClipboardContentAtom = atom19((get) => get(clipboardAtom) !== null);
var clipboardNodeCountAtom = atom19((get) => {
  const clipboard = get(clipboardAtom);
  return clipboard?.nodes.length ?? 0;
});
function calculateBounds2(nodes) {
  if (nodes.length === 0) {
    return {
      minX: 0,
      minY: 0,
      maxX: 0,
      maxY: 0
    };
  }
  let minX = Infinity;
  let minY = Infinity;
  let maxX = -Infinity;
  let maxY = -Infinity;
  for (const node of nodes) {
    minX = Math.min(minX, node.attrs.x);
    minY = Math.min(minY, node.attrs.y);
    maxX = Math.max(maxX, node.attrs.x + node.attrs.width);
    maxY = Math.max(maxY, node.attrs.y + node.attrs.height);
  }
  return {
    minX,
    minY,
    maxX,
    maxY
  };
}
function generatePasteId(index) {
  return `paste-${Date.now()}-${index}-${Math.random().toString(36).slice(2, 8)}`;
}
var copyToClipboardAtom = atom19(null, (get, set, nodeIds) => {
  const selectedIds = nodeIds ?? Array.from(get(selectedNodeIdsAtom));
  if (selectedIds.length === 0) {
    debug11("Nothing to copy - no nodes selected");
    return;
  }
  const graph = get(graphAtom);
  const selectedSet = new Set(selectedIds);
  const nodes = [];
  const edges = [];
  for (const nodeId of selectedIds) {
    if (!graph.hasNode(nodeId)) {
      debug11("Node %s not found in graph, skipping", nodeId);
      continue;
    }
    const attrs = graph.getNodeAttributes(nodeId);
    nodes.push({
      attrs: {
        ...attrs
      },
      dbData: {
        ...attrs.dbData
      }
    });
  }
  graph.forEachEdge((edgeKey, attrs, source, target) => {
    if (selectedSet.has(source) && selectedSet.has(target)) {
      edges.push({
        source,
        target,
        attrs: {
          ...attrs
        },
        dbData: {
          ...attrs.dbData
        }
      });
    }
  });
  const bounds = calculateBounds2(nodes);
  const clipboardData = {
    nodes,
    edges,
    bounds,
    timestamp: Date.now()
  };
  set(clipboardAtom, clipboardData);
  debug11("Copied %d nodes and %d edges to clipboard", nodes.length, edges.length);
});
var cutToClipboardAtom = atom19(null, (get, set, nodeIds) => {
  const selectedIds = nodeIds ?? Array.from(get(selectedNodeIdsAtom));
  if (selectedIds.length === 0) return;
  set(copyToClipboardAtom, selectedIds);
  set(pushHistoryAtom, "Cut nodes");
  for (const nodeId of selectedIds) {
    set(optimisticDeleteNodeAtom, {
      nodeId
    });
  }
  set(clearSelectionAtom);
  debug11("Cut %d nodes \u2014 copied to clipboard and deleted from graph", selectedIds.length);
});
var pasteFromClipboardAtom = atom19(null, (get, set, offset) => {
  const clipboard = get(clipboardAtom);
  if (!clipboard || clipboard.nodes.length === 0) {
    debug11("Nothing to paste - clipboard empty");
    return [];
  }
  const pasteOffset = offset ?? PASTE_OFFSET;
  const graph = get(graphAtom);
  set(pushHistoryAtom, "Paste nodes");
  const idMap = /* @__PURE__ */ new Map();
  const newNodeIds = [];
  for (let i = 0; i < clipboard.nodes.length; i++) {
    const nodeData = clipboard.nodes[i];
    const newId = generatePasteId(i);
    idMap.set(nodeData.dbData.id, newId);
    newNodeIds.push(newId);
    const newDbNode = {
      ...nodeData.dbData,
      id: newId,
      created_at: (/* @__PURE__ */ new Date()).toISOString(),
      updated_at: (/* @__PURE__ */ new Date()).toISOString(),
      ui_properties: {
        ...nodeData.dbData.ui_properties || {},
        x: nodeData.attrs.x + pasteOffset.x,
        y: nodeData.attrs.y + pasteOffset.y
      }
    };
    debug11("Pasting node %s -> %s at (%d, %d)", nodeData.dbData.id, newId, nodeData.attrs.x + pasteOffset.x, nodeData.attrs.y + pasteOffset.y);
    set(addNodeToLocalGraphAtom, newDbNode);
  }
  for (const edgeData of clipboard.edges) {
    const newSourceId = idMap.get(edgeData.source);
    const newTargetId = idMap.get(edgeData.target);
    if (!newSourceId || !newTargetId) {
      debug11("Edge %s: source or target not found in id map, skipping", edgeData.dbData.id);
      continue;
    }
    const newEdgeId = generatePasteId(clipboard.edges.indexOf(edgeData) + clipboard.nodes.length);
    const newDbEdge = {
      ...edgeData.dbData,
      id: newEdgeId,
      source_node_id: newSourceId,
      target_node_id: newTargetId,
      created_at: (/* @__PURE__ */ new Date()).toISOString(),
      updated_at: (/* @__PURE__ */ new Date()).toISOString()
    };
    debug11("Pasting edge %s -> %s (from %s to %s)", edgeData.dbData.id, newEdgeId, newSourceId, newTargetId);
    set(addEdgeToLocalGraphAtom, newDbEdge);
  }
  set(clearSelectionAtom);
  set(addNodesToSelectionAtom, newNodeIds);
  debug11("Pasted %d nodes and %d edges", newNodeIds.length, clipboard.edges.length);
  return newNodeIds;
});
var duplicateSelectionAtom = atom19(null, (get, set) => {
  set(copyToClipboardAtom);
  return set(pasteFromClipboardAtom);
});
var clearClipboardAtom = atom19(null, (_get, set) => {
  set(clipboardAtom, null);
  debug11("Clipboard cleared");
});

// src/core/virtualization-store.ts
import { atom as atom20 } from "jotai";

// src/core/spatial-index.ts
var SpatialGrid = class {
  constructor(cellSize = 500) {
    /** cell key → set of node IDs in that cell */
    __publicField(this, "cells", /* @__PURE__ */ new Map());
    /** node ID → entry data (for update/remove) */
    __publicField(this, "entries", /* @__PURE__ */ new Map());
    this.cellSize = cellSize;
  }
  /** Number of tracked entries */
  get size() {
    return this.entries.size;
  }
  cellKey(cx, cy) {
    return `${cx},${cy}`;
  }
  getCellRange(x, y, w, h) {
    const cs = this.cellSize;
    return {
      minCX: Math.floor(x / cs),
      minCY: Math.floor(y / cs),
      maxCX: Math.floor((x + w) / cs),
      maxCY: Math.floor((y + h) / cs)
    };
  }
  /**
   * Insert a node into the index.
   * If the node already exists, it is updated.
   */
  insert(id, x, y, width, height) {
    if (this.entries.has(id)) {
      this.update(id, x, y, width, height);
      return;
    }
    const entry = {
      id,
      x,
      y,
      width,
      height
    };
    this.entries.set(id, entry);
    const {
      minCX,
      minCY,
      maxCX,
      maxCY
    } = this.getCellRange(x, y, width, height);
    for (let cx = minCX; cx <= maxCX; cx++) {
      for (let cy = minCY; cy <= maxCY; cy++) {
        const key = this.cellKey(cx, cy);
        let cell = this.cells.get(key);
        if (!cell) {
          cell = /* @__PURE__ */ new Set();
          this.cells.set(key, cell);
        }
        cell.add(id);
      }
    }
  }
  /**
   * Update a node's position/dimensions.
   */
  update(id, x, y, width, height) {
    const prev = this.entries.get(id);
    if (!prev) {
      this.insert(id, x, y, width, height);
      return;
    }
    const prevRange = this.getCellRange(prev.x, prev.y, prev.width, prev.height);
    const newRange = this.getCellRange(x, y, width, height);
    prev.x = x;
    prev.y = y;
    prev.width = width;
    prev.height = height;
    if (prevRange.minCX === newRange.minCX && prevRange.minCY === newRange.minCY && prevRange.maxCX === newRange.maxCX && prevRange.maxCY === newRange.maxCY) {
      return;
    }
    for (let cx = prevRange.minCX; cx <= prevRange.maxCX; cx++) {
      for (let cy = prevRange.minCY; cy <= prevRange.maxCY; cy++) {
        const key = this.cellKey(cx, cy);
        const cell = this.cells.get(key);
        if (cell) {
          cell.delete(id);
          if (cell.size === 0) this.cells.delete(key);
        }
      }
    }
    for (let cx = newRange.minCX; cx <= newRange.maxCX; cx++) {
      for (let cy = newRange.minCY; cy <= newRange.maxCY; cy++) {
        const key = this.cellKey(cx, cy);
        let cell = this.cells.get(key);
        if (!cell) {
          cell = /* @__PURE__ */ new Set();
          this.cells.set(key, cell);
        }
        cell.add(id);
      }
    }
  }
  /**
   * Remove a node from the index.
   */
  remove(id) {
    const entry = this.entries.get(id);
    if (!entry) return;
    const {
      minCX,
      minCY,
      maxCX,
      maxCY
    } = this.getCellRange(entry.x, entry.y, entry.width, entry.height);
    for (let cx = minCX; cx <= maxCX; cx++) {
      for (let cy = minCY; cy <= maxCY; cy++) {
        const key = this.cellKey(cx, cy);
        const cell = this.cells.get(key);
        if (cell) {
          cell.delete(id);
          if (cell.size === 0) this.cells.delete(key);
        }
      }
    }
    this.entries.delete(id);
  }
  /**
   * Query all node IDs whose bounding box overlaps the given bounds.
   * Returns a Set for O(1) membership checks.
   */
  query(bounds) {
    const result = /* @__PURE__ */ new Set();
    const {
      minCX,
      minCY,
      maxCX,
      maxCY
    } = this.getCellRange(bounds.minX, bounds.minY, bounds.maxX - bounds.minX, bounds.maxY - bounds.minY);
    for (let cx = minCX; cx <= maxCX; cx++) {
      for (let cy = minCY; cy <= maxCY; cy++) {
        const cell = this.cells.get(this.cellKey(cx, cy));
        if (!cell) continue;
        for (const id of cell) {
          if (result.has(id)) continue;
          const entry = this.entries.get(id);
          const entryRight = entry.x + entry.width;
          const entryBottom = entry.y + entry.height;
          if (entry.x <= bounds.maxX && entryRight >= bounds.minX && entry.y <= bounds.maxY && entryBottom >= bounds.minY) {
            result.add(id);
          }
        }
      }
    }
    return result;
  }
  /**
   * Clear all entries.
   */
  clear() {
    this.cells.clear();
    this.entries.clear();
  }
  /**
   * Check if a node is tracked.
   */
  has(id) {
    return this.entries.has(id);
  }
};

// src/core/virtualization-store.ts
var VIRTUALIZATION_BUFFER = 200;
var spatialIndexAtom = atom20((get) => {
  get(graphUpdateVersionAtom);
  get(nodePositionUpdateCounterAtom);
  const graph = get(graphAtom);
  const grid = new SpatialGrid(500);
  graph.forEachNode((nodeId, attrs) => {
    const a = attrs;
    grid.insert(nodeId, a.x, a.y, a.width || 200, a.height || 100);
  });
  return grid;
});
var visibleBoundsAtom = atom20((get) => {
  const viewport = get(viewportRectAtom);
  const pan = get(panAtom);
  const zoom = get(zoomAtom);
  if (!viewport || zoom === 0) {
    return null;
  }
  const buffer = VIRTUALIZATION_BUFFER;
  return {
    minX: (-buffer - pan.x) / zoom,
    minY: (-buffer - pan.y) / zoom,
    maxX: (viewport.width + buffer - pan.x) / zoom,
    maxY: (viewport.height + buffer - pan.y) / zoom
  };
});
var visibleNodeKeysAtom = atom20((get) => {
  const end = canvasMark("virtualization-cull");
  const enabled = get(virtualizationEnabledAtom);
  const allKeys = get(nodeKeysAtom);
  if (!enabled) {
    end();
    return allKeys;
  }
  const bounds = get(visibleBoundsAtom);
  if (!bounds) {
    end();
    return allKeys;
  }
  const grid = get(spatialIndexAtom);
  const visibleSet = grid.query(bounds);
  const result = allKeys.filter((k) => visibleSet.has(k));
  end();
  return result;
});
var visibleEdgeKeysAtom = atom20((get) => {
  const enabled = get(virtualizationEnabledAtom);
  const allEdgeKeys = get(edgeKeysAtom);
  const edgeCreation = get(edgeCreationAtom);
  const remap = get(collapsedEdgeRemapAtom);
  const tempEdgeKey = edgeCreation.isCreating ? "temp-creating-edge" : null;
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  const filteredEdges = allEdgeKeys.filter((edgeKey) => {
    const source = graph.source(edgeKey);
    const target = graph.target(edgeKey);
    const effectiveSource = remap.get(source) ?? source;
    const effectiveTarget = remap.get(target) ?? target;
    if (effectiveSource === effectiveTarget) return false;
    return true;
  });
  if (!enabled) {
    return tempEdgeKey ? [...filteredEdges, tempEdgeKey] : filteredEdges;
  }
  const visibleNodeKeys = get(visibleNodeKeysAtom);
  const visibleNodeSet = new Set(visibleNodeKeys);
  const visibleEdges = filteredEdges.filter((edgeKey) => {
    const source = graph.source(edgeKey);
    const target = graph.target(edgeKey);
    const effectiveSource = remap.get(source) ?? source;
    const effectiveTarget = remap.get(target) ?? target;
    return visibleNodeSet.has(effectiveSource) && visibleNodeSet.has(effectiveTarget);
  });
  return tempEdgeKey ? [...visibleEdges, tempEdgeKey] : visibleEdges;
});
var virtualizationMetricsAtom = atom20((get) => {
  const enabled = get(virtualizationEnabledAtom);
  const totalNodes = get(nodeKeysAtom).length;
  const totalEdges = get(edgeKeysAtom).length;
  const visibleNodes = get(visibleNodeKeysAtom).length;
  const visibleEdges = get(visibleEdgeKeysAtom).length;
  const bounds = get(visibleBoundsAtom);
  return {
    enabled,
    totalNodes,
    totalEdges,
    visibleNodes,
    visibleEdges,
    culledNodes: totalNodes - visibleNodes,
    culledEdges: totalEdges - visibleEdges,
    bounds
  };
});

// src/core/canvas-api.ts
function createCanvasAPI(store, options = {}) {
  const helpers = buildActionHelpers(store, options);
  const api = {
    // Selection
    selectNode: (id) => store.set(selectSingleNodeAtom, id),
    addToSelection: (ids) => store.set(addNodesToSelectionAtom, ids),
    clearSelection: () => store.set(clearSelectionAtom),
    getSelectedNodeIds: () => Array.from(store.get(selectedNodeIdsAtom)),
    selectEdge: (edgeId) => store.set(selectEdgeAtom, edgeId),
    clearEdgeSelection: () => store.set(clearEdgeSelectionAtom),
    getSelectedEdgeId: () => store.get(selectedEdgeIdAtom),
    // Viewport
    getZoom: () => store.get(zoomAtom),
    setZoom: (zoom) => store.set(zoomAtom, zoom),
    getPan: () => store.get(panAtom),
    setPan: (pan) => store.set(panAtom, pan),
    resetViewport: () => store.set(resetViewportAtom),
    fitToBounds: (mode, padding) => {
      const fitMode = mode === "graph" ? FitToBoundsMode.Graph : FitToBoundsMode.Selection;
      store.set(fitToBoundsAtom, {
        mode: fitMode,
        padding
      });
    },
    centerOnNode: (nodeId) => store.set(centerOnNodeAtom, nodeId),
    // Graph
    addNode: (node) => store.set(addNodeToLocalGraphAtom, node),
    removeNode: (nodeId) => store.set(optimisticDeleteNodeAtom, {
      nodeId
    }),
    addEdge: (edge) => store.set(addEdgeToLocalGraphAtom, edge),
    removeEdge: (edgeKey) => store.set(optimisticDeleteEdgeAtom, {
      edgeKey
    }),
    getNodeKeys: () => store.get(nodeKeysAtom),
    getEdgeKeys: () => store.get(edgeKeysAtom),
    getNodeAttributes: (id) => {
      const graph = store.get(graphAtom);
      return graph.hasNode(id) ? graph.getNodeAttributes(id) : void 0;
    },
    // History
    undo: () => store.set(undoAtom),
    redo: () => store.set(redoAtom),
    canUndo: () => store.get(canUndoAtom),
    canRedo: () => store.get(canRedoAtom),
    recordSnapshot: (label) => store.set(pushHistoryAtom, label),
    clearHistory: () => store.set(clearHistoryAtom),
    // Clipboard
    copy: () => store.set(copyToClipboardAtom),
    cut: () => store.set(cutToClipboardAtom),
    paste: () => store.set(pasteFromClipboardAtom),
    duplicate: () => store.set(duplicateSelectionAtom),
    hasClipboardContent: () => store.get(clipboardAtom) !== null,
    // Snap
    isSnapEnabled: () => store.get(snapEnabledAtom),
    toggleSnap: () => store.set(toggleSnapAtom),
    getSnapGridSize: () => store.get(snapGridSizeAtom),
    // Virtualization
    isVirtualizationEnabled: () => store.get(virtualizationEnabledAtom),
    getVisibleNodeKeys: () => store.get(visibleNodeKeysAtom),
    getVisibleEdgeKeys: () => store.get(visibleEdgeKeysAtom),
    // Actions
    executeAction: (actionId, context) => executeAction(actionId, context, helpers),
    executeEventAction: (event, context) => {
      const mappings = store.get(eventMappingsAtom);
      const actionId = getActionForEvent(mappings, event);
      return executeAction(actionId, context, helpers);
    },
    // Serialization
    exportSnapshot: (metadata) => exportGraph(store, metadata),
    importSnapshot: (snapshot, options2) => importGraph(store, snapshot, options2),
    validateSnapshot: (data) => validateSnapshot(data)
  };
  return api;
}

// src/core/port-types.ts
function calculatePortPosition(nodeX, nodeY, nodeWidth, nodeHeight, port) {
  switch (port.side) {
    case "left":
      return {
        x: nodeX,
        y: nodeY + nodeHeight * port.position
      };
    case "right":
      return {
        x: nodeX + nodeWidth,
        y: nodeY + nodeHeight * port.position
      };
    case "top":
      return {
        x: nodeX + nodeWidth * port.position,
        y: nodeY
      };
    case "bottom":
      return {
        x: nodeX + nodeWidth * port.position,
        y: nodeY + nodeHeight
      };
  }
}
var DEFAULT_PORT = {
  id: "default",
  type: "bidirectional",
  side: "right",
  position: 0.5
};
function getNodePorts(ports) {
  if (ports && ports.length > 0) {
    return ports;
  }
  return [DEFAULT_PORT];
}
function canPortAcceptConnection(port, currentConnections, isSource) {
  if (isSource && port.type === "input") {
    return false;
  }
  if (!isSource && port.type === "output") {
    return false;
  }
  if (port.maxConnections !== void 0 && currentConnections >= port.maxConnections) {
    return false;
  }
  return true;
}
function arePortsCompatible(sourcePort, targetPort) {
  if (sourcePort.type === "input") {
    return false;
  }
  if (targetPort.type === "output") {
    return false;
  }
  return true;
}

// src/core/input-classifier.ts
function classifyPointer(e) {
  const source = pointerTypeToSource(e.pointerType);
  return {
    source,
    pointerId: e.pointerId,
    pressure: e.pressure,
    tiltX: e.tiltX,
    tiltY: e.tiltY,
    isPrimary: e.isPrimary,
    rawPointerType: e.pointerType
  };
}
function pointerTypeToSource(pointerType) {
  switch (pointerType) {
    case "pen":
      return "pencil";
    case "touch":
      return "finger";
    case "mouse":
      return "mouse";
    default:
      return "mouse";
  }
}
function detectInputCapabilities() {
  if (typeof window === "undefined") {
    return {
      hasTouch: false,
      hasStylus: false,
      hasMouse: true,
      hasCoarsePointer: false
    };
  }
  const hasTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0;
  const supportsMatchMedia = typeof window.matchMedia === "function";
  const hasCoarsePointer = supportsMatchMedia ? window.matchMedia("(pointer: coarse)").matches : false;
  const hasFinePointer = supportsMatchMedia ? window.matchMedia("(pointer: fine)").matches : true;
  const hasMouse = hasFinePointer || !hasTouch;
  return {
    hasTouch,
    hasStylus: false,
    // Set to true on first pen event
    hasMouse,
    hasCoarsePointer
  };
}
function getGestureThresholds(source) {
  switch (source) {
    case "finger":
      return {
        dragThreshold: 10,
        tapThreshold: 10,
        longPressDuration: 600,
        longPressMoveLimit: 10
      };
    case "pencil":
      return {
        dragThreshold: 2,
        tapThreshold: 3,
        longPressDuration: 500,
        longPressMoveLimit: 5
      };
    case "mouse":
      return {
        dragThreshold: 3,
        tapThreshold: 5,
        longPressDuration: 0,
        // Mouse uses right-click instead
        longPressMoveLimit: 0
      };
  }
}
var HIT_TARGET_SIZES = {
  /** Minimum touch target (Apple HIG: 44pt) */
  finger: 44,
  /** Stylus target (precise, can use smaller targets) */
  pencil: 24,
  /** Mouse target (hover-discoverable, smallest) */
  mouse: 16
};
function getHitTargetSize(source) {
  return HIT_TARGET_SIZES[source];
}

// src/core/input-store.ts
import { atom as atom21 } from "jotai";
var activePointersAtom = atom21(/* @__PURE__ */ new Map());
var primaryInputSourceAtom = atom21("mouse");
var inputCapabilitiesAtom = atom21(detectInputCapabilities());
var isStylusActiveAtom = atom21((get) => {
  const pointers = get(activePointersAtom);
  for (const [, pointer] of pointers) {
    if (pointer.source === "pencil") return true;
  }
  return false;
});
var isMultiTouchAtom = atom21((get) => {
  const pointers = get(activePointersAtom);
  let fingerCount = 0;
  for (const [, pointer] of pointers) {
    if (pointer.source === "finger") fingerCount++;
  }
  return fingerCount > 1;
});
var fingerCountAtom = atom21((get) => {
  const pointers = get(activePointersAtom);
  let count = 0;
  for (const [, pointer] of pointers) {
    if (pointer.source === "finger") count++;
  }
  return count;
});
var isTouchDeviceAtom = atom21((get) => {
  const caps = get(inputCapabilitiesAtom);
  return caps.hasTouch;
});
var pointerDownAtom = atom21(null, (get, set, pointer) => {
  const pointers = new Map(get(activePointersAtom));
  pointers.set(pointer.pointerId, pointer);
  set(activePointersAtom, pointers);
  set(primaryInputSourceAtom, pointer.source);
  if (pointer.source === "pencil") {
    const caps = get(inputCapabilitiesAtom);
    if (!caps.hasStylus) {
      set(inputCapabilitiesAtom, {
        ...caps,
        hasStylus: true
      });
    }
  }
});
var pointerUpAtom = atom21(null, (get, set, pointerId) => {
  const pointers = new Map(get(activePointersAtom));
  pointers.delete(pointerId);
  set(activePointersAtom, pointers);
});
var clearPointersAtom = atom21(null, (_get, set) => {
  set(activePointersAtom, /* @__PURE__ */ new Map());
});

// src/core/selection-path-store.ts
import { atom as atom22 } from "jotai";
var selectionPathAtom = atom22(null);
var isSelectingAtom = atom22((get) => get(selectionPathAtom) !== null);
var startSelectionAtom = atom22(null, (_get, set, {
  type,
  point
}) => {
  set(selectionPathAtom, {
    type,
    points: [point]
  });
});
var updateSelectionAtom = atom22(null, (get, set, point) => {
  const current = get(selectionPathAtom);
  if (!current) return;
  if (current.type === "rect") {
    set(selectionPathAtom, {
      ...current,
      points: [current.points[0], point]
    });
  } else {
    set(selectionPathAtom, {
      ...current,
      points: [...current.points, point]
    });
  }
});
var cancelSelectionAtom = atom22(null, (_get, set) => {
  set(selectionPathAtom, null);
});
var endSelectionAtom = atom22(null, (get, set) => {
  const path = get(selectionPathAtom);
  if (!path || path.points.length < 2) {
    set(selectionPathAtom, null);
    return;
  }
  const nodes = get(uiNodesAtom);
  const selectedIds = [];
  if (path.type === "rect") {
    const [p1, p2] = [path.points[0], path.points[path.points.length - 1]];
    const minX = Math.min(p1.x, p2.x);
    const maxX = Math.max(p1.x, p2.x);
    const minY = Math.min(p1.y, p2.y);
    const maxY = Math.max(p1.y, p2.y);
    for (const node of nodes) {
      const nodeRight = node.position.x + (node.width ?? 200);
      const nodeBottom = node.position.y + (node.height ?? 100);
      if (node.position.x < maxX && nodeRight > minX && node.position.y < maxY && nodeBottom > minY) {
        selectedIds.push(node.id);
      }
    }
  } else {
    const polygon = path.points;
    for (const node of nodes) {
      const cx = node.position.x + (node.width ?? 200) / 2;
      const cy = node.position.y + (node.height ?? 100) / 2;
      if (pointInPolygon(cx, cy, polygon)) {
        selectedIds.push(node.id);
      }
    }
  }
  set(selectedNodeIdsAtom, new Set(selectedIds));
  set(selectionPathAtom, null);
});
var selectionRectAtom = atom22((get) => {
  const path = get(selectionPathAtom);
  if (!path || path.type !== "rect" || path.points.length < 2) return null;
  const [p1, p2] = [path.points[0], path.points[path.points.length - 1]];
  return {
    x: Math.min(p1.x, p2.x),
    y: Math.min(p1.y, p2.y),
    width: Math.abs(p2.x - p1.x),
    height: Math.abs(p2.y - p1.y)
  };
});
function pointInPolygon(px, py, polygon) {
  let inside = false;
  const n = polygon.length;
  for (let i = 0, j = n - 1; i < n; j = i++) {
    const xi = polygon[i].x;
    const yi = polygon[i].y;
    const xj = polygon[j].x;
    const yj = polygon[j].y;
    if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi) {
      inside = !inside;
    }
  }
  return inside;
}

// src/core/search-store.ts
import { atom as atom23 } from "jotai";
var searchQueryAtom = atom23("");
var setSearchQueryAtom = atom23(null, (_get, set, query) => {
  set(searchQueryAtom, query);
  set(highlightedSearchIndexAtom, 0);
});
var clearSearchAtom = atom23(null, (_get, set) => {
  set(searchQueryAtom, "");
  set(highlightedSearchIndexAtom, 0);
});
function fuzzyMatch(query, ...haystacks) {
  const tokens = query.toLowerCase().split(/\s+/).filter(Boolean);
  if (tokens.length === 0) return false;
  const combined = haystacks.join(" ").toLowerCase();
  return tokens.every((token) => combined.includes(token));
}
var searchResultsAtom = atom23((get) => {
  const query = get(searchQueryAtom).trim();
  if (!query) return /* @__PURE__ */ new Set();
  const nodes = get(uiNodesAtom);
  const matches = /* @__PURE__ */ new Set();
  for (const node of nodes) {
    if (fuzzyMatch(query, node.label || "", node.dbData.node_type || "", node.id)) {
      matches.add(node.id);
    }
  }
  return matches;
});
var searchResultsArrayAtom = atom23((get) => {
  return Array.from(get(searchResultsAtom));
});
var searchResultCountAtom = atom23((get) => {
  return get(searchResultsAtom).size;
});
var searchEdgeResultsAtom = atom23((get) => {
  const query = get(searchQueryAtom).trim();
  if (!query) return /* @__PURE__ */ new Set();
  get(graphUpdateVersionAtom);
  const graph = get(graphAtom);
  const matches = /* @__PURE__ */ new Set();
  graph.forEachEdge((edgeKey, attrs) => {
    const label = attrs.label || "";
    const edgeType = attrs.dbData?.edge_type || "";
    if (fuzzyMatch(query, label, edgeType, edgeKey)) {
      matches.add(edgeKey);
    }
  });
  return matches;
});
var searchEdgeResultCountAtom = atom23((get) => {
  return get(searchEdgeResultsAtom).size;
});
var isFilterActiveAtom = atom23((get) => {
  return get(searchQueryAtom).trim().length > 0;
});
var searchTotalResultCountAtom = atom23((get) => {
  return get(searchResultCountAtom) + get(searchEdgeResultCountAtom);
});
var highlightedSearchIndexAtom = atom23(0);
var nextSearchResultAtom = atom23(null, (get, set) => {
  const results = get(searchResultsArrayAtom);
  if (results.length === 0) return;
  const currentIndex = get(highlightedSearchIndexAtom);
  const nextIndex = (currentIndex + 1) % results.length;
  set(highlightedSearchIndexAtom, nextIndex);
  const nodeId = results[nextIndex];
  set(centerOnNodeAtom, nodeId);
  set(selectSingleNodeAtom, nodeId);
});
var prevSearchResultAtom = atom23(null, (get, set) => {
  const results = get(searchResultsArrayAtom);
  if (results.length === 0) return;
  const currentIndex = get(highlightedSearchIndexAtom);
  const prevIndex = (currentIndex - 1 + results.length) % results.length;
  set(highlightedSearchIndexAtom, prevIndex);
  const nodeId = results[prevIndex];
  set(centerOnNodeAtom, nodeId);
  set(selectSingleNodeAtom, nodeId);
});
var highlightedSearchNodeIdAtom = atom23((get) => {
  const results = get(searchResultsArrayAtom);
  if (results.length === 0) return null;
  const index = get(highlightedSearchIndexAtom);
  return results[index] ?? null;
});

// src/core/gesture-rules-defaults.ts
var MODIFIER_KEYS = ["shift", "ctrl", "alt", "meta"];
var SOURCE_LABELS = {
  mouse: "Mouse",
  pencil: "Pencil",
  finger: "Touch"
};
var GESTURE_LABELS = {
  tap: "Tap",
  "double-tap": "Double-tap",
  "triple-tap": "Triple-tap",
  drag: "Drag",
  "long-press": "Long-press",
  "right-click": "Right-click",
  pinch: "Pinch",
  scroll: "Scroll"
};
var TARGET_LABELS = {
  node: "node",
  edge: "edge",
  port: "port",
  "resize-handle": "resize handle",
  background: "background"
};
var BUTTON_LABELS = {
  0: "Left",
  1: "Middle",
  2: "Right"
};
function formatRuleLabel(pattern) {
  const parts = [];
  if (pattern.modifiers) {
    const mods = MODIFIER_KEYS.filter((k) => pattern.modifiers[k]).map((k) => k.charAt(0).toUpperCase() + k.slice(1));
    if (mods.length) parts.push(mods.join("+"));
  }
  if (pattern.button !== void 0 && pattern.button !== 0) {
    parts.push(BUTTON_LABELS[pattern.button]);
  }
  if (pattern.source) {
    parts.push(SOURCE_LABELS[pattern.source]);
  }
  if (pattern.gesture) {
    parts.push(GESTURE_LABELS[pattern.gesture] ?? pattern.gesture);
  }
  if (pattern.target) {
    parts.push("on " + (TARGET_LABELS[pattern.target] ?? pattern.target));
  }
  if (parts.length === 0) return "Any gesture";
  if (pattern.modifiers) {
    const modCount = MODIFIER_KEYS.filter((k) => pattern.modifiers[k]).length;
    if (modCount > 0 && parts.length > modCount) {
      const modPart = parts.slice(0, 1).join("");
      const rest = parts.slice(1).join(" ").toLowerCase();
      return `${modPart} + ${rest}`;
    }
  }
  return parts.join(" ");
}
function mergeRules(defaults, overrides) {
  const overrideMap = new Map(overrides.map((r) => [r.id, r]));
  const result = [];
  for (const rule of defaults) {
    const override = overrideMap.get(rule.id);
    if (override) {
      result.push(override);
      overrideMap.delete(rule.id);
    } else {
      result.push(rule);
    }
  }
  for (const rule of overrideMap.values()) {
    result.push(rule);
  }
  return result;
}
var DEFAULT_GESTURE_RULES = [
  // ── Tap gestures ──────────────────────────────────────────────
  {
    id: "tap-node",
    pattern: {
      gesture: "tap",
      target: "node"
    },
    actionId: "select-node"
  },
  {
    id: "tap-edge",
    pattern: {
      gesture: "tap",
      target: "edge"
    },
    actionId: "select-edge"
  },
  {
    id: "tap-port",
    pattern: {
      gesture: "tap",
      target: "port"
    },
    actionId: "select-node"
  },
  {
    id: "tap-bg",
    pattern: {
      gesture: "tap",
      target: "background"
    },
    actionId: "clear-selection"
  },
  // ── Double-tap ────────────────────────────────────────────────
  {
    id: "dtap-node",
    pattern: {
      gesture: "double-tap",
      target: "node"
    },
    actionId: "fit-to-view"
  },
  {
    id: "dtap-bg",
    pattern: {
      gesture: "double-tap",
      target: "background"
    },
    actionId: "fit-all-to-view"
  },
  // ── Triple-tap ────────────────────────────────────────────────
  {
    id: "ttap-node",
    pattern: {
      gesture: "triple-tap",
      target: "node"
    },
    actionId: "toggle-lock"
  },
  // ── Left-button drag ──────────────────────────────────────────
  {
    id: "drag-node",
    pattern: {
      gesture: "drag",
      target: "node"
    },
    actionId: "move-node"
  },
  {
    id: "drag-port",
    pattern: {
      gesture: "drag",
      target: "port"
    },
    actionId: "create-edge"
  },
  {
    id: "drag-bg-finger",
    pattern: {
      gesture: "drag",
      target: "background",
      source: "finger"
    },
    actionId: "pan"
  },
  {
    id: "drag-bg-mouse",
    pattern: {
      gesture: "drag",
      target: "background",
      source: "mouse"
    },
    actionId: "pan"
  },
  {
    id: "drag-bg-pencil",
    pattern: {
      gesture: "drag",
      target: "background",
      source: "pencil"
    },
    actionId: "lasso-select"
  },
  // ── Shift+drag overrides ──────────────────────────────────────
  {
    id: "shift-drag-bg",
    pattern: {
      gesture: "drag",
      target: "background",
      modifiers: {
        shift: true
      }
    },
    actionId: "rect-select"
  },
  // ── Right-click tap (context menu) ────────────────────────────
  {
    id: "rc-node",
    pattern: {
      gesture: "tap",
      target: "node",
      button: 2
    },
    actionId: "open-context-menu"
  },
  {
    id: "rc-edge",
    pattern: {
      gesture: "tap",
      target: "edge",
      button: 2
    },
    actionId: "open-context-menu"
  },
  {
    id: "rc-bg",
    pattern: {
      gesture: "tap",
      target: "background",
      button: 2
    },
    actionId: "open-context-menu"
  },
  // ── Long-press ────────────────────────────────────────────────
  {
    id: "lp-node",
    pattern: {
      gesture: "long-press",
      target: "node"
    },
    actionId: "open-context-menu"
  },
  {
    id: "lp-bg-finger",
    pattern: {
      gesture: "long-press",
      target: "background",
      source: "finger"
    },
    actionId: "create-node"
  },
  // ── Right-button drag (defaults to none — consumers override) ─
  {
    id: "rdrag-node",
    pattern: {
      gesture: "drag",
      target: "node",
      button: 2
    },
    actionId: "none"
  },
  {
    id: "rdrag-bg",
    pattern: {
      gesture: "drag",
      target: "background",
      button: 2
    },
    actionId: "none"
  },
  // ── Middle-button drag (defaults to none) ─────────────────────
  {
    id: "mdrag-node",
    pattern: {
      gesture: "drag",
      target: "node",
      button: 1
    },
    actionId: "none"
  },
  {
    id: "mdrag-bg",
    pattern: {
      gesture: "drag",
      target: "background",
      button: 1
    },
    actionId: "none"
  },
  // ── Zoom ──────────────────────────────────────────────────────
  {
    id: "pinch-bg",
    pattern: {
      gesture: "pinch",
      target: "background"
    },
    actionId: "zoom"
  },
  {
    id: "scroll-any",
    pattern: {
      gesture: "scroll"
    },
    actionId: "zoom"
  },
  // ── Split ─────────────────────────────────────────────────────
  {
    id: "pinch-node",
    pattern: {
      gesture: "pinch",
      target: "node"
    },
    actionId: "split-node"
  }
];

// src/core/gesture-rules.ts
var MODIFIER_KEYS2 = ["shift", "ctrl", "alt", "meta"];
function matchSpecificity(pattern, desc) {
  let score = 0;
  if (pattern.gesture !== void 0) {
    if (pattern.gesture !== desc.gesture) return -1;
    score += 32;
  }
  if (pattern.target !== void 0) {
    if (pattern.target !== desc.target) return -1;
    score += 16;
  }
  if (pattern.source !== void 0) {
    if (pattern.source !== desc.source) return -1;
    score += 4;
  }
  if (pattern.button !== void 0) {
    if (pattern.button !== (desc.button ?? 0)) return -1;
    score += 2;
  }
  if (pattern.modifiers !== void 0) {
    const dm = desc.modifiers ?? {};
    for (const key of MODIFIER_KEYS2) {
      const required = pattern.modifiers[key];
      if (required === void 0) continue;
      const actual = dm[key] ?? false;
      if (required !== actual) return -1;
      score += 8;
    }
  }
  return score;
}
var PALM_REJECTION_RULE = {
  id: "__palm-rejection__",
  pattern: {},
  actionId: "none",
  label: "Palm rejection"
};
function resolveGesture(desc, rules, options) {
  const palmRejection = options?.palmRejection !== false;
  if (palmRejection && desc.isStylusActive && desc.source === "finger") {
    if (desc.gesture === "tap" || desc.gesture === "long-press" || desc.gesture === "double-tap" || desc.gesture === "triple-tap") {
      return {
        actionId: "none",
        rule: PALM_REJECTION_RULE,
        score: Infinity
      };
    }
    if (desc.gesture === "drag" && desc.target !== "background") {
      return resolveGesture({
        ...desc,
        target: "background",
        isStylusActive: false
      }, rules, {
        palmRejection: false
      });
    }
  }
  let best = null;
  for (const rule of rules) {
    const specificity = matchSpecificity(rule.pattern, desc);
    if (specificity < 0) continue;
    const effectiveScore = specificity * 1e3 + (rule.priority ?? 0);
    if (!best || effectiveScore > best.score) {
      best = {
        actionId: rule.actionId,
        rule,
        score: effectiveScore
      };
    }
  }
  return best;
}
function buildRuleIndex(rules) {
  const buckets = /* @__PURE__ */ new Map();
  const wildcardRules = [];
  for (const rule of rules) {
    const key = rule.pattern.gesture;
    if (key === void 0) {
      wildcardRules.push(rule);
    } else {
      const bucket = buckets.get(key);
      if (bucket) {
        bucket.push(rule);
      } else {
        buckets.set(key, [rule]);
      }
    }
  }
  const index = /* @__PURE__ */ new Map();
  if (wildcardRules.length > 0) {
    for (const [key, bucket] of buckets) {
      index.set(key, bucket.concat(wildcardRules));
    }
    index.set("__wildcard__", wildcardRules);
  } else {
    for (const [key, bucket] of buckets) {
      index.set(key, bucket);
    }
  }
  return index;
}
function resolveGestureIndexed(desc, index, options) {
  const rules = index.get(desc.gesture) ?? index.get("__wildcard__") ?? [];
  return resolveGesture(desc, rules, options);
}

// src/core/gesture-rule-store.ts
import { atom as atom24 } from "jotai";
import { atomWithStorage as atomWithStorage2 } from "jotai/utils";
var DEFAULT_RULE_STATE = {
  customRules: [],
  palmRejection: true
};
var gestureRuleSettingsAtom = atomWithStorage2("canvas-gesture-rules", DEFAULT_RULE_STATE);
var consumerGestureRulesAtom = atom24([]);
var gestureRulesAtom = atom24((get) => {
  const settings = get(gestureRuleSettingsAtom);
  const consumerRules = get(consumerGestureRulesAtom);
  let rules = mergeRules(DEFAULT_GESTURE_RULES, settings.customRules);
  if (consumerRules.length > 0) {
    rules = mergeRules(rules, consumerRules);
  }
  return rules;
});
var gestureRuleIndexAtom = atom24((get) => {
  return buildRuleIndex(get(gestureRulesAtom));
});
var palmRejectionEnabledAtom = atom24((get) => get(gestureRuleSettingsAtom).palmRejection, (get, set, enabled) => {
  const current = get(gestureRuleSettingsAtom);
  set(gestureRuleSettingsAtom, {
    ...current,
    palmRejection: enabled
  });
});
var addGestureRuleAtom = atom24(null, (get, set, rule) => {
  const current = get(gestureRuleSettingsAtom);
  const existing = current.customRules.findIndex((r) => r.id === rule.id);
  const newRules = [...current.customRules];
  if (existing >= 0) {
    newRules[existing] = rule;
  } else {
    newRules.push(rule);
  }
  set(gestureRuleSettingsAtom, {
    ...current,
    customRules: newRules
  });
});
var removeGestureRuleAtom = atom24(null, (get, set, ruleId) => {
  const current = get(gestureRuleSettingsAtom);
  set(gestureRuleSettingsAtom, {
    ...current,
    customRules: current.customRules.filter((r) => r.id !== ruleId)
  });
});
var updateGestureRuleAtom = atom24(null, (get, set, {
  id,
  updates
}) => {
  const current = get(gestureRuleSettingsAtom);
  const index = current.customRules.findIndex((r) => r.id === id);
  if (index < 0) return;
  const newRules = [...current.customRules];
  newRules[index] = {
    ...newRules[index],
    ...updates
  };
  set(gestureRuleSettingsAtom, {
    ...current,
    customRules: newRules
  });
});
var resetGestureRulesAtom = atom24(null, (get, set) => {
  const current = get(gestureRuleSettingsAtom);
  set(gestureRuleSettingsAtom, {
    ...current,
    customRules: []
  });
});

// src/core/external-keyboard-store.ts
import { atom as atom25 } from "jotai";
var hasExternalKeyboardAtom = atom25(false);
var watchExternalKeyboardAtom = atom25(null, (get, set) => {
  if (typeof window === "undefined") return;
  const handler = (e) => {
    if (e.key && e.key.length === 1 || ["Tab", "Escape", "Enter", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
      set(hasExternalKeyboardAtom, true);
      window.removeEventListener("keydown", handler);
    }
  };
  window.addEventListener("keydown", handler);
  return () => window.removeEventListener("keydown", handler);
});

// src/core/plugin-types.ts
var PluginError = class extends Error {
  constructor(message, pluginId, code) {
    super(`[Plugin "${pluginId}"] ${message}`);
    this.pluginId = pluginId;
    this.code = code;
    this.name = "PluginError";
  }
};

// src/gestures/types.ts
var NO_MODIFIERS = Object.freeze({
  shift: false,
  ctrl: false,
  alt: false,
  meta: false
});
var NO_HELD_KEYS = Object.freeze({
  byKey: Object.freeze({}),
  byCode: Object.freeze({})
});

// src/gestures/dispatcher.ts
var handlers = /* @__PURE__ */ new Map();
function registerAction2(actionId, handler) {
  handlers.set(actionId, handler);
}
function unregisterAction2(actionId) {
  handlers.delete(actionId);
}

// src/commands/registry.ts
var CommandRegistry = class {
  constructor() {
    __publicField(this, "commands", /* @__PURE__ */ new Map());
    __publicField(this, "aliases", /* @__PURE__ */ new Map());
  }
  // alias -> command name
  /**
   * Register a command with the registry.
   * @param command The command definition to register
   * @throws Error if command name or alias already exists
   */
  register(command) {
    if (this.commands.has(command.name)) {
      throw new Error(`Command "${command.name}" is already registered`);
    }
    this.commands.set(command.name, command);
    if (command.aliases) {
      for (const alias of command.aliases) {
        if (this.aliases.has(alias)) {
          throw new Error(`Alias "${alias}" is already registered for command "${this.aliases.get(alias)}"`);
        }
        if (this.commands.has(alias)) {
          throw new Error(`Alias "${alias}" conflicts with existing command name`);
        }
        this.aliases.set(alias, command.name);
      }
    }
  }
  /**
   * Unregister a command by name.
   * @param name The command name to remove
   */
  unregister(name) {
    const command = this.commands.get(name);
    if (command) {
      if (command.aliases) {
        for (const alias of command.aliases) {
          this.aliases.delete(alias);
        }
      }
      this.commands.delete(name);
    }
  }
  /**
   * Get a command by name or alias.
   * @param nameOrAlias Command name or alias
   * @returns The command definition or undefined if not found
   */
  get(nameOrAlias) {
    const direct = this.commands.get(nameOrAlias);
    if (direct) return direct;
    const commandName = this.aliases.get(nameOrAlias);
    if (commandName) {
      return this.commands.get(commandName);
    }
    return void 0;
  }
  /**
   * Check if a command exists by name or alias.
   * @param nameOrAlias Command name or alias
   */
  has(nameOrAlias) {
    return this.commands.has(nameOrAlias) || this.aliases.has(nameOrAlias);
  }
  /**
   * Search for commands matching a query.
   * Searches command names, aliases, and descriptions.
   * @param query Search query (case-insensitive)
   * @returns Array of matching commands, sorted by relevance
   */
  search(query) {
    if (!query.trim()) {
      return this.all();
    }
    const lowerQuery = query.toLowerCase().trim();
    const results = [];
    const commands = Array.from(this.commands.values());
    for (const command of commands) {
      let score = 0;
      if (command.name.toLowerCase() === lowerQuery) {
        score = 100;
      } else if (command.name.toLowerCase().startsWith(lowerQuery)) {
        score = 80;
      } else if (command.name.toLowerCase().includes(lowerQuery)) {
        score = 60;
      } else if (command.aliases?.some((a) => a.toLowerCase() === lowerQuery)) {
        score = 90;
      } else if (command.aliases?.some((a) => a.toLowerCase().startsWith(lowerQuery))) {
        score = 70;
      } else if (command.aliases?.some((a) => a.toLowerCase().includes(lowerQuery))) {
        score = 50;
      } else if (command.description.toLowerCase().includes(lowerQuery)) {
        score = 30;
      }
      if (score > 0) {
        results.push({
          command,
          score
        });
      }
    }
    return results.sort((a, b) => b.score - a.score || a.command.name.localeCompare(b.command.name)).map((r) => r.command);
  }
  /**
   * Get all registered commands.
   * @returns Array of all commands, sorted alphabetically by name
   */
  all() {
    return Array.from(this.commands.values()).sort((a, b) => a.name.localeCompare(b.name));
  }
  /**
   * Get commands by category.
   * @param category The category to filter by
   * @returns Array of commands in the category
   */
  byCategory(category) {
    return this.all().filter((cmd) => cmd.category === category);
  }
  /**
   * Get all available categories.
   * @returns Array of unique categories
   */
  categories() {
    const categories = /* @__PURE__ */ new Set();
    const commands = Array.from(this.commands.values());
    for (const command of commands) {
      categories.add(command.category);
    }
    return Array.from(categories).sort();
  }
  /**
   * Get the count of registered commands.
   */
  get size() {
    return this.commands.size;
  }
  /**
   * Clear all registered commands.
   * Useful for testing.
   */
  clear() {
    this.commands.clear();
    this.aliases.clear();
  }
  /**
   * Get a serializable list of commands for API responses.
   */
  toJSON() {
    return this.all().map((cmd) => ({
      name: cmd.name,
      aliases: cmd.aliases || [],
      description: cmd.description,
      category: cmd.category,
      inputs: cmd.inputs.map((input) => ({
        name: input.name,
        type: input.type,
        prompt: input.prompt,
        required: input.required !== false
      }))
    }));
  }
};
var commandRegistry = new CommandRegistry();

// src/utils/edge-path-registry.ts
var customCalculators = /* @__PURE__ */ new Map();
function registerEdgePathCalculator(name, calculator) {
  customCalculators.set(name, calculator);
}
function unregisterEdgePathCalculator(name) {
  return customCalculators.delete(name);
}

// src/core/plugin-registry.ts
var debug12 = createDebug("plugins");
var plugins = /* @__PURE__ */ new Map();
function registerPlugin(plugin) {
  debug12("Registering plugin: %s", plugin.id);
  if (plugins.has(plugin.id)) {
    throw new PluginError("Plugin is already registered", plugin.id, "ALREADY_REGISTERED");
  }
  if (plugin.dependencies) {
    for (const depId of plugin.dependencies) {
      if (!plugins.has(depId)) {
        throw new PluginError(`Missing dependency: "${depId}"`, plugin.id, "MISSING_DEPENDENCY");
      }
    }
  }
  detectConflicts(plugin);
  const cleanups = [];
  try {
    if (plugin.nodeTypes) {
      const nodeTypeNames = Object.keys(plugin.nodeTypes);
      registerNodeTypes(plugin.nodeTypes);
      cleanups.push(() => {
        for (const name of nodeTypeNames) {
          unregisterNodeType(name);
        }
      });
    }
    if (plugin.edgePathCalculators) {
      for (const [name, calc] of Object.entries(plugin.edgePathCalculators)) {
        registerEdgePathCalculator(name, calc);
        cleanups.push(() => unregisterEdgePathCalculator(name));
      }
    }
    if (plugin.actionHandlers) {
      for (const [actionId, handler] of Object.entries(plugin.actionHandlers)) {
        registerAction2(actionId, handler);
        cleanups.push(() => unregisterAction2(actionId));
      }
    }
    if (plugin.commands) {
      for (const cmd of plugin.commands) {
        commandRegistry.register(cmd);
        cleanups.push(() => commandRegistry.unregister(cmd.name));
      }
    }
    if (plugin.actions) {
      for (const action of plugin.actions) {
        registerAction(action);
        cleanups.push(() => unregisterAction(action.id));
      }
    }
    let lifecycleCleanup = null;
    if (plugin.onRegister) {
      const ctx = makePluginContext(plugin.id);
      try {
        const result = plugin.onRegister(ctx);
        if (typeof result === "function") {
          lifecycleCleanup = result;
        }
      } catch (err) {
        for (const cleanup of cleanups.reverse()) {
          try {
            cleanup();
          } catch {
          }
        }
        throw new PluginError(`onRegister failed: ${err instanceof Error ? err.message : String(err)}`, plugin.id, "LIFECYCLE_ERROR");
      }
    }
    plugins.set(plugin.id, {
      plugin,
      cleanup: () => {
        for (const cleanup of cleanups.reverse()) {
          try {
            cleanup();
          } catch {
          }
        }
        if (lifecycleCleanup) {
          try {
            lifecycleCleanup();
          } catch {
          }
        }
      },
      registeredAt: Date.now()
    });
    debug12("Plugin registered: %s (%d node types, %d commands, %d actions)", plugin.id, Object.keys(plugin.nodeTypes ?? {}).length, plugin.commands?.length ?? 0, plugin.actions?.length ?? 0);
  } catch (err) {
    if (err instanceof PluginError) throw err;
    for (const cleanup of cleanups.reverse()) {
      try {
        cleanup();
      } catch {
      }
    }
    throw err;
  }
}
function unregisterPlugin(pluginId) {
  const registration = plugins.get(pluginId);
  if (!registration) {
    throw new PluginError("Plugin is not registered", pluginId, "NOT_FOUND");
  }
  for (const [otherId, other] of plugins) {
    if (other.plugin.dependencies?.includes(pluginId)) {
      throw new PluginError(`Cannot unregister: plugin "${otherId}" depends on it`, pluginId, "CONFLICT");
    }
  }
  if (registration.cleanup) {
    registration.cleanup();
  }
  plugins.delete(pluginId);
  debug12("Plugin unregistered: %s", pluginId);
}
function getPlugin(id) {
  return plugins.get(id)?.plugin;
}
function hasPlugin(id) {
  return plugins.has(id);
}
function getAllPlugins() {
  return Array.from(plugins.values()).map((r) => r.plugin);
}
function getPluginIds() {
  return Array.from(plugins.keys());
}
function getPluginGestureContexts() {
  const contexts = [];
  for (const registration of plugins.values()) {
    if (registration.plugin.gestureContexts) {
      contexts.push(...registration.plugin.gestureContexts);
    }
  }
  return contexts;
}
function clearPlugins() {
  const ids = Array.from(plugins.keys()).reverse();
  for (const id of ids) {
    const reg = plugins.get(id);
    if (reg?.cleanup) {
      try {
        reg.cleanup();
      } catch {
      }
    }
    plugins.delete(id);
  }
  debug12("All plugins cleared");
}
function detectConflicts(plugin) {
  if (plugin.commands) {
    for (const cmd of plugin.commands) {
      if (commandRegistry.has(cmd.name)) {
        throw new PluginError(`Command "${cmd.name}" is already registered`, plugin.id, "CONFLICT");
      }
    }
  }
  if (plugin.edgePathCalculators) {
    for (const name of Object.keys(plugin.edgePathCalculators)) {
      for (const [otherId, other] of plugins) {
        if (other.plugin.edgePathCalculators?.[name]) {
          throw new PluginError(`Edge path calculator "${name}" already registered by plugin "${otherId}"`, plugin.id, "CONFLICT");
        }
      }
    }
  }
  if (plugin.nodeTypes) {
    for (const nodeType of Object.keys(plugin.nodeTypes)) {
      for (const [otherId, other] of plugins) {
        if (other.plugin.nodeTypes?.[nodeType]) {
          throw new PluginError(`Node type "${nodeType}" already registered by plugin "${otherId}"`, plugin.id, "CONFLICT");
        }
      }
    }
  }
  if (plugin.actionHandlers) {
    for (const actionId of Object.keys(plugin.actionHandlers)) {
      for (const [otherId, other] of plugins) {
        if (other.plugin.actionHandlers?.[actionId]) {
          throw new PluginError(`Action handler "${actionId}" already registered by plugin "${otherId}"`, plugin.id, "CONFLICT");
        }
      }
    }
  }
}
function makePluginContext(pluginId) {
  return {
    pluginId,
    getPlugin,
    hasPlugin
  };
}
export {
  ActionCategory,
  BUILT_IN_PRESETS,
  BuiltInActionId,
  CanvasEventType,
  DEFAULT_GESTURE_RULES,
  DEFAULT_MAPPINGS,
  DEFAULT_PORT,
  EDGE_ANIMATION_DURATION,
  EVENT_TYPE_INFO,
  FallbackNodeTypeComponent,
  HIT_TARGET_SIZES,
  PASTE_OFFSET,
  PluginError,
  SNAPSHOT_VERSION,
  SpatialGrid,
  VIRTUALIZATION_BUFFER,
  ZOOM_EXIT_THRESHOLD,
  ZOOM_TRANSITION_THRESHOLD,
  activePointersAtom,
  activePresetAtom,
  activePresetIdAtom,
  addGestureRuleAtom,
  addNodeToLocalGraphAtom,
  addNodesToSelectionAtom,
  alignmentGuidesAtom,
  allPresetsAtom,
  animateFitToBoundsAtom,
  animateZoomToNodeAtom,
  applyDelta,
  applyPresetAtom,
  arePortsCompatible,
  autoResizeGroupAtom,
  buildActionHelpers,
  buildRuleIndex,
  calculatePortPosition,
  canPortAcceptConnection,
  canRedoAtom,
  canUndoAtom,
  cancelSelectionAtom,
  canvasMark,
  canvasSettingsAtom,
  canvasToastAtom,
  canvasWrap,
  centerOnNodeAtom,
  classifyPointer,
  cleanupAllNodePositionsAtom,
  cleanupNodePositionAtom,
  clearActions,
  clearAlignmentGuidesAtom,
  clearClipboardAtom,
  clearEdgeSelectionAtom,
  clearGraphOnSwitchAtom,
  clearHistoryAtom,
  clearMutationQueueAtom,
  clearNodeTypeRegistry,
  clearPlugins,
  clearPointersAtom,
  clearSearchAtom,
  clearSelectionAtom,
  clipboardAtom,
  clipboardNodeCountAtom,
  collapseGroupAtom,
  collapsedEdgeRemapAtom,
  collapsedGroupsAtom,
  completeMutationAtom,
  conditionalSnap,
  consumerGestureRulesAtom,
  copyToClipboardAtom,
  createActionContext,
  createActionContextFromReactEvent,
  createActionContextFromTouchEvent,
  createCanvasAPI,
  createSnapshot,
  currentGraphIdAtom,
  cutToClipboardAtom,
  deletePresetAtom,
  departingEdgesAtom,
  dequeueMutationAtom,
  detectInputCapabilities,
  draggingNodeIdAtom,
  dropTargetNodeIdAtom,
  duplicateSelectionAtom,
  edgeCreationAtom,
  edgeFamilyAtom,
  edgeKeysAtom,
  edgeKeysWithTempEdgeAtom,
  editingEdgeLabelAtom,
  endNodeDragAtom,
  endSelectionAtom,
  eventMappingsAtom,
  executeAction,
  expandGroupAtom,
  exportGraph,
  findAlignmentGuides,
  fingerCountAtom,
  fitToBoundsAtom,
  focusedNodeIdAtom,
  formatRuleLabel,
  fuzzyMatch,
  gestureRuleIndexAtom,
  gestureRuleSettingsAtom,
  gestureRulesAtom,
  getAction,
  getActionForEvent,
  getActionsByCategories,
  getActionsByCategory,
  getAllActions,
  getAllPlugins,
  getGestureThresholds,
  getHitTargetSize,
  getNextQueuedMutationAtom,
  getNodeDescendants,
  getNodePorts,
  getNodeTypeComponent,
  getPlugin,
  getPluginGestureContexts,
  getPluginIds,
  getRegisteredNodeTypes,
  getSnapGuides,
  goToLockedPageAtom,
  graphAtom,
  graphOptions,
  graphUpdateVersionAtom,
  groupChildCountAtom,
  groupSelectedNodesAtom,
  handleNodePointerDownSelectionAtom,
  hasAction,
  hasClipboardContentAtom,
  hasExternalKeyboardAtom,
  hasFocusedNodeAtom,
  hasLockedNodeAtom,
  hasNodeTypeComponent,
  hasPlugin,
  hasSelectionAtom,
  hasUnsavedChangesAtom,
  highestZIndexAtom,
  highlightedSearchIndexAtom,
  highlightedSearchNodeIdAtom,
  historyLabelsAtom,
  historyStateAtom,
  importGraph,
  incrementRetryCountAtom,
  inputCapabilitiesAtom,
  inputModeAtom,
  interactionFeedbackAtom,
  invertDelta,
  isFilterActiveAtom,
  isGroupNodeAtom,
  isMultiTouchAtom,
  isNodeCollapsed,
  isOnlineAtom,
  isPanelOpenAtom,
  isPickNodeModeAtom,
  isPickingModeAtom,
  isSelectingAtom,
  isSnappingActiveAtom,
  isStylusActiveAtom,
  isTouchDeviceAtom,
  isZoomTransitioningAtom,
  keyboardInteractionModeAtom,
  lastSyncErrorAtom,
  lastSyncTimeAtom,
  loadGraphFromDbAtom,
  lockNodeAtom,
  lockedNodeDataAtom,
  lockedNodeIdAtom,
  lockedNodePageCountAtom,
  lockedNodePageIndexAtom,
  matchSpecificity,
  mergeNodesAtom,
  mergeRules,
  moveNodesToGroupAtom,
  mutationQueueAtom,
  nestNodesOnDropAtom,
  nextLockedPageAtom,
  nextSearchResultAtom,
  nodeChildrenAtom,
  nodeFamilyAtom,
  nodeKeysAtom,
  nodeParentAtom,
  nodePositionAtomFamily,
  nodePositionUpdateCounterAtom,
  optimisticDeleteEdgeAtom,
  optimisticDeleteNodeAtom,
  palmRejectionEnabledAtom,
  panAtom,
  pasteFromClipboardAtom,
  pendingInputResolverAtom,
  pendingMutationsCountAtom,
  perfEnabledAtom,
  pointInPolygon,
  pointerDownAtom,
  pointerUpAtom,
  preDragNodeAttributesAtom,
  prefersReducedMotionAtom,
  prevLockedPageAtom,
  prevSearchResultAtom,
  primaryInputSourceAtom,
  provideInputAtom,
  pushDeltaAtom,
  pushHistoryAtom,
  queueMutationAtom,
  redoAtom,
  redoCountAtom,
  registerAction,
  registerNodeType,
  registerNodeTypes,
  registerPlugin,
  removeEdgeWithAnimationAtom,
  removeFromGroupAtom,
  removeGestureRuleAtom,
  removeNodesFromSelectionAtom,
  resetGestureRulesAtom,
  resetInputModeAtom,
  resetKeyboardInteractionModeAtom,
  resetSettingsAtom,
  resetViewportAtom,
  resolveGesture,
  resolveGestureIndexed,
  saveAsPresetAtom,
  screenToWorldAtom,
  searchEdgeResultCountAtom,
  searchEdgeResultsAtom,
  searchQueryAtom,
  searchResultCountAtom,
  searchResultsArrayAtom,
  searchResultsAtom,
  searchTotalResultCountAtom,
  selectEdgeAtom,
  selectSingleNodeAtom,
  selectedEdgeIdAtom,
  selectedNodeIdsAtom,
  selectedNodesCountAtom,
  selectionPathAtom,
  selectionRectAtom,
  setEventMappingAtom,
  setFocusedNodeAtom,
  setGridSizeAtom,
  setKeyboardInteractionModeAtom,
  setNodeParentAtom,
  setOnlineStatusAtom,
  setPanelOpenAtom,
  setPerfEnabled,
  setSearchQueryAtom,
  setVirtualizationEnabledAtom,
  setZoomAtom,
  showToastAtom,
  snapAlignmentEnabledAtom,
  snapEnabledAtom,
  snapGridSizeAtom,
  snapTemporaryDisableAtom,
  snapToGrid,
  spatialIndexAtom,
  splitNodeAtom,
  startMutationAtom,
  startNodeDragAtom,
  startPickNodeAtom,
  startPickNodesAtom,
  startPickPointAtom,
  startSelectionAtom,
  swapEdgeAtomicAtom,
  syncStateAtom,
  syncStatusAtom,
  toggleAlignmentGuidesAtom,
  toggleGroupCollapseAtom,
  toggleNodeInSelectionAtom,
  togglePanelAtom,
  toggleSnapAtom,
  toggleVirtualizationAtom,
  trackMutationErrorAtom,
  uiNodesAtom,
  undoAtom,
  undoCountAtom,
  ungroupNodesAtom,
  unlockNodeAtom,
  unregisterAction,
  unregisterNodeType,
  unregisterPlugin,
  updateEdgeLabelAtom,
  updateGestureRuleAtom,
  updateInteractionFeedbackAtom,
  updateNodePositionAtom,
  updatePresetAtom,
  updateSelectionAtom,
  validateSnapshot,
  viewportRectAtom,
  virtualizationEnabledAtom,
  virtualizationMetricsAtom,
  visibleBoundsAtom,
  visibleEdgeKeysAtom,
  visibleNodeKeysAtom,
  watchExternalKeyboardAtom,
  watchReducedMotionAtom,
  worldToScreenAtom,
  zoomAnimationTargetAtom,
  zoomAtom,
  zoomFocusNodeIdAtom,
  zoomTransitionProgressAtom
};
//# sourceMappingURL=index.mjs.map