UNPKG

mermaid

Version:

Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.

2,059 lines 80.7 kB
import {
  applyLineJumpsToSvg
} from "./chunk-NETBCI7D.mjs";
import {
  createCommonLayoutRenderer,
  defaultMeasureLayout
} from "./chunk-3FUC2YCW.mjs";
import {
  clusterPaintsTitle
} from "./chunk-UA2S7LBM.mjs";
import {
  markerOffsets,
  markerOffsets2
} from "./chunk-Z7XXMR3K.mjs";
import "./chunk-5DYCD2WN.mjs";
import "./chunk-7INBJB4K.mjs";
import "./chunk-7PRAP22T.mjs";
import "./chunk-MBY4JIJT.mjs";
import "./chunk-742MDFTN.mjs";
import "./chunk-J5ZVWO5B.mjs";
import "./chunk-ZIGJFQKS.mjs";
import {
  setConfig2 as setConfig
} from "./chunk-O7XYJQB3.mjs";
import "./chunk-X3CZISLH.mjs";
import {
  __name
} from "./chunk-Y2CYZVJY.mjs";

// src/rendering-util/layout-algorithms/elk/render.ts
import { curveLinear } from "d3";
import ELK from "elkjs/lib/elk.bundled.js";

// src/rendering-util/layout-algorithms/elk/find-common-ancestor.ts
var findCommonAncestor = /* @__PURE__ */ __name((id1, id2, { parentById }) => {
  const visited = /* @__PURE__ */ new Set();
  let currentId = id1;
  if (id1 === id2) {
    return parentById[id1] || "root";
  }
  while (currentId) {
    visited.add(currentId);
    if (currentId === id2) {
      return currentId;
    }
    currentId = parentById[currentId];
  }
  currentId = id2;
  while (currentId) {
    if (visited.has(currentId)) {
      return currentId;
    }
    currentId = parentById[currentId];
  }
  return "root";
}, "findCommonAncestor");

// src/rendering-util/layout-algorithms/elk/lineHops.ts
var JUMP_RADIUS = 6;
function applyElkLineJumps(data4Layout, { measure }) {
  const lineHops = data4Layout.config?.elk?.lineHops;
  if (lineHops === false) {
    return;
  }
  const edgeGeometries = data4Layout.edges.filter(
    (edge) => Array.isArray(edge.points) && edge.points.length >= 2
  ).map((edge) => ({
    id: edge.id,
    points: edge.points,
    curve: edge.curve,
    arrowTypeStart: edge.arrowTypeStart,
    arrowTypeEnd: edge.arrowTypeEnd
  }));
  applyLineJumpsToSvg(measure.groups.edgePaths, edgeGeometries, {
    enabled: true,
    jumpRadius: JUMP_RADIUS,
    jumpStyle: lineHops === "gap" ? "gap" : "arc"
  });
}
__name(applyElkLineJumps, "applyElkLineJumps");

// src/rendering-util/layout-algorithms/elk/elkOptionCatalogue.ts
var PLACEMENT_OPTIONS = {
  // ─── Layering — which layer a node lands in (the column in LR, row in TB) ───
  // Coarsest placement decision there is; relocates 38-48% of nodes.
  // 'elk.layered.layering.strategy': 'COFFMAN_GRAHAM',
  // 'elk.layered.layering.strategy': 'NETWORK_SIMPLEX',      // ELK default: fewest long edges *
  // 'elk.layered.layering.strategy': 'LONGEST_PATH',         // every node as late as possible
  // 'elk.layered.layering.strategy': 'LONGEST_PATH_SOURCE',  // same, measured from sources
  // 'elk.layered.layering.strategy': 'MIN_WIDTH',            // narrower drawing, longer edges
  // 'elk.layered.layering.strategy': 'STRETCH_WIDTH',        // wider drawing, shorter edges
  // 'elk.layered.layering.strategy': 'INTERACTIVE',          // honours positions already on nodes
  // Cap on how many nodes COFFMAN_GRAHAM puts in one layer; ignored by the rest.
  // 'elk.layered.layering.coffmanGraham.layerBound': 2,
  // 'elk.layered.layering.coffmanGraham.layerBound': 4,      // ELK default; taller and narrower
  // Pulls nodes into earlier layers to cut dummy nodes on long edges.
  // 'elk.layered.layering.nodePromotion.strategy': 'NONE',   // ELK default
  // 'elk.layered.layering.nodePromotion.strategy': 'NIKOLOV',
  // 'elk.layered.layering.nodePromotion.strategy': 'NIKOLOV_PIXEL',
  // 'elk.layered.layering.nodePromotion.strategy': 'NIKOLOV_IMPROVED',
  // 'elk.layered.layering.nodePromotion.strategy': 'NIKOLOV_IMPROVED_PIXEL',
  // 'elk.layered.layering.nodePromotion.strategy': 'DUMMYNODE_PERCENTAGE',
  // 'elk.layered.layering.nodePromotion.strategy': 'NODECOUNT_PERCENTAGE',
  // 'elk.layered.layering.nodePromotion.strategy': 'NO_BOUNDARY',
  // ─── Crossing minimisation — the order of nodes within a layer ───
  // How node order inside each layer is chosen.
  // 'elk.layered.crossingMinimization.strategy': 'LAYER_SWEEP',  // ELK default
  //'elk.layered.crossingMinimization.strategy': 'INTERACTIVE',  // keeps existing order
  // 'elk.layered.crossingMinimization.strategy': 'NONE',         // declaration order, no sweep
  // Extra pass that swaps adjacent node pairs when it removes crossings.
  // 'elk.layered.crossingMinimization.greedySwitch.type': 'TWO_SIDED',  // ELK default
  // 'elk.layered.crossingMinimization.greedySwitch.type': 'ONE_SIDED',
  // 'elk.layered.crossingMinimization.greedySwitch.type': 'OFF',
  // How hard declaration order is defended against crossing reduction.
  // 'elk.layered.considerModelOrder.strategy': 'NODES_AND_EDGES',
  // 'elk.layered.considerModelOrder.strategy': 'NONE',          // ignore declaration order
  // 'elk.layered.considerModelOrder.strategy': 'PREFER_EDGES',  // order edges, let nodes move
  // 'elk.layered.considerModelOrder.strategy': 'PREFER_NODES',  // order nodes, let edges move
  // ─── Node placement — the coordinate within the layer ───
  // Wired to `elk.nodePlacementStrategy`; uncomment to override that config.
  // 'elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX',  // balanced; root under modelOrder/depthFirst
  // 'elk.layered.nodePlacement.strategy': 'BRANDES_KOEPF',    // ELK default and ours: straight long edges
  // 'elk.layered.nodePlacement.strategy': 'LINEAR_SEGMENTS',  // keeps chains aligned
  // 'elk.layered.nodePlacement.strategy': 'SIMPLE',           // cheapest, least tidy
  // Shifts nodes to straighten edges rather than centre them in the layer.
  // 'elk.layered.nodePlacement.favorStraightEdges': true,
  // 'elk.layered.nodePlacement.favorStraightEdges': false,
  // Brandes-Koepf only: which of its four candidate alignments to keep.
  // 'elk.layered.nodePlacement.bk.fixedAlignment': 'NONE',      // pick the shortest result; named presets
  // 'elk.layered.nodePlacement.bk.fixedAlignment': 'BALANCED',  // average all four; default preset
  // 'elk.layered.nodePlacement.bk.fixedAlignment': 'LEFTUP',
  // 'elk.layered.nodePlacement.bk.fixedAlignment': 'RIGHTUP',
  // 'elk.layered.nodePlacement.bk.fixedAlignment': 'LEFTDOWN',
  // 'elk.layered.nodePlacement.bk.fixedAlignment': 'RIGHTDOWN',
  // Brandes-Koepf only: post-pass that trades compactness for straighter edges.
  // 'elk.layered.nodePlacement.bk.edgeStraightening': 'IMPROVE_STRAIGHTNESS',
  // 'elk.layered.nodePlacement.bk.edgeStraightening': 'NONE',   // ELK default
  // Network-simplex only: what the placer is allowed to stretch to straighten edges.
  // 'elk.layered.nodePlacement.networkSimplex.nodeFlexibility': 'NONE',  // ELK default
  // 'elk.layered.nodePlacement.networkSimplex.nodeFlexibility': 'NODE_SIZE',
  // 'elk.layered.nodePlacement.networkSimplex.nodeFlexibility': 'PORT_POSITION',
  // 'elk.layered.nodePlacement.networkSimplex.nodeFlexibility': 'NODE_SIZE_WHERE_SPACE_PERMITS',
  // ─── Cycles and hierarchy ───
  // Which edges get reversed to make the graph acyclic; decides which ones detour.
  // Wired to `elk.cycleBreakingStrategy`; uncomment to override that config.
  // 'elk.layered.cycleBreaking.strategy': 'GREEDY_MODEL_ORDER',  // our default
  // 'elk.layered.cycleBreaking.strategy': 'GREEDY',              // ELK default; short back edges, +20% total
  // 'elk.layered.cycleBreaking.strategy': 'DEPTH_FIRST',         // middle ground, +6% total
  // 'elk.layered.cycleBreaking.strategy': 'MODEL_ORDER',         // reverse purely by declaration order
  // 'elk.layered.cycleBreaking.strategy': 'INTERACTIVE',         // reverse by existing positions
  // Whether subgraphs are laid out with the parent or in their own coordinate system.
  // 'elk.hierarchyHandling': 'INCLUDE_CHILDREN',   // our default, one global pass
  // 'elk.hierarchyHandling': 'SEPARATE_CHILDREN',  // shorter edges, far more constraint violations
  // Post-pass that pulls nodes back towards one side to reclaim space.
  // 'elk.layered.compaction.postCompaction.strategy': 'NONE',   // ELK default
  // 'elk.layered.compaction.postCompaction.strategy': 'LEFT',
  // 'elk.layered.compaction.postCompaction.strategy': 'RIGHT',
  // 'elk.layered.compaction.postCompaction.strategy': 'LEFT_RIGHT_CONSTRAINT_LOCKING',
  // 'elk.layered.compaction.postCompaction.strategy': 'LEFT_RIGHT_CONNECTION_LOCKING',
  // 'elk.layered.compaction.postCompaction.strategy': 'EDGE_LENGTH',
  // ─── Spacing and labels ───
  // Base spacing everything else derives from; the single biggest lever on size.
  // 'spacing.baseValue': 40,
  // 'spacing.baseValue': 20,   // ELK default — collapses this corpus, 13/14 invalid
  // Where a container's own title sits inside its frame.
  // 'nodeLabels.placement': '[H_CENTER V_TOP, INSIDE]',
  // ─── Measured inert on this corpus — a null result here means nothing ───
  // Overwritten straight after createRootElkGraph by the diagram's own direction.
  // 'elk.direction': 'UP',
  // ELK ignores this key in every spelling; the gap derives from spacing.baseValue.
  // 'elk.spacing.edgeNode': 20,
  // Only applies when wrapping.strategy is on, and it is off.
  // 'elk.layered.wrapping.cutting.strategy': 'ARD',
  // Routes reversed edges in their own band. No effect measured here.
  // 'elk.layered.feedbackEdges': true,
  // ─── Tried and parked ───
  // 'elk.layered.wrapping.strategy': 'MULTI_EDGE',
  // 'elk.layered.wrapping.strategy': 'SINGLE_EDGE',
  // 'elk.layered.crossingMinimization.semiInteractive': true,
  // 'elk.layered.edgeRouting.splines.sloppy.layerSpacingFactor': 1,
  // 'elk.layered.edgeRouting.polyline.slopedEdgeZoneWidth': 4.0,
  // 'elk.layered.wrapping.validify.strategy': 'LOOK_BACK',
  // 'elk.insideSelfLoops.activate': true,
  // 'elk.separateConnectedComponents': true,
  // 'elk.alignment': 'LEFT',
};
var EDGE_ROUTING_OPTIONS = {
  // Shape of every edge. ORTHOGONAL is ELK's default and what the adapter expects.
  // 'elk.edgeRouting': 'ORTHOGONAL',
  // 'elk.edgeRouting': 'POLYLINE',   // diagonal runs, fewer bends
  // 'elk.edgeRouting': 'SPLINES',    // curved; validateLayout treats these as non-orthogonal
  // 'elk.edgeRouting': 'UNDEFINED',  // let the algorithm decide
  // Drops bends that do not change the path. Already on in the literal below.
  // 'elk.layered.unnecessaryBendpoints': true,
  // 'elk.layered.unnecessaryBendpoints': false,
  // Routes reversed edges in their own band instead of among the forward ones.
  // The obvious candidate for a back-edge detour — measured inert on this corpus.
  // 'elk.layered.feedbackEdges': true,
  // 'elk.layered.feedbackEdges': false,
  // Lets edges that meet at a node share a trunk. Collapses arriving and leaving
  // onto ONE handle, which can imply a connection that does not exist.
  // 'elk.layered.mergeEdges': true,
  // 'elk.layered.mergeEdges': false,
  // Same, for edges that cross a subgraph boundary. On in the literal below.
  // 'elk.layered.mergeHierarchyEdges': true,
  // 'elk.layered.mergeHierarchyEdges': false,
  // How much straightening an edge is worth relative to other objectives.
  // Also settable per edge, which is the targeted way to rescue one bad route.
  // 'elk.layered.priority.straightness': 0,
  // 'elk.layered.priority.shortness': 0,
  // 'elk.layered.priority.direction': 1,
  // ─── Self loops ───
  // Which sides a node's self loops are spread across. EQUALLY ships below.
  // 'elk.layered.edgeRouting.selfLoopDistribution': 'EQUALLY',
  // 'elk.layered.edgeRouting.selfLoopDistribution': 'NORTH',
  // 'elk.layered.edgeRouting.selfLoopDistribution': 'NORTH_SOUTH',
  // Whether stacked self loops nest or sit side by side.
  // 'elk.layered.edgeRouting.selfLoopOrdering': 'STACKED',
  // 'elk.layered.edgeRouting.selfLoopOrdering': 'SEQUENCED',
  // Draw self loops inside the node rather than hanging off it.
  // 'elk.insideSelfLoops.activate': true,
  // ─── Spline and polyline tuning (only read by the matching edgeRouting) ───
  // How closely splines hug the orthogonal path they replace.
  // 'elk.layered.edgeRouting.splines.mode': 'CONSERVATIVE',
  // 'elk.layered.edgeRouting.splines.mode': 'CONSERVATIVE_SOFT',
  // 'elk.layered.edgeRouting.splines.mode': 'SLOPPY',
  // 'elk.layered.edgeRouting.splines.sloppy.layerSpacingFactor': 1,
  // Width of the band a POLYLINE edge may slope through.
  // (Was left uncommented while this block was inert; commented now that it is
  // live, since it would otherwise be permanently on for every diagram.)
  // 'elk.layered.edgeRouting.polyline.slopedEdgeZoneWidth': 4.0,
  // ─── Lanes and clearance ───
  // Gap between two edges sharing a lane; too small trips the proximity checks.
  // 'spacing.edgeEdge': 10,
  // 'elk.layered.spacing.edgeEdgeBetweenLayers': 20,
  // Gap between an edge and a node it passes. Ignored at root; the subgraph
  // value derives from `spacing.baseValue` at roughly half.
  // 'spacing.edgeNode': 20,
  // 'elk.layered.spacing.edgeNodeBetweenLayers': 80,
  // ─── Edge labels ───
  // Which side of its edge a label sits on.
  // 'elk.layered.edgeLabels.sideSelection': 'SMART_DOWN',
  // 'elk.layered.edgeLabels.sideSelection': 'SMART_UP',
  // 'elk.layered.edgeLabels.sideSelection': 'ALWAYS_UP',
  // 'elk.layered.edgeLabels.sideSelection': 'ALWAYS_DOWN',
  // 'elk.layered.edgeLabels.sideSelection': 'DIRECTION_UP',
  // 'elk.layered.edgeLabels.sideSelection': 'DIRECTION_DOWN',
  // Which layer a centre label is parked in when the edge spans several.
  // 'elk.layered.edgeLabels.centerLabelPlacementStrategy': 'MEDIAN_LAYER',
  // 'elk.layered.edgeLabels.centerLabelPlacementStrategy': 'HEAD_LAYER',
  // 'elk.layered.edgeLabels.centerLabelPlacementStrategy': 'TAIL_LAYER',
  // 'elk.layered.edgeLabels.centerLabelPlacementStrategy': 'SPACE_EFFICIENT_LAYER',
  // 'elk.layered.edgeLabels.centerLabelPlacementStrategy': 'WIDEST_LAYER',
  // 'elk.layered.edgeLabels.centerLabelPlacementStrategy': 'CENTER_LAYER',
};
var ROOT_EXPERIMENT_OVERRIDES = {
  // 'elk.layered.layering.strategy': 'COFFMAN_GRAHAM',
  // 'elk.layered.nodePlacement.strategy': 'BRANDES_KOEPF',
  // 'elk.layered.cycleBreaking.strategy': 'DEPTH_FIRST',
  // 'elk.edgeRouting': 'POLYLINE',
  // 'spacing.baseValue': 60,
};
var SUBGRAPH_EXPERIMENT_OVERRIDES = {
  // 'spacing.nodeNode': 60,
  // 'elk.spacing.edgeEdge': 10,
  // 'elk.layered.spacing.edgeNodeBetweenLayers': 80,
  // 'elk.padding': '[top=24,left=24,bottom=24,right=24]',
  // 'nodeLabels.placement': '[H_CENTER V_TOP, INSIDE]',
  // Equal-width subgraph frames. Tried and DOES NOT WORK: the options reach
  // ELK intact, but every container is forced back to INCLUDE_CHILDREN by
  // `setIncludeChildrenPolicy` (cross-boundary edges), and in that mode ELK
  // sizes a compound node to its contents and ignores the minimum.
  // 'nodeSize.constraints': '[MINIMUM_SIZE]',
  // 'nodeSize.minimum': '(446, 0)',
};

// src/rendering-util/layout-algorithms/elk/geometry.ts
var EPS = 1;
var PUSH_OUT = 10;
var onBorder = /* @__PURE__ */ __name((bounds, p, tol = 0.5) => {
  const halfW = bounds.width / 2;
  const halfH = bounds.height / 2;
  const left = bounds.x - halfW;
  const right = bounds.x + halfW;
  const top = bounds.y - halfH;
  const bottom = bounds.y + halfH;
  const onLeft = Math.abs(p.x - left) <= tol && p.y >= top - tol && p.y <= bottom + tol;
  const onRight = Math.abs(p.x - right) <= tol && p.y >= top - tol && p.y <= bottom + tol;
  const onTop = Math.abs(p.y - top) <= tol && p.x >= left - tol && p.x <= right + tol;
  const onBottom = Math.abs(p.y - bottom) <= tol && p.x >= left - tol && p.x <= right + tol;
  return onLeft || onRight || onTop || onBottom;
}, "onBorder");
var intersection = /* @__PURE__ */ __name((node, outsidePoint, insidePoint) => {
  const x = node.x;
  const y = node.y;
  const dx = Math.abs(x - insidePoint.x);
  const w = node.width / 2;
  let r = insidePoint.x < outsidePoint.x ? w - dx : w + dx;
  const h = node.height / 2;
  const Q = Math.abs(outsidePoint.y - insidePoint.y);
  const R = Math.abs(outsidePoint.x - insidePoint.x);
  if (Math.abs(y - outsidePoint.y) * w > Math.abs(x - outsidePoint.x) * h) {
    const q = insidePoint.y < outsidePoint.y ? outsidePoint.y - h - y : y - h - outsidePoint.y;
    r = R * q / Q;
    const res = {
      x: insidePoint.x < outsidePoint.x ? insidePoint.x + r : insidePoint.x - R + r,
      y: insidePoint.y < outsidePoint.y ? insidePoint.y + Q - q : insidePoint.y - Q + q
    };
    if (R === 0) {
      res.x = outsidePoint.x;
    }
    if (Q === 0) {
      res.y = outsidePoint.y;
    }
    return res;
  } else {
    if (insidePoint.x < outsidePoint.x) {
      r = outsidePoint.x - w - x;
    } else {
      r = x - w - outsidePoint.x;
    }
    const q = Q * r / R;
    let _x = insidePoint.x < outsidePoint.x ? insidePoint.x + R - r : insidePoint.x - R + r;
    let _y = insidePoint.y < outsidePoint.y ? insidePoint.y + q : insidePoint.y - q;
    if (R === 0) {
      _x = outsidePoint.x;
    }
    if (Q === 0) {
      _y = outsidePoint.y;
    }
    return { x: _x, y: _y };
  }
}, "intersection");
var outsideNode = /* @__PURE__ */ __name((node, point) => {
  const x = node.x;
  const y = node.y;
  const dx = Math.abs(point.x - x);
  const dy = Math.abs(point.y - y);
  const w = node.width / 2;
  const h = node.height / 2;
  return dx >= w || dy >= h;
}, "outsideNode");
var ensureTrulyOutside = /* @__PURE__ */ __name((bounds, p, push = PUSH_OUT) => {
  const dx = Math.abs(p.x - bounds.x);
  const dy = Math.abs(p.y - bounds.y);
  const w = bounds.width / 2;
  const h = bounds.height / 2;
  if (Math.abs(dx - w) < EPS || Math.abs(dy - h) < EPS) {
    const dirX = p.x - bounds.x;
    const dirY = p.y - bounds.y;
    const len = Math.sqrt(dirX * dirX + dirY * dirY);
    if (len > 0) {
      return {
        x: bounds.x + dirX / len * (len + push),
        y: bounds.y + dirY / len * (len + push)
      };
    }
  }
  return p;
}, "ensureTrulyOutside");
var makeInsidePoint = /* @__PURE__ */ __name((bounds, outside, center) => {
  const isVertical = Math.abs(outside.x - bounds.x) < EPS;
  const isHorizontal = Math.abs(outside.y - bounds.y) < EPS;
  return {
    x: isVertical ? outside.x : outside.x < bounds.x ? bounds.x - bounds.width / 4 : bounds.x + bounds.width / 4,
    y: isHorizontal ? outside.y : center.y
  };
}, "makeInsidePoint");
var tryNodeIntersect = /* @__PURE__ */ __name((node, bounds, outside) => {
  if (!node?.intersect) {
    return null;
  }
  const res = node.intersect(outside);
  if (!res) {
    return null;
  }
  const wrongSide = outside.x < bounds.x && res.x > bounds.x || outside.x > bounds.x && res.x < bounds.x;
  if (wrongSide) {
    return null;
  }
  const dist = Math.hypot(outside.x - res.x, outside.y - res.y);
  if (dist <= EPS) {
    return null;
  }
  return res;
}, "tryNodeIntersect");
var fallbackIntersection = /* @__PURE__ */ __name((bounds, outside, center) => {
  const inside = makeInsidePoint(bounds, outside, center);
  return intersection(bounds, outside, inside);
}, "fallbackIntersection");
var OUTLINE_RAY_STEPS = 20;
var DEPARTURE_AXIS_EPS = 1e-6;
var insideOutline = /* @__PURE__ */ __name((node, centre, probe) => {
  const crossing = node.intersect?.(probe);
  if (!crossing) {
    return false;
  }
  const probeDist = Math.hypot(probe.x - centre.x, probe.y - centre.y);
  const outlineDist = Math.hypot(crossing.x - centre.x, crossing.y - centre.y);
  return probeDist <= outlineDist + 1e-9;
}, "insideOutline");
var outlineAttachPoint = /* @__PURE__ */ __name((node, bounds, port, next) => {
  if (!node?.intersect) {
    return null;
  }
  const dx = next.x - port.x;
  const dy = next.y - port.y;
  if (dx === 0 && dy === 0) {
    return null;
  }
  if (Math.abs(dx) > DEPARTURE_AXIS_EPS && Math.abs(dy) > DEPARTURE_AXIS_EPS) {
    return null;
  }
  const centre = { x: bounds.x, y: bounds.y };
  const horizontal = Math.abs(dx) > Math.abs(dy);
  const along = /* @__PURE__ */ __name((t) => horizontal ? { x: t, y: port.y } : { x: port.x, y: t }, "along");
  let inner = horizontal ? centre.x : centre.y;
  let outer = horizontal ? port.x : port.y;
  if (!insideOutline(node, centre, along(inner))) {
    return null;
  }
  if (insideOutline(node, centre, along(outer))) {
    return { ...port };
  }
  for (let step = 0; step < OUTLINE_RAY_STEPS; step++) {
    const mid = (inner + outer) / 2;
    if (insideOutline(node, centre, along(mid))) {
      inner = mid;
    } else {
      outer = mid;
    }
  }
  return along(inner);
}, "outlineAttachPoint");
var computeNodeIntersection = /* @__PURE__ */ __name((node, bounds, outside, center) => {
  const outside2 = ensureTrulyOutside(bounds, outside);
  return tryNodeIntersect(node, bounds, outside2) ?? fallbackIntersection(bounds, outside2, center);
}, "computeNodeIntersection");
var replaceEndpoint = /* @__PURE__ */ __name((points, which, value, tol = 0.1) => {
  if (!value || points.length === 0) {
    return;
  }
  if (which === "start") {
    if (points.length > 0 && Math.abs(points[0].x - value.x) < tol && Math.abs(points[0].y - value.y) < tol) {
      points.shift();
    } else {
      points[0] = value;
    }
  } else {
    const last = points.length - 1;
    if (points.length > 0 && Math.abs(points[last].x - value.x) < tol && Math.abs(points[last].y - value.y) < tol) {
      points.pop();
    } else {
      points[last] = value;
    }
  }
}, "replaceEndpoint");

// src/rendering-util/layout-algorithms/elk/render.ts
var MIN_END_MARKER_SEGMENT_LENGTH = 8;
var markerPathOffset = /* @__PURE__ */ __name((arrowType) => {
  if (typeof arrowType !== "string") {
    return 0;
  }
  return Math.max(
    markerOffsets[arrowType] ?? 0,
    markerOffsets2[arrowType] ?? 0
  );
}, "markerPathOffset");
var ARROW_MAP = {
  arrow_open: ["none", "none"],
  arrow_cross: ["none", "arrow_cross"],
  double_arrow_cross: ["arrow_cross", "arrow_cross"],
  arrow_point: ["none", "arrow_point"],
  double_arrow_point: ["arrow_point", "arrow_point"],
  arrow_circle: ["none", "arrow_circle"],
  double_arrow_circle: ["arrow_circle", "arrow_circle"]
};
var PORTS_SURROUNDING_MARGIN = 12;
var PORTS_SURROUNDING = `[top=${PORTS_SURROUNDING_MARGIN},left=${PORTS_SURROUNDING_MARGIN},bottom=${PORTS_SURROUNDING_MARGIN},right=${PORTS_SURROUNDING_MARGIN}]`;
var SUBGRAPH_PADDING = 24;
var DEFAULT_SUBGRAPH_SPACING_BASE_VALUE = 24;
var DEFAULT_SUBGRAPH_NODE_SPACING = 50;
var CONTAINER_PADDING = 15;
var RECTPACKING_CONTAINER_PADDING = 10;
var RECTPACKING_OPTIONS = {
  "spacing.baseValue": 15,
  "spacing.nodeNode": 15,
  "elk.aspectRatio": "1.6",
  "elk.expandNodes": "true",
  "elk.rectpacking.trybox": "true",
  "elk.rectpacking.packing.compaction.rowHeightReevaluation": "true",
  "elk.rectpacking.packing.compaction.iterations": 10,
  "elk.rectpacking.whiteSpaceElimination.strategy": "EQUAL_BETWEEN_STRUCTURES",
  "elk.rectpacking.widthApproximation.strategy": "SCANLINE"
};
var CONTAINER_ALGORITHM_OVERRIDES = [
  "nodeSize.constraints",
  "nodeSize.minimum",
  "elk.algorithm",
  "elk.aspectRatio",
  "elk.contentAlignment",
  "elk.expandNodes",
  "elk.padding",
  ...Object.keys(RECTPACKING_OPTIONS)
];
function clearContainerAlgorithmOptions(layoutOptions) {
  for (const key of CONTAINER_ALGORITHM_OVERRIDES) {
    delete layoutOptions[key];
  }
  layoutOptions["spacing.baseValue"] = DEFAULT_SUBGRAPH_SPACING_BASE_VALUE;
  layoutOptions["spacing.nodeNode"] = DEFAULT_SUBGRAPH_NODE_SPACING;
}
__name(clearContainerAlgorithmOptions, "clearContainerAlgorithmOptions");
var CONTAINER_ALGORITHMS = /* @__PURE__ */ new Set([
  "elk.layered",
  "elk.box",
  "elk.rectpacking",
  "elk.stress",
  "elk.force",
  "elk.mrtree",
  "elk.radial",
  "elk.sporeOverlap"
]);
function resolveContainerAlgorithm(requested, log) {
  if (typeof requested !== "string") {
    return void 0;
  }
  if (!CONTAINER_ALGORITHMS.has(requested)) {
    log?.warn(
      `Unknown container layout algorithm "${requested}". Supported values: ${[...CONTAINER_ALGORITHMS].join(", ")}. Falling back to the diagram's layout algorithm.`
    );
    return void 0;
  }
  return requested;
}
__name(resolveContainerAlgorithm, "resolveContainerAlgorithm");
function dir2ElkDirection(dir) {
  switch (dir) {
    case "LR":
      return "RIGHT";
    case "RL":
      return "LEFT";
    case "TB":
    case "TD":
      return "DOWN";
    case "BT":
      return "UP";
    default:
      return "DOWN";
  }
}
__name(dir2ElkDirection, "dir2ElkDirection");
function groupTitleWidth(node) {
  if (!clusterPaintsTitle(node.shape)) {
    return 0;
  }
  return (node.labelData?.width ?? node.labels?.[0]?.width ?? 0) + (node.padding ?? 0);
}
__name(groupTitleWidth, "groupTitleWidth");
function groupTitleSizeOptions(node) {
  if (!clusterPaintsTitle(node.shape)) {
    return {};
  }
  return {
    "nodeSize.constraints": "[MINIMUM_SIZE, NODE_LABELS]",
    "nodeSize.minimum": `(${groupTitleWidth(node)}, 0)`
  };
}
__name(groupTitleSizeOptions, "groupTitleSizeOptions");
function buildSubgraphLayoutOptions(node, elkConfig, algorithm, log) {
  const labelW = node.labelData?.width ?? 0;
  const pad = node.padding ?? 0;
  const minWidth = labelW + 2 * pad;
  const labelH = node.labelData?.height ?? 0;
  const preset = resolveElkPreset(elkConfig?.preset);
  const layoutOptions = {
    // Reserve the painted title width before routing. Enlarging a frame after
    // ELK has placed its ports leaves those ports inside the painted border.
    ...groupTitleSizeOptions(node),
    "spacing.baseValue": DEFAULT_SUBGRAPH_SPACING_BASE_VALUE,
    // The straight run an edge gets before the node it enters, bought on its
    // own rather than out of `spacing.baseValue` — see the note there. This is
    // the layered-scoped key; the unscoped `spacing.edgeNodeBetweenLayers` is
    // not an ELK id at all and setting it does nothing.
    //
    // 30, which is where the approach run stops improving: 40 measured the same
    // 30px shortest approach and only widened the lane this value also pays
    // for. That lane used to be the reason to go lower — the value is charged
    // TWICE against a group with an edge routed inside its frame, once between
    // the nodes and the lane and again between the lane and the frame, so the
    // group's extra width came out at exactly `36 + 2x`. `evenGroupFrames` now
    // pulls the frame in past the lane regardless, so a wider lane no longer
    // shows as lopsided padding and the only cost left is overall diagram size.
    //
    // Do NOT lower it further on that reasoning. Over the DDLT corpus this is
    // not monotonic: 30 and 40 leave one fixture invalid (the deliberate
    // merge-edge counterexample), while 20 leaves two and 25 leaves three —
    // `right-angles-not-curves` starts tripping `edge-parallel-segment-too-close`
    // because this spacing also separates edges running alongside each other in
    // the layer gap. 30 is the lowest value that keeps the corpus clean.
    "elk.layered.spacing.edgeNodeBetweenLayers": 30,
    // Separation between edges sharing a lane. Also raised off the base value,
    // so that lowering the base does not leave parallel edges touching.
    "elk.spacing.edgeEdge": 20,
    // Node separation, likewise bought on its own — see the note on the constant.
    "spacing.nodeNode": DEFAULT_SUBGRAPH_NODE_SPACING,
    // Breathing room between a frame and its children. Set explicitly rather
    // than left to ELK's default of 12. The top gets the same value as the
    // rest: ELK reserves the subgraph's own title strip on top of whatever is
    // given here, so adding the label height again double-counts it.
    "elk.padding": `[top=${SUBGRAPH_PADDING},left=${SUBGRAPH_PADDING},bottom=${SUBGRAPH_PADDING},right=${SUBGRAPH_PADDING}]`,
    "nodeLabels.placement": "[H_CENTER V_TOP, INSIDE]",
    "elk.layered.mergeEdges": elkConfig?.mergeEdges,
    "elk.layered.nodePlacement.bk.fixedAlignment": elkConfig?.nodePlacementAlignment ?? preset.alignment,
    // The preset resolves child placement separately from root placement.
    // Named presets retain their previous strategies; explicit options win.
    //
    // ONE key, fully qualified. ELK reads `nodePlacement.strategy` and
    // `elk.layered.nodePlacement.strategy` as the same option, so listing both
    // — as this did — leaves the container holding two values for it with no
    // say in which wins, and quietly ignores an explicit `nodePlacementStrategy`.
    "elk.layered.nodePlacement.strategy": elkConfig?.nodePlacementStrategy ?? preset.containerPlacement,
    // Resolved here as well as at the root, because a container laid out on its
    // own never sees the root's value and falls back to ELK's default, GREEDY.
    // That reverses a different edge than the preset asked for, so a composite
    // containing a loop opens on whichever node greedy promoted to a source
    // rather than on its own start node. Same key and same resolution as the
    // root, so `legacy` reproduces the old rendering inside frames too.
    "elk.layered.cycleBreaking.strategy": elkConfig?.cycleBreakingStrategy ?? preset.cycleBreaking,
    // PORT_POSITION lets a node shift so an edge can leave straight rather than
    // bending immediately off the port.
    "elk.layered.nodePlacement.networkSimplex.nodeFlexibility": "PORT_POSITION",
    // Keep a frame's ports off its own corners. See the note in
    // `createRootElkGraph`; a container is where this bites hardest, because a
    // cross-boundary edge attaches to the frame rather than to a node inside it.
    "elk.spacing.portsSurrounding": PORTS_SURROUNDING
  };
  const algo = resolveContainerAlgorithm(node.metadata?.algorithm, log);
  if (algo) {
    const padTop = labelH + CONTAINER_PADDING;
    layoutOptions["nodeSize.constraints"] = "[MINIMUM_SIZE, NODE_LABELS]";
    layoutOptions["nodeSize.minimum"] = `(${minWidth}, ${padTop + CONTAINER_PADDING})`;
    layoutOptions["elk.algorithm"] = algo;
    layoutOptions["elk.hierarchyHandling"] = "SEPARATE_CHILDREN";
    layoutOptions["elk.aspectRatio"] = "2.0";
    layoutOptions["elk.contentAlignment"] = "H_CENTER V_TOP";
    layoutOptions["elk.expandNodes"] = "true";
    layoutOptions["elk.padding"] = `[top=${padTop},left=${CONTAINER_PADDING},bottom=${CONTAINER_PADDING},right=${CONTAINER_PADDING}]`;
    if (algo === "elk.rectpacking") {
      const rectPadTop = labelH + RECTPACKING_CONTAINER_PADDING;
      Object.assign(layoutOptions, RECTPACKING_OPTIONS, {
        "elk.padding": `[top=${rectPadTop},left=${RECTPACKING_CONTAINER_PADDING},bottom=${RECTPACKING_CONTAINER_PADDING},right=${RECTPACKING_CONTAINER_PADDING}]`,
        "nodeSize.minimum": `(${minWidth}, ${rectPadTop + RECTPACKING_CONTAINER_PADDING})`
      });
    }
  } else if (node.dir) {
    layoutOptions["elk.algorithm"] = algorithm;
    layoutOptions["elk.direction"] = dir2ElkDirection(node.dir);
    layoutOptions["elk.hierarchyHandling"] = "SEPARATE_CHILDREN";
  }
  Object.assign(layoutOptions, SUBGRAPH_EXPERIMENT_OVERRIDES);
  return layoutOptions;
}
__name(buildSubgraphLayoutOptions, "buildSubgraphLayoutOptions");
function findCyclicEntryNodes(nodes, edges) {
  const entries = /* @__PURE__ */ new Set();
  const groups = /* @__PURE__ */ new Map();
  for (const { id, parentId } of nodes) {
    const group = groups.get(parentId);
    if (group) {
      group.push(id);
    } else {
      groups.set(parentId, [id]);
    }
  }
  for (const ids of groups.values()) {
    const idSet = new Set(ids);
    const inDegree = new Map(ids.map((id) => [id, 0]));
    const neighbors = new Map(ids.map((id) => [id, []]));
    const internalEdges = [];
    for (const edge of edges) {
      const source = edge.source == null ? void 0 : String(edge.source);
      const target = edge.target == null ? void 0 : String(edge.target);
      if (!source || !target || source === target) {
        continue;
      }
      if (!idSet.has(source) || !idSet.has(target)) {
        continue;
      }
      inDegree.set(target, (inDegree.get(target) ?? 0) + 1);
      neighbors.get(source).push(target);
      neighbors.get(target).push(source);
      internalEdges.push([source, target]);
    }
    const component = /* @__PURE__ */ new Map();
    let componentCount = 0;
    for (const id of ids) {
      if (component.has(id)) {
        continue;
      }
      const stack = [id];
      component.set(id, componentCount);
      while (stack.length > 0) {
        const current = stack.pop();
        for (const next of neighbors.get(current)) {
          if (!component.has(next)) {
            component.set(next, componentCount);
            stack.push(next);
          }
        }
      }
      componentCount++;
    }
    const hasSource = new Array(componentCount).fill(false);
    for (const id of ids) {
      if ((inDegree.get(id) ?? 0) === 0) {
        hasSource[component.get(id)] = true;
      }
    }
    if (!hasSource.includes(false)) {
      continue;
    }
    const forward = new Map(ids.map((id) => [id, []]));
    const residualInDegree = new Map(ids.map((id) => [id, 0]));
    const reaches = /* @__PURE__ */ __name((from, to) => {
      const seen = /* @__PURE__ */ new Set([from]);
      const stack = [from];
      while (stack.length > 0) {
        const current = stack.pop();
        if (current === to) {
          return true;
        }
        for (const next of forward.get(current)) {
          if (!seen.has(next)) {
            seen.add(next);
            stack.push(next);
          }
        }
      }
      return false;
    }, "reaches");
    for (const [source, target] of internalEdges) {
      if (reaches(target, source)) {
        continue;
      }
      forward.get(source).push(target);
      residualInDegree.set(target, (residualInDegree.get(target) ?? 0) + 1);
    }
    const nominated = new Array(componentCount).fill(false);
    for (const id of ids) {
      const c = component.get(id);
      if (!hasSource[c] && !nominated[c] && residualInDegree.get(id) === 0) {
        entries.add(id);
        nominated[c] = true;
      }
    }
  }
  return entries;
}
__name(findCyclicEntryNodes, "findCyclicEntryNodes");
function applyCyclicEntryConstraint(data4Layout, nodeDb) {
  if (!data4Layout.config.elk?.keepEntryNodeOnTop) {
    return;
  }
  const entryNodeIds = findCyclicEntryNodes(
    data4Layout.nodes,
    data4Layout.edges.map((edge) => ({ source: edge.start, target: edge.end }))
  );
  for (const id of entryNodeIds) {
    const elkNode = nodeDb[id];
    if (elkNode) {
      elkNode.layoutOptions = {
        ...elkNode.layoutOptions,
        "elk.layered.layering.layerConstraint": "FIRST"
      };
    }
  }
}
__name(applyCyclicEntryConstraint, "applyCyclicEntryConstraint");
function prepareLayoutForElk(data4Layout, context) {
  const elkContext = getElkLayoutContext(context);
  syncHostConfig(elkContext);
  applyElkEdgeRenderData(data4Layout, elkContext);
  return { algorithm: elkContext.algorithm };
}
__name(prepareLayoutForElk, "prepareLayoutForElk");
async function runElkLayoutCore(data4Layout, context) {
  const elkContext = getElkLayoutContext(context);
  const layoutState = buildElkGraphFromLayoutData(data4Layout, elkContext);
  const elk = new ELK();
  elkContext.log.info("Drawing flowchart using v4 renderer", elk);
  const graph = await runElkLayout(elk, layoutState.elkGraph, elkContext.log);
  applyElkLayoutResult(data4Layout, graph, layoutState, elkContext.log);
  orderNodesForElkPaint(data4Layout.nodes);
  return graph;
}
__name(runElkLayoutCore, "runElkLayoutCore");
function buildElkGraphFromLayoutData(data4Layout, elkContext) {
  const nodeDb = {};
  const elkGraph = createRootElkGraph(
    data4Layout,
    elkContext.algorithm,
    elkContext.rootLayoutOptions
  );
  const dir = data4Layout.direction ?? "DOWN";
  elkGraph.layoutOptions["elk.direction"] = dir2ElkDirection(dir);
  const parentLookupDb = addSubGraphs(data4Layout.nodes, elkContext.log);
  addVertices(data4Layout.nodes, elkGraph, nodeDb, elkContext);
  addEdgesToElkGraph(data4Layout, elkGraph, nodeDb, elkContext);
  configureSubgraphNodes(data4Layout, nodeDb, parentLookupDb, elkContext);
  configureCrossHierarchyEdges(elkGraph, nodeDb, parentLookupDb, elkContext.log);
  applyCyclicEntryConstraint(data4Layout, nodeDb);
  return { elkGraph, nodeDb, parentLookupDb };
}
__name(buildElkGraphFromLayoutData, "buildElkGraphFromLayoutData");
var render = createCommonLayoutRenderer({
  afterPaint: applyElkLineJumps,
  prepareLayout: prepareLayoutForElk,
  // ELK derives a compound node's minimum size from the measured cluster label,
  // so the label has to be measured the way `insertCluster` paints it —
  // unwrapped — rather than at the 200px flowchart wrapping width. Requested
  // here rather than sniffed for in core: core has no business knowing which
  // layout it is running.
  measureLayout: /* @__PURE__ */ __name((data4Layout, context) => defaultMeasureLayout(data4Layout, context, { unwrapGroupLabels: true }), "measureLayout"),
  runLayoutCore: runElkLayoutCore,
  paintOptions: {
    skipIntersect: true
  }
});
function syncHostConfig(elkContext) {
  setConfig(elkContext.getConfig());
}
__name(syncHostConfig, "syncHostConfig");
function orderNodesForElkPaint(nodes) {
  const nodeById = new Map(nodes.map((node) => [node.id, node]));
  nodes.sort((a, b) => {
    if (a.isGroup !== b.isGroup) {
      return a.isGroup ? -1 : 1;
    }
    if (a.isGroup && b.isGroup) {
      return getGroupDepth(a, nodeById) - getGroupDepth(b, nodeById);
    }
    return 0;
  });
}
__name(orderNodesForElkPaint, "orderNodesForElkPaint");
function getGroupDepth(node, nodeById) {
  let depth = 0;
  const visited = /* @__PURE__ */ new Set();
  let parentId = node.parentId;
  while (parentId && !visited.has(parentId)) {
    visited.add(parentId);
    const parent = nodeById.get(parentId);
    if (!parent?.isGroup) {
      break;
    }
    depth++;
    parentId = parent.parentId;
  }
  return depth;
}
__name(getGroupDepth, "getGroupDepth");
function getElkLayoutContext(context) {
  const helpers = context.helpers;
  if (!helpers) {
    throw new Error("ELK layout requires Mermaid internal helpers");
  }
  return {
    algorithm: context.preparedLayout?.algorithm ?? context.options?.algorithm,
    rootLayoutOptions: context.options?.rootLayoutOptions,
    common: helpers.common,
    getConfig: helpers.getConfig,
    interpolateToCurve: helpers.interpolateToCurve,
    log: helpers.log
  };
}
__name(getElkLayoutContext, "getElkLayoutContext");
var ELK_PRESETS = {
  // Balanced Brandes-Koepf centers simple branches and composite-state entries.
  // Layering and cycle breaking retain the release defaults.
  default: {
    layering: "NETWORK_SIMPLEX",
    placement: "BRANDES_KOEPF",
    containerPlacement: "BRANDES_KOEPF",
    alignment: "BALANCED",
    cycleBreaking: "DEPTH_FIRST"
  },
  // Reproduce the layout before presets, including ELK's own greedy cycle
  // breaking rather than the greedy-model-order value advertised by the schema.
  legacy: {
    layering: "NETWORK_SIMPLEX",
    placement: "BRANDES_KOEPF",
    containerPlacement: "BRANDES_KOEPF",
    alignment: "NONE",
    cycleBreaking: "GREEDY"
  },
  modelOrder: {
    layering: "NETWORK_SIMPLEX",
    placement: "NETWORK_SIMPLEX",
    containerPlacement: "BRANDES_KOEPF",
    alignment: "NONE",
    cycleBreaking: "GREEDY_MODEL_ORDER"
  },
  // Preserve the previous default recipe for callers selecting it by name.
  depthFirst: {
    layering: "NETWORK_SIMPLEX",
    placement: "NETWORK_SIMPLEX",
    containerPlacement: "BRANDES_KOEPF",
    alignment: "NONE",
    cycleBreaking: "DEPTH_FIRST"
  }
};
function resolveElkPreset(name) {
  return name !== void 0 && Object.hasOwn(ELK_PRESETS, name) ? ELK_PRESETS[name] : ELK_PRESETS.default;
}
__name(resolveElkPreset, "resolveElkPreset");
function createRootElkGraph(data4Layout, algorithm, rootLayoutOptions) {
  const preset = resolveElkPreset(data4Layout.config.elk?.preset);
  const graph = {
    id: "root",
    layoutOptions: {
      "elk.hierarchyHandling": "INCLUDE_CHILDREN",
      "elk.algorithm": algorithm,
      "elk.layered.nodePlacement.strategy": data4Layout.config.elk?.nodePlacementStrategy ?? preset.placement,
      "elk.layered.nodePlacement.bk.fixedAlignment": data4Layout.config.elk?.nodePlacementAlignment ?? preset.alignment,
      "elk.layered.mergeEdges": data4Layout.config.elk?.mergeEdges,
      "elk.direction": "DOWN",
      "spacing.baseValue": 40,
      "elk.layered.crossingMinimization.forceNodeModelOrder": data4Layout.config.elk?.forceNodeModelOrder,
      "elk.layered.considerModelOrder.strategy": data4Layout.config.elk?.considerModelOrder,
      "elk.layered.unnecessaryBendpoints": true,
      "elk.layered.cycleBreaking.strategy": data4Layout.config.elk?.cycleBreakingStrategy ?? preset.cycleBreaking,
      "elk.layered.layering.strategy": data4Layout.config.elk?.layeringStrategy ?? preset.layering,
      // Only COFFMAN_GRAHAM reads this; the others ignore it.
      "elk.layered.layering.coffmanGraham.layerBound": data4Layout.config.elk?.layeringLayerBound,
      // 'spacing.nodeNode': 120,
      // 'spacing.nodeNodeBetweenLayers': 25,
      // 'spacing.edgeNode': 20,
      // 'spacing.edgeNodeBetweenLayers': 10,
      // 'spacing.edgeEdge': 10,
      // 'spacing.edgeEdgeBetweenLayers': 20,
      // 'spacing.nodeSelfLoop': 20,
      // Tweaking options
      "elk.layered.wrapping.multiEdge.improveCuts": true,
      "elk.layered.wrapping.multiEdge.improveWrappedEdges": true,
      "elk.layered.edgeRouting.selfLoopDistribution": "EQUALLY",
      "elk.layered.mergeHierarchyEdges": true,
      // Reserve a margin at the ends of every side so a port cannot land on a
      // corner. ELK's default is 0, which permits it — and a corner is the one
      // boundary point with no side to leave from, so the edge came out of the
      // vertex and then ran ALONG the box's own edge before turning away. It
      // showed up on subgraphs first because a cross-boundary edge attaches to
      // the frame, which is large enough for the corner to be visible.
      //
      // Chosen at 12 by measurement, not taste: it is the smallest value that
      // clears the corner on the `elk-edge-cases` corpus. 30 was tried and
      // reorders layers, so this is not a free parameter — raising it changes
      // more than clearance.
      "elk.spacing.portsSurrounding": PORTS_SURROUNDING
    },
    children: [],
    edges: []
  };
  if (algorithm === "elk.rectpacking") {
    Object.assign(graph.layoutOptions, RECTPACKING_OPTIONS, {
      "elk.contentAlignment": "H_CENTER V_TOP",
      "elk.padding": "[top=15,left=15,bottom=15,right=15]"
    });
  }
  if (rootLayoutOptions) {
    Object.assign(graph.layoutOptions, rootLayoutOptions);
  }
  Object.assign(
    graph.layoutOptions,
    PLACEMENT_OPTIONS,
    EDGE_ROUTING_OPTIONS,
    ROOT_EXPERIMENT_OVERRIDES
  );
  return graph;
}
__name(createRootElkGraph, "createRootElkGraph");
function addSubGraphs(nodeArr, log) {
  const parentLookupDb = { parentById: {}, childrenById: {} };
  const subgraphs = nodeArr.filter((node) => node.isGroup);
  log.info("Subgraphs - ", subgraphs);
  subgraphs.forEach((subgraph) => {
    const children = nodeArr.filter((node) => node.parentId === subgraph.id);
    children.forEach((node) => {
      parentLookupDb.parentById[node.id] = subgraph.id;
      parentLookupDb.childrenById[subgraph.id] ??= [];
      parentLookupDb.childrenById[subgraph.id].push(node.id);
    });
  });
  return parentLookupDb;
}
__name(addSubGraphs, "addSubGraphs");
function addVertices(nodeArr, graph, nodeDb, elkContext, parentId) {
  const siblings = nodeArr.filter((node) => node?.parentId === parentId);
  elkContext.log.info("addVertices APA12", siblings, parentId);
  siblings.forEach((node) => {
    addVertex(graph, nodeArr, node, nodeDb, elkContext);
  });
  return graph;
}
__name(addVertices, "addVertices");
function addVertex(graph, nodeArr, node, nodeDb, elkContext) {
  const child = createElkNode(node);
  graph.children.push(child);
  nodeDb[node.id] = child;
  if (node.isGroup) {
    child.children = [];
    addVertices(nodeArr, child, nodeDb, elkContext, node.id);
    child.labelData = getMeasuredLabelData(node, elkContext.getConfig());
  }
}
__name(addVertex, "addVertex");
function createElkNode(node) {
  const child = { ...node };
  delete child.domId;
  if (node.isGroup) {
    child.children = [];
  } else {
    child.width = node.width ?? 0;
    child.height = node.height ?? 0;
    if (node.spreadPorts) {
      child.layoutOptions = {
        ...child.layoutOptions,
        "elk.portAlignment.default": "CENTER"
      };
    }
  }
  return child;
}
__name(createElkNode, "createElkNode");
function getMeasuredLabelData(node, config) {
  const existing = node.labelData;
  if (existing) {
    return existing;
  }
  if (node.labelBBox) {
    return {
      width: node.labelBBox.width,
      height: Math.max(0, node.labelBBox.height - 2),
      wrappingWidth: node.wrappingWidth ?? config.flowchart?.wrappingWidth
    };
  }
  return {
    width: 0,
    height: 0,
    wrappingWidth: node.wrappingWidth ?? config.flowchart?.wrappingWidth
  };
}
__name(getMeasuredLabelData, "getMeasuredLabelData");
function addEdgesToElkGraph(dataForLayout, graph, nodeDb, elkContext) {
  elkContext.log.info("abc78 DAGA edges = ", dataForLayout);
  const linkIdCnt = {};
  dataForLayout.edges.forEach((edge) => {
    const linkIdBase = edge.id;
    linkIdCnt[linkIdBase] = (linkIdCnt[linkIdBase] ?? -1) + 1;
    const linkId = linkIdBase;
    edge.id = linkId;
    elkContext.log.info(
      "abc78 new link id to be used is",
      linkIdBase,
      linkId,
      linkIdCnt[linkIdBase]
    );
    const { source, target, sourceId, targetId } = getEdgeStartEndPoint(edge, nodeDb);
    elkContext.log.debug("abc78 source and target", source, target);
    graph.edges.push({
      ...edge,
      sources: [source],
      targets: [target],
      sourceId,
      targetId,
      labels: [
        {
          width: edge.width ?? 0,
          height: edge.height ?? 0,
          orgWidth: edge.width ?? 0,
          orgHeight: edge.height ?? 0,
          text: edge.label ?? "",
          layoutOptions: {
            "edgeLabels.inline": "true",
            "edgeLabels.placement": "CENTER"
          }
        }
      ]
    });
  });
  return graph;
}
__name(addEdgesToElkGraph, "addEdgesToElkGraph");
function getEdgeStartEndPoint(edge, nodeDb) {
  const sourceId = edge.start;
  const targetId = edge.end;
  const source = sourceId;
  const target = targetId;
  const startNode = sourceId ? nodeDb[sourceId] : void 0;
  const endNode = targetId ? nodeDb[targetId] : void 0;
  if (!startNode || !endNode) {
    return { source, target };
  }
  return { source, target, sourceId, targetId };
}
__name(getEdgeStartEndPoint, "getEdgeStartEndPoint");
function configureSubgraphNodes(data4Layout, nodeDb, parentLookupDb, elkContext) {
  data4Layout.nodes.forEach((n) => {
    const node = nodeDb[n.id];
    if (!node || parentLookupDb.childrenById[node.id] === void 0) {
      return;
    }
    node.labels = [
      {
        text: node.label,
        width: node?.labelData?.width ?? 50,
        height: node?.labelData?.height ?? 50
      }
    ];
    elkContext.log.debug("UIO node label", node?.labelData?.width, node.padding);
    node.layoutOptions = buildSubgraphLayoutOptions(
      node,
      data4Layout.config.elk,
      elkContext.algorithm,
      elkContext.log
    );
    delete node.x;
    delete node.y;
    delete node.width;
    delete node.height;
  });
}
__name(configureSubgraphNodes, "configureSubgraphNodes");
function configureCrossHierarchyEdges(elkGraph, nodeDb, parentLookupDb, log) {
  log.debug("APA01 processing edges, count:", elkGraph.edges.length);
  elkGraph.edges.forEach((edge, index) => {
    log.debug("APA01 processing edge", index, ":", edge);
    const source = edge.sources[0];
    const target = edge.targets[0];
    log.debug("APA01 source:", source, "target:", target);
    log.debug("APA01 nodeDb[source]:", nodeDb[source]);
    log.debug("APA01 nodeDb[target]:", nodeDb[target]);
    if (nodeDb[source] && nodeDb[target] && nodeDb[source].parentId !== nodeDb[target].parentId) {
      const ancestorId = findCommonAncestor(source, target, parentLookupDb);
      setIncludeChildrenPolicy(nodeDb, source, ancestorId, log);
      setIncludeChildrenPolicy(nodeDb, target, ancestorId, log);
    }
  });
}
__name(configureCrossHierarchyEdges, "configureCrossHierarchyEdges");
function setIncludeChildrenPolicy(nodeDb, nodeId, ancestorId, log) {
  const node = nodeDb[nodeId];
  if (!node) {
    return;
  }
  node.layoutOptions ??= {};
  if (node.layoutOptions["elk.hierarchyHandling"] === "SEPARATE_CHILDREN" && resolveContainerAlgorithm(node.metadata?.algorithm)) {
    log.debug("Dropping explicit algorithm for node", node.id, "due to cross-boundary edges");
    clearContainerAlgorithmOptions(node.layoutOptions);
    Object.assign(node.layoutOptions, groupTitleSizeOptions(node));
  }
  node.layoutOptions["elk.hierarchyHandling"] = "INCLUDE_CHILDREN";
  if (node.id !== ancestorId && node.parentId) {
    setIncludeChildrenPolicy(nodeDb, node.parentId, ancestorId, log);
  }
}
__name(setIncludeChildrenPolicy, "setIncludeChildrenPolicy");
async function runElkLayout(elk, elkGraph, log) {
  const profiler = globalThis.__mermaidProfiler;
  try {
    profiler?.begin("layoutCore");
    let graph;
    try {
      graph = await elk.layout(elkGraph);
    } finally {
      profiler?.end();
    }
    log.debug("APA01 after - success");
    log.debug("APA01 layout result:", graph);
    return graph;
  } catch (error) {
    log.error("ELK layout error:", error);
    throw error;
  }
}
__name(runElkLayout, "runElkLayout");
function applyElkLayoutResult(data4Layout, graph, layoutState, log) {
  const nodeById = new Map(data4Layout.nodes.map((node) => [node.id, node]));
  applyElkNodePositions(graph.children ?? [], layoutState, nodeById, 0, 0, 0, log);
  evenGroupFrames(graph.children ?? [], layoutState, nodeById, graph);
  applyElkEdgeLayout(data4Layout, graph, layoutState, log);
}
__name(applyElkLayoutResult, "applyElkLayoutResult");
function collectDescendantIds(elkNode, into = /* @__PURE__ */ new Set()) {
  for (const child of elkNode.children ?? []) {
    into.add(child.id);
    collectDescendantIds(child, into);
  }
  return into;
}
__name(collectDescendantIds, "collectDescendantIds");
function internalEdgePoints(graph, descendants, layoutState, groupId) {
  const points = [];
  for (const edge of graph.edges ?? []) {
    const source = edge.sources?.[0] ?? edge.start;
    const target = edge.targets?.[0] ?? edge.end;
    const isInternal = descendants.has(source) && descendants.has(target);
    const attachesAtStart = source === groupId;
    const attachesAtEnd = target === groupId;
    if (!isInternal && !attachesAtStart && !attachesAtEnd) {
      continue;
    }
    const offset = calcOffset(source, target, layoutState.parentLookupDb, layoutState.nodeDb);
    for (const section of edge.sections ?? []) {
      const sectionPoints = isInternal ? [section.startPoint, ...section.bendPoints ?? [], section.endPoint] : [attachesAtStart ? section.startPoint : null, attachesAtEnd ? section.endPoint : null];
      for (const p of sectionPoints) {
        if (p) {
          points.push({ x: p.x + offset.x, y: p.y + offset.y });
        }
      }
    }
  }
  return points;
}
__name(internalEdgePoints, "internalEdgePoints");
function evenGroupFrames(elkNodes, layoutState, nodeById, graph = {}) {
  for (const elkNode of elkNodes) {
    if (!elkNode?.isGroup) {
      continue;
    }
    const children = elkNode.children ?? [];
    evenGroupFrames(children, layoutState, nodeById, graph);
    const group = layoutState.nodeDb[elkNode.id];
    const boxes = children.map((child) => layoutState.nodeDb[child.id]).filter((child) => child?.offset && child.width && child.height);
    if (!group?.offset || boxes.length === 0) {
      continue;
    }
    const lane = internalEdgePoints(graph, collectDescendantIds(elkNode), layoutState, elkNode.id);
    const xs = [
      ...boxes.map((b) => b.offset.posX),
      ...boxes.map((b) => b.offset.posX + b.width),
      ...lane.map((p) => p.x)
    ];
    const ys = [
      ...boxes.map((b) => b.offset.posY),
      ...boxes.map((b) => b.offset.posY + b.height),
      ...lane.map((p) => p.y)
    ];
    const origin = group.offset;
    const left = Math.max(origin.posX, Math.min(...xs) - SUBGRAPH_PADDING);
    const right = Math.min(origin.posX + group.width, Math.max(...xs) + SUBGRAPH_PADDING);
    const bottom = Math.min(origin.posY + group.height, Math.max(...ys) + SUBGRAPH_PADDING);
    const top = origin.posY;
    const labelFloor = groupTitleWidth(elkNode);
    let x = left;
    let width = right - left;
    if (width < labelFloor) {
      x -= (labelFloor - width) / 2;
      width = labelFloor;
      const origRight = origin.posX + group.width;
      x = Math.max(origin.posX, Math.min(x, origRight - width));
      width = Math.min(width, group.width);
    }
    const height = bottom - top;
    if (height <= 0 || width <= 0) {
      continue;
    }
    group.elkOrigin ??= { posX: origin.posX, posY: origin.posY };
    group.offset.posX = x;
    group.offset.width = width;
    group.offset.height = height;
    group.width = width;
    group.height = height;
    group.x = x + width / 2;
    group.y = top + height / 2;
    const layoutNode = nodeById.get(elkNode.id);
    if (layoutNode) {
      layoutNode.x = group.x;
      layoutNode.y = group.y;
      layoutNode.width = width;
      layoutNode.height = height;
    }
  }
}
__name(evenGroupFrames, "evenGroupFrames");
function applyElkNodePositions(nodeArray, layoutState, nodeById, relX, relY, depth, log) {
  nodeArray.forEach((node) => {
    if (!node) {
      return;
    }
    const graphNode = layoutState.nodeDb[node.id] ?? node;
    const width = Math.max(node.width, node.labels ? node.labels[0]?.width || 0 : 0);
    const offset = {
      posX: node.x + relX,
      posY: node.y + relY,
      x: relX,
      y: relY,
      depth,
      width,
      height: node.height
    };
    graphNode.offset = offset;
    graphNode.x = offset.posX + node.width / 2;
    graphNode.y = offset.posY + node.height / 2;
    graphNode.width = node.width;
    graphNode.height = node.height;
    const layoutNode = nodeById.get(node.id);
    if (layoutNode) {
      layoutNode.x = graphNode.x;
      layoutNode.y = graphNode.y;
      layoutNode.width = node.isGroup ? Math.max(node.width, node.labelData?.width ?? 0) : node.width;
      layoutNode.height = node.height;
      const layoutNodeLabels = layoutNode;
      layoutNodeLabels.labelData = node.labelData;
      layoutNodeLabels.labels = node.labels;
    }
    if (node.isGroup) {
      log.debug("Id abc88 subgraph = ", node.id, node.x, node.y, node.labelData);
      applyElkNodePositions(
        node.children ?? [],
        layoutState,
        nodeById,
        offset.posX,
        offset.posY,
        depth + 1,
        log
      );
    } else {
      log.info(
        "Id NODE = ",
        node.id,
        node.x,
        node.y,
        relX,
        relY,
        `translate(${graphNode.x}, ${graphNode.y})`
      );
    }
  });
}
__name(applyElkNodePositions, "applyElkNodePositions");
var TERMINAL_JOG_MAX = 16;
var TERMINAL_RUN_MAX = 30;
var JOG_EPS = 0.01;
function axisOf(a, b) {
  const dx = Math.abs(b.x - a.x);
  const dy = Math.abs(b.y - a.y);
  if (dx > JOG_EPS && dy <= JOG_EPS) {
    return "h";
  }
  if (dy > JOG_EPS && dx <= JOG_EPS) {
    return "v";
  }
  return void 0;
}
__name(axisOf, "axisOf");
function straightenTerminalJogs(points) {
  let pts = straightenFront(points) ?? points;
  const reversed = [...pts].reverse();
  const fixedEnd = straightenFront(reversed);
  if (fixedEnd) {
    pts = fixedEnd.reverse();
  }
  return pts;
}
__name(straightenTerminalJogs, "straightenTerminalJogs");
function straightenFront(pts) {
  if (pts.length < 5) {
    return null;
  }
  const [p0, p1, p2, p3] = pts;
  const axis = axisOf(p0, p1);
  if (!axis || axisOf(p2, p3) !== axis || axisOf(p1, p2) !== (axis === "h" ? "v" : "h")) {
    return null;
  }
  if (Math.hypot(p1.x - p0.x, p1.y - p0.y) > TERMINAL_RUN_MAX) {
    return null;
  }
  const jog = axis === "h" ? Math.abs(p2.y - p1.y) : Math.abs(p2.x - p1.x);
  if (jog < JOG_EPS || jog > TERMINAL_JOG_MAX) {
    return null;
  }
  const forward = axis === "h" ? Math.sign(p1.x - p0.x) === Math.sign(p3.x - p2.x) : Math.sign(p1.y - p0.y) === Math.sign(p3.y - p2.y);
  if (!forward) {
    return null;
  }
  let last = 3;
  while (last + 1 < pts.length && axisOf(pts[last], pts[last + 1]) === axis) {
    last++;
  }
  if (last === pts.length - 1) {
    return null;
  }
  const moved = [...pts];
  for (let i = 2; i <= last; i++) {
    moved[i] = axis === "h" ? { x: pts[i].x, y: p0.y } : { x: p0.x, y: pts[i].y };
  }
  moved.splice(1, 2);
  return moved;
}
__name(straightenFront, "straightenFront");
function segmentsCrossStrict(a1, a2, b1, b2) {
  const side = /* @__PURE__ */ __name((o, p, q) => (p.x - o.x) * (q.y - o.y) - (p.y - o.y) * (q.x - o.x), "side");
  const d1 = side(b1, b2, a1);
  const d2 = side(b1, b2, a2);
  const d3 = side(a1, a2, b1);
  const d4 = side(a1, a2, b2);
  return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
}
__name(segmentsCrossStrict, "segmentsCrossStrict");
function crossingCount(a, b) {
  let n = 0;
  for (let i = 0; i < a.length - 1; i++) {
    for (let j = 0; j < b.length - 1; j++) {
      if (segmentsCrossStrict(a[i], a[i + 1], b[j], b[j + 1])) {
        n++;
      }
    }
  }
  return n;
}
__name(crossingCount, "crossingCount");
function straightenEdgeTerminals(edges) {
  const routes = edges.map((edge) => edge.points ?? []);
  for (const [index, edge] of edges.entries()) {
    const original = routes[index];
    if (original.length < 5) {
      continue;
    }
    const candidate = straightenTerminalJogs(original);
    if (candidate === original) {
      continue;
    }
    let before = 0;
    let after = 0;
    for (const [other, route] of routes.entries()) {
      if (other === index || route.length < 2) {
        continue;
      }
      before += crossingCount(original, route);
      after += crossingCount(candidate, route);
    }
    if (after > before) {
      continue;
    }
    edge.points = candidate;
    routes[index] = candidate;
  }
}
__name(straightenEdgeTerminals, "straightenEdgeTerminals");
function applyElkEdgeLayout(data4Layout, graph, layoutState, log) {
  const edgeById = new Map(data4Layout.edges.map((edge) => [edge.id, edge]));
  const straightenEdges = data4Layout.config.elk?.straightenEdges !== false;
  const layoutNodeById = new Map(data4Layout.nodes.map((node) => [node.id, node]));
  const alignedNodes = /* @__PURE__ */ new Set();
  graph.edges?.forEach((edge) => {
    if (!edge.sections?.length) {
      return;
    }
    const startNode = layoutState.nodeDb[edge.sources?.[0] ?? edge.start];
    const endNode = layoutState.nodeDb[edge.targets?.[0] ?? edge.end];
    if (!startNode || !endNode) {
      return;
    }
    const sourceId = edge.start ?? edge.sourceId ?? edge.sources?.[0];
    const targetId = edge.end ?? edge.targetId ?? edge.targets?.[0];
    const offset = calcOffset(sourceId, targetId, layoutState.parentLookupDb, layoutState.nodeDb);
    const section = edge.sections[0];
    if (startNode.shape !== "rect33") {
      alignDegenerateNodeToAnchor(
        startNode,
        { x: section.startPoint.x + offset.x, y: section.startPoint.y + offset.y },
        layoutNodeById,
        alignedNodes
      );
    }
    if (endNode.shape !== "rect33") {
      alignDegenerateNodeToAnchor(
        endNode,
        { x: section.endPoint.x + offset.x, y: section.endPoint.y + offset.y },
        layoutNodeById,
        alignedNodes
      );
    }
  });
  graph.edges?.forEach((edge) => {
    const layoutEdge = edgeById.get(edge.id);
    if (!layoutEdge) {
      return;
    }
    const startId = edge.sources?.[0] ?? edge.start;
    const endId = edge.targets?.[0] ?? edge.end;
    const startNode = layoutState.nodeDb[startId];
    const endNode = layoutState.nodeDb[endId];
    if (!startNode || !endNode) {
      return;
    }
    if (!edge.sections?.length) {
      const centre = /* @__PURE__ */ __name((node) => ({
        x: (node.offset?.posX ?? node.x ?? 0) + (node.width ?? 0) / 2,
        y: (node.offset?.posY ?? node.y ?? 0) + (node.height ?? 0) / 2
      }), "centre");
      const from = centre(startNode);
      const to = centre(endNode);
      startNode.x = from.x;
      startNode.y = from.y;
      endNode.x = to.x;
      endNode.y = to.y;
      const straightPoints = sanitizeElkEdgePoints([from, to], startNode, endNode, log);
      layoutEdge.points = straightPoints;
      layoutEdge.curve = "linear";
      const lineStart = straightPoints[0];
      const lineEnd = straightPoints[straightPoints.length - 1];
      layoutEdge.x = (lineStart.x + lineEnd.x) / 2;
      layoutEdge.y = (lineStart.y + lineEnd.y) / 2;
      log.debug("APA18 no edge sections, using a straight line", edge.id, layoutEdge.points);
      return;
    }
    const sourceId = edge.start ?? edge.sourceId ?? startId;
    const targetId = edge.end ?? edge.targetId ?? endId;
    const offset = calcOffset(sourceId, targetId, layoutState.parentLookupDb, layoutState.nodeDb);
    log.debug("APA18 offset", offset, sourceId, " ==> ", targetId, "edge:", edge, startNode);
    const section = edge.sections[0];
    const points = createEdgePointsFromSection(section, offset);
    startNode.x = startNode.offset.posX + startNode.width / 2;
    startNode.y = startNode.offset.posY + startNode.height / 2;
    endNode.x = endNode.offset.posX + endNode.width / 2;
    endNode.y = endNode.offset.posY + endNode.height / 2;
    if (startNode.shape !== "rect33") {
      points.unshift({ x: startNode.x, y: startNode.y });
    }
    if (endNode.shape !== "rect33") {
      points.push({ x: endNode.x, y: endNode.y });
    }
    const clipped = sanitizeElkEdgePoints(points, startNode, endNode, log);
    layoutEdge.points = ensureStartMarkerSegmentLength(
      ensureEndMarkerSegmentLength(
        clipped,
        boundsFor(endNode),
        getEndMarkerPathOffset(layoutEdge),
        log
      ),
      boundsFor(startNode),
      getStartMarkerPathOffset(layoutEdge),
      log
    );
    layoutEdge.curve = "rounded";
    const label = edge.labels?.[0];
    if (label) {
      layoutEdge.x = label.x + offset.x + label.width / 2;
      layoutEdge.y = label.y + offset.y + label.height / 2;
    }
  });
  if (straightenEdges) {
    straightenEdgeTerminals(data4Layout.edges);
  }
}
__name(applyElkEdgeLayout, "applyElkEdgeLayout");
function alignDegenerateNodeToAnchor(node, anchor, layoutNodeById, alignedNodes) {
  const width = node.width ?? 0;
  const height = node.height ?? 0;
  const top = node.offset.posY;
  const bottom = top + height;
  const tol = 0.5;
  const alongWidth = Math.abs(anchor.y - top) <= tol || Math.abs(anchor.y - bottom) <= tol;
  if ((alongWidth ? width : height) >= 2 * PORTS_SURROUNDING_MARGIN) {
    return;
  }
  if (alignedNodes.has(node.id)) {
    return;
  }
  alignedNodes.add(node.id);
  const delta = alongWidth ? anchor.x - (node.offset.posX + width / 2) : anchor.y - (node.offset.posY + height / 2);
  if (Math.abs(delta) < 0.01) {
    return;
  }
  if (alongWidth) {
    node.offset.posX += delta;
    node.x = node.offset.posX + width / 2;
  } else {
    node.offset.posY += delta;
    node.y = node.offset.posY + height / 2;
  }
  const layoutNode = layoutNodeById.get(node.id);
  if (layoutNode) {
    layoutNode.x = node.offset.posX + width / 2;
    layoutNode.y = node.offset.posY + height / 2;
  }
}
__name(alignDegenerateNodeToAnchor, "alignDegenerateNodeToAnchor");
function createEdgePointsFromSection(section, offset) {
  const src = section.startPoint;
  const dest = section.endPoint;
  const segments = section.bendPoints ? section.bendPoints : [];
  const segPoints = segments.map((segment) => ({
    x: segment.x + offset.x,
    y: segment.y + offset.y
  }));
  return [
    { x: src.x + offset.x, y: src.y + offset.y },
    ...segPoints,
    { x: dest.x + offset.x, y: dest.y + offset.y }
  ];
}
__name(createEdgePointsFromSection, "createEdgePointsFromSection");
function calcOffset(src, dest, parentLookupDb, nodeDb) {
  const ancestor = findCommonAncestor(src, dest, parentLookupDb);
  if (ancestor === void 0 || ancestor === "root") {
    return { x: 0, y: 0 };
  }
  const node = nodeDb[ancestor];
  const ancestorOffset = node?.elkOrigin ?? node?.offset;
  return {
    x: ancestorOffset?.posX ?? 0,
    y: ancestorOffset?.posY ?? 0
  };
}
__name(calcOffset, "calcOffset");
function sanitizeElkEdgePoints(points, startNode, endNode, log) {
  const prevPoints = Array.isArray(points) ? [...points] : [];
  const endBounds = boundsFor(endNode);
  log.debug(
    "PPP cutter2: Points before cutter2:",
    JSON.stringify(points),
    "endBounds:",
    endBounds,
    onBorder(endBounds, points[points.length - 1])
  );
  let clippedPoints;
  {
    const startBounds = boundsFor(startNode);
    const endBounds2 = boundsFor(endNode);
    const startIsGroup = !!startNode?.isGroup;
    const endIsGroup = !!endNode?.isGroup;
    const { candidate: startCandidate, centerApprox: startCenterApprox } = getCandidateBorderPoint(
      prevPoints,
      startNode,
      "start"
    );
    const { candidate: endCandidate, centerApprox: endCenterApprox } = getCandidateBorderPoint(
      prevPoints,
      endNode,
      "end"
    );
    let skipStart = startIsGroup && onBorder(startBounds, startCandidate);
    let skipEnd = endIsGroup && onBorder(endBounds2, endCandidate);
    dropAutoCenterPoint(prevPoints, "start", skipStart && startCenterApprox);
    dropAutoCenterPoint(prevPoints, "end", skipEnd && endCenterApprox);
    if (startIsGroup && !skipStart) {
      skipStart = clipGroupEndpoint(prevPoints, startBounds, "start");
    }
    if (endIsGroup && !skipEnd) {
      skipEnd = clipGroupEndpoint(prevPoints, endBounds2, "end");
    }
    if (skipStart || skipEnd) {
      if (!skipStart) {
        applyStartIntersectionIfNeeded(prevPoints, startNode, startBounds, log);
      }
      if (!skipEnd) {
        applyEndIntersectionIfNeeded(prevPoints, endNode, endBounds2, log);
      }
      log.debug("PPP cutter2: skipping cutter2 due to on-border group endpoint(s)", {
        skipStart,
        skipEnd,
        startCenterApprox,
        endCenterApprox,
        startCandidate,
        endCandidate
      });
      clippedPoints = prevPoints;
    } else {
      clippedPoints = cutter2(startNode, endNode, prevPoints, log);
    }
  }
  log.debug("PPP cutter2: Points after cutter2:", JSON.stringify(clippedPoints));
  if (!Array.isArray(clippedPoints) || clippedPoints.length < 2 || hasInvalidPoint(clippedPoints)) {
    log.warn("POI cutter2: Invalid points from cutter2, falling back to prevPoints", clippedPoints);
    const cleaned = prevPoints.filter((p) => Number.isFinite(p?.x) && Number.isFinite(p?.y));
    clippedPoints = cleaned.length >= 2 ? cleaned : prevPoints;
  }
  log.debug("UIO cutter2: Points after cutter2 (sanitized):", clippedPoints);
  return dedupeConsecutivePoints(clippedPoints, log);
}
__name(sanitizeElkEdgePoints, "sanitizeElkEdgePoints");
function hasInvalidPoint(points) {
  return points?.some((point) => !Number.isFinite(point?.x) || !Number.isFinite(point?.y));
}
__name(hasInvalidPoint, "hasInvalidPoint");
function dedupeConsecutivePoints(points, log) {
  const deduped = points.filter((point, index, arr) => {
    if (index === 0) {
      return true;
    }
    const prev = arr[index - 1];
    return Math.abs(point.x - prev.x) > 1e-6 || Math.abs(point.y - prev.y) > 1e-6;
  });
  if (deduped.length !== points.length) {
    log.debug("UIO cutter2: removed consecutive duplicate points", {
      before: points,
      after: deduped
    });
  }
  return deduped;
}
__name(dedupeConsecutivePoints, "dedupeConsecutivePoints");
function getEndMarkerPathOffset(edge) {
  return markerPathOffset(edge.arrowTypeEnd);
}
__name(getEndMarkerPathOffset, "getEndMarkerPathOffset");
function getStartMarkerPathOffset(edge) {
  return markerPathOffset(edge.arrowTypeStart);
}
__name(getStartMarkerPathOffset, "getStartMarkerPathOffset");
function ensureStartMarkerSegmentLength(points, startBounds, markerOffset, log) {
  if (markerOffset <= 0 || points.length < 3) {
    return points;
  }
  const start = points[0];
  const exit = points[1];
  const segmentLength = Math.hypot(exit.x - start.x, exit.y - start.y);
  if (segmentLength >= Math.max(MIN_END_MARKER_SEGMENT_LENGTH, markerOffset * 2)) {
    return points;
  }
  if (!onBorder(startBounds, exit, 1)) {
    return points;
  }
  const adjusted = [start, ...points.slice(2)];
  log.debug("UIO cutter2: removed short start marker segment", {
    before: points,
    after: adjusted,
    markerOffset,
    segmentLength
  });
  return adjusted;
}
__name(ensureStartMarkerSegmentLength, "ensureStartMarkerSegmentLength");
function ensureEndMarkerSegmentLength(points, endBounds, markerOffset, log) {
  if (markerOffset <= 0 || points.length < 3) {
    return points;
  }
  const end = points[points.length - 1];
  const entry = points[points.length - 2];
  const segmentLength = Math.hypot(end.x - entry.x, end.y - entry.y);
  if (segmentLength >= Math.max(MIN_END_MARKER_SEGMENT_LENGTH, markerOffset * 2)) {
    return points;
  }
  if (!onBorder(endBounds, entry, 1)) {
    return points;
  }
  const adjusted = [...points.slice(0, -2), end];
  log.debug("UIO cutter2: removed short end marker segment", {
    before: points,
    after: adjusted,
    markerOffset,
    segmentLength
  });
  return adjusted;
}
__name(ensureEndMarkerSegmentLength, "ensureEndMarkerSegmentLength");
function applyElkEdgeRenderData(data4Layout, elkContext) {
  const defaultInterpolate = data4Layout.edges.defaultInterpolate;
  const defaultStyle = data4Layout.edges.defaultStyle;
  const conf = elkContext.getConfig();
  data4Layout.edges.forEach((edge) => {
    const edgeData = buildEdgeData(
      edge,
      {
        defaultStyle,
        defaultInterpolate,
        confCurve: conf.curve
      },
      elkContext
    );
    Object.assign(edge, edgeData);
  });
}
__name(applyElkEdgeRenderData, "applyElkEdgeRenderData");
function buildFallbackEdgeClasses(edge) {
  if (edge.classes !== void 0) {
    return edge.classes;
  }
  if (edge.start && edge.end) {
    return `flowchart-link LS_${edge.start} LE_${edge.end}`;
  }
  return void 0;
}
__name(buildFallbackEdgeClasses, "buildFallbackEdgeClasses");
function computeStroke(stroke, defaultStyle, defaultLabelStyle) {
  let thickness = "normal";
  let pattern = "solid";
  let style = [];
  let labelStyle = [];
  if (stroke === "dotted") {
    pattern = "dotted";
    style = ["fill:none", "stroke-width:2px", "stroke-dasharray:3"];
  } else if (stroke === "thick") {
    thickness = "thick";
    style = ["stroke-width: 3.5px", "fill:none"];
  } else {
    style = defaultStyle ?? ["fill:none"];
    if (defaultLabelStyle !== void 0) {
      labelStyle = defaultLabelStyle;
    }
  }
  return { thickness, pattern, style, labelStyle };
}
__name(computeStroke, "computeStroke");
function getCurve(edgeInterpolate, edgesDefaultInterpolate, confCurve) {
  if (edgeInterpolate !== void 0) {
    return edgeInterpolate;
  }
  if (edgesDefaultInterpolate !== void 0) {
    return edgesDefaultInterpolate;
  }
  return confCurve;
}
__name(getCurve, "getCurve");
function buildEdgeData(edge, defaults, elkContext) {
  const edgeData = {};
  edgeData.minlen = edge.minlen ?? edge.length ?? 1;
  edgeData.text = edge.text ?? edge.label;
  edgeData.arrowhead = edge.arrowhead ?? (edge.type === "arrow_open" ? "none" : "normal");
  const arrowMap = ARROW_MAP[edge.type ?? "arrow_open"] ?? ARROW_MAP.arrow_open;
  edgeData.arrowTypeStart = edge.arrowTypeStart ?? arrowMap[0];
  edgeData.arrowTypeEnd = edge.arrowTypeEnd ?? arrowMap[1];
  edgeData.startLabelRight = edge.startLabelRight;
  edgeData.endLabelLeft = edge.endLabelLeft;
  const strokeRes = computeStroke(edge.stroke, defaults.defaultStyle, defaults.defaultLabelStyle);
  edgeData.thickness = edge.thickness ?? strokeRes.thickness;
  edgeData.pattern = edge.pattern ?? strokeRes.pattern;
  edgeData.style = edge.style ?? strokeRes.style;
  edgeData.labelStyle = edge.labelStyle ?? strokeRes.labelStyle;
  edgeData.classes = buildFallbackEdgeClasses(edge);
  edgeData.curve = elkContext.interpolateToCurve(
    getCurve(edge.curve ?? edge.interpolate, defaults.defaultInterpolate, defaults.confCurve),
    curveLinear
  );
  const hasText = (edgeData.text ?? "") !== "";
  if (edge.arrowheadStyle !== void 0) {
    edgeData.arrowheadStyle = edge.arrowheadStyle;
  } else if (hasText || edge.style !== void 0) {
    edgeData.arrowheadStyle = "fill: #333";
  }
  edgeData.labelpos = edge.labelpos ?? (hasText ? "c" : void 0);
  edgeData.labelType = edge.labelType;
  edgeData.label = (edge.label ?? edgeData.text ?? "").replace(
    elkContext.common.lineBreakRegex,
    "\n"
  );
  return edgeData;
}
__name(buildEdgeData, "buildEdgeData");
function getEffectiveGroupWidth(node) {
  return Math.max(node.width ?? 0, groupTitleWidth(node));
}
__name(getEffectiveGroupWidth, "getEffectiveGroupWidth");
function boundsFor(node) {
  const width = node?.isGroup ? getEffectiveGroupWidth(node) : node.width;
  return {
    x: node.offset.posX + node.width / 2,
    y: node.offset.posY + node.height / 2,
    width: width ?? 0,
    height: node.height ?? 0,
    padding: node.padding
  };
}
__name(boundsFor, "boundsFor");
function approxEq(a, b, eps = 1e-6) {
  return Math.abs(a - b) < eps;
}
__name(approxEq, "approxEq");
function isCenterApprox(point, node) {
  return approxEq(point.x, node.x ?? 0) && approxEq(point.y, node.y ?? 0);
}
__name(isCenterApprox, "isCenterApprox");
function getCandidateBorderPoint(points, node, side) {
  if (!points?.length) {
    return { candidate: { x: node.x ?? 0, y: node.y ?? 0 }, centerApprox: true };
  }
  if (side === "start") {
    const first = points[0];
    const centerApprox = isCenterApprox(first, node);
    const candidate = centerApprox && points.length > 1 ? points[1] : first;
    return { candidate, centerApprox };
  } else {
    const last = points[points.length - 1];
    const centerApprox = isCenterApprox(last, node);
    const candidate = centerApprox && points.length > 1 ? points[points.length - 2] : last;
    return { candidate, centerApprox };
  }
}
__name(getCandidateBorderPoint, "getCandidateBorderPoint");
function dropAutoCenterPoint(points, side, doDrop) {
  if (!doDrop) {
    return;
  }
  if (side === "start") {
    if (points.length > 0) {
      points.shift();
    }
  } else {
    if (points.length > 0) {
      points.pop();
    }
  }
}
__name(dropAutoCenterPoint, "dropAutoCenterPoint");
function clipGroupEndpoint(points, bounds, side) {
  const step = side === "start" ? 1 : -1;
  let index = side === "start" ? 0 : points.length - 1;
  const terminalIndex = index;
  while (index >= 0 && index < points.length && !outsideNode(bounds, points[index])) {
    index += step;
  }
  if (index === terminalIndex || index < 0 || index >= points.length) {
    return false;
  }
  const outside = points[index];
  const inside = points[index - step];
  const dx = outside.x - inside.x;
  const dy = outside.y - inside.y;
  const tx = dx === 0 ? Infinity : (bounds.x + Math.sign(dx) * bounds.width / 2 - inside.x) / dx;
  const ty = dy === 0 ? Infinity : (bounds.y + Math.sign(dy) * bounds.height / 2 - inside.y) / dy;
  const t = Math.min(tx, ty);
  const crossing = { x: inside.x + t * dx, y: inside.y + t * dy };
  if (side === "start") {
    points.splice(0, index, crossing);
  } else {
    points.splice(index + 1, points.length - index - 1, crossing);
  }
  return true;
}
__name(clipGroupEndpoint, "clipGroupEndpoint");
function applyStartIntersectionIfNeeded(points, startNode, startBounds, log) {
  let firstOutsideStartIndex = -1;
  for (const [index, point] of points.entries()) {
    if (outsideNode(startBounds, point)) {
      firstOutsideStartIndex = index;
      break;
    }
  }
  if (firstOutsideStartIndex !== -1) {
    const outsidePointForStart = points[firstOutsideStartIndex];
    const startCenter = points[0];
    const startIntersection = computeNodeIntersection(
      startNode,
      startBounds,
      outsidePointForStart,
      startCenter
    );
    replaceEndpoint(points, "start", startIntersection);
    log.debug("UIO cutter2: start-only intersection applied", { startIntersection });
  }
}
__name(applyStartIntersectionIfNeeded, "applyStartIntersectionIfNeeded");
function applyEndIntersectionIfNeeded(points, endNode, endBounds, log) {
  let outsideIndexForEnd = -1;
  for (let index = points.length - 1; index >= 0; index--) {
    if (outsideNode(endBounds, points[index])) {
      outsideIndexForEnd = index;
      break;
    }
  }
  if (outsideIndexForEnd !== -1) {
    const outsidePointForEnd = points[outsideIndexForEnd];
    const endCenter = points[points.length - 1];
    const endIntersection = computeNodeIntersection(
      endNode,
      endBounds,
      outsidePointForEnd,
      endCenter
    );
    replaceEndpoint(points, "end", endIntersection);
    log.debug("UIO cutter2: end-only intersection applied", { endIntersection });
  }
}
__name(applyEndIntersectionIfNeeded, "applyEndIntersectionIfNeeded");
function attachAlongDepartureAxis(node, bounds, points, portIndex, step) {
  if (node?.isGroup) {
    return null;
  }
  const port = points[portIndex];
  const next = points[portIndex + step];
  if (!port || !next) {
    return null;
  }
  return outlineAttachPoint(node, bounds, port, next);
}
__name(attachAlongDepartureAxis, "attachAlongDepartureAxis");
function cutter2(startNode, endNode, originalPoints, log) {
  const startBounds = boundsFor(startNode);
  const endBounds = boundsFor(endNode);
  if (originalPoints.length === 0) {
    return [];
  }
  const points = [...originalPoints];
  const startCenter = points[0];
  const endCenter = points[points.length - 1];
  log.debug("PPP cutter2: bounds", { startBounds, endBounds });
  log.debug("PPP cutter2: original points", originalPoints);
  let firstOutsideStartIndex = -1;
  for (const [index, point] of points.entries()) {
    if (firstOutsideStartIndex === -1 && outsideNode(startBounds, point)) {
      firstOutsideStartIndex = index;
    }
  }
  if (firstOutsideStartIndex !== -1) {
    const outsidePointForStart = points[firstOutsideStartIndex];
    const startIntersection = (
      // Prefer an attachment on the edge's own departure axis; see
      // `outlineAttachPoint`. Falls back to the centre-ray intersection, which
      // is all a non-axis-aligned or shapeless endpoint can offer.
      attachAlongDepartureAxis(startNode, startBounds, points, firstOutsideStartIndex, 1) ?? computeNodeIntersection(startNode, startBounds, outsidePointForStart, startCenter)
    );
    log.debug("UIO cutter2: start intersection", startIntersection);
    replaceEndpoint(points, "start", startIntersection);
  }
  let outsidePointForEnd = null;
  let outsideIndexForEnd = -1;
  for (let index = points.length - 1; index >= 0; index--) {
    if (outsideNode(endBounds, points[index])) {
      outsidePointForEnd = points[index];
      outsideIndexForEnd = index;
      break;
    }
  }
  if (!outsidePointForEnd && points.length > 1) {
    outsidePointForEnd = points[points.length - 2];
    outsideIndexForEnd = points.length - 2;
  }
  if (outsidePointForEnd) {
    const endIntersection = attachAlongDepartureAxis(endNode, endBounds, points, outsideIndexForEnd, -1) ?? computeNodeIntersection(endNode, endBounds, outsidePointForEnd, endCenter);
    log.debug("UIO cutter2: end intersection", { endIntersection, outsideIndexForEnd });
    replaceEndpoint(points, "end", endIntersection);
  }
  if (points.length > 1) {
    const lastPoint = points[points.length - 1];
    const secondLastPoint = points[points.length - 2];
    const distance = Math.sqrt(
      (lastPoint.x - secondLastPoint.x) ** 2 + (lastPoint.y - secondLastPoint.y) ** 2
    );
    if (distance < 2) {
      log.debug("UIO cutter2: trimming tail point (too close)", {
        distance,
        lastPoint,
        secondLastPoint
      });
      points.pop();
    }
  }
  log.debug("UIO cutter2: final points", points);
  return points;
}
__name(cutter2, "cutter2");
export {
  render
};