function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
/**
 * 拓扑连线：锚点坐标、自动选边、正交折线路径
 */

import { gapToPx } from "./layoutMeasurements";
var ANCHORS = ['top', 'right', 'bottom', 'left'];

/**
 * 将图元逻辑坐标转为内容区像素矩形（与 EditableItemLayer 一致）
 */
export function getItemPixelRect(item, options) {
  var cellSize = options.cellSize,
    itemUnit = options.itemUnit,
    _options$itemGap = options.itemGap,
    itemGap = _options$itemGap === void 0 ? 0 : _options$itemGap,
    override = options.override;
  var g = gapToPx(itemGap);
  var left;
  var top;
  var width;
  var height;
  if (itemUnit === 'pixel') {
    var _item$width, _item$height;
    width = (_item$width = item.width) !== null && _item$width !== void 0 ? _item$width : cellSize;
    height = (_item$height = item.height) !== null && _item$height !== void 0 ? _item$height : cellSize;
    left = item.x;
    top = item.y;
  } else {
    var _item$width2, _item$height2;
    var w = (_item$width2 = item.width) !== null && _item$width2 !== void 0 ? _item$width2 : 1;
    var h = (_item$height2 = item.height) !== null && _item$height2 !== void 0 ? _item$height2 : 1;
    left = g > 0 ? item.x * (cellSize + g) + g : item.x * cellSize;
    top = g > 0 ? item.y * (cellSize + g) + g : item.y * cellSize;
    width = g > 0 ? w * cellSize + (w - 1) * g : w * cellSize;
    height = g > 0 ? h * cellSize + (h - 1) * g : h * cellSize;
  }
  if (override) {
    if (override.left != null) left = override.left;
    if (override.top != null) top = override.top;
    if (override.width != null) width = override.width;
    if (override.height != null) height = override.height;
  }
  return {
    left: left,
    top: top,
    width: width,
    height: height
  };
}
export function rectCenter(rect) {
  return {
    x: rect.left + rect.width / 2,
    y: rect.top + rect.height / 2
  };
}

/**
 * 锚点在矩形边框上的坐标
 */
export function getAnchorPoint(rect, anchor) {
  var cx = rect.left + rect.width / 2;
  var cy = rect.top + rect.height / 2;
  switch (anchor) {
    case 'top':
      return {
        x: cx,
        y: rect.top
      };
    case 'right':
      return {
        x: rect.left + rect.width,
        y: cy
      };
    case 'bottom':
      return {
        x: cx,
        y: rect.top + rect.height
      };
    case 'left':
      return {
        x: rect.left,
        y: cy
      };
    case 'center':
    default:
      return {
        x: cx,
        y: cy
      };
  }
}

/** 与 EdgeLayer 端口标签 pill 高度一致 */
export var EDGE_PORT_LABEL_HEIGHT = 18;

/** 标签中心相对边框锚点的外移间距（px） */
export var EDGE_PORT_LABEL_OFFSET_GAP = 6;

/**
 * 编辑态连线锚点圆点外移距离（px），避开 EditableItemLayer 边中点 20×20 缩放手柄。
 */
export var EDGE_PORT_MARKER_OUTSET = 16;

/** 折线绕开图元矩形的间距（px） */
export var EDGE_ROUTE_RECT_CLEARANCE = 10;

/**
 * 编辑态锚点圆点 UI 位置（相对图元局部坐标），沿外法线外移，连线几何仍用 {@link getAnchorPoint}。
 */
export function getPortMarkerDisplayPoint(rect, anchor) {
  var outset = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : EDGE_PORT_MARKER_OUTSET;
  var base = getAnchorPoint(rect, anchor);
  var v = sideVector(anchor);
  return {
    x: base.x + v.dx * outset,
    y: base.y + v.dy * outset
  };
}
export function getPortLabelPosition(rect, anchor, options) {
  var _options$labelHeight, _options$gap;
  var labelHeight = (_options$labelHeight = options === null || options === void 0 ? void 0 : options.labelHeight) !== null && _options$labelHeight !== void 0 ? _options$labelHeight : EDGE_PORT_LABEL_HEIGHT;
  var gap = (_options$gap = options === null || options === void 0 ? void 0 : options.gap) !== null && _options$gap !== void 0 ? _options$gap : EDGE_PORT_LABEL_OFFSET_GAP;
  var base = getAnchorPoint(rect, anchor);
  var v = sideVector(anchor);
  var offset = labelHeight / 2 + gap;
  return {
    x: base.x + v.dx * offset,
    y: base.y + v.dy * offset
  };
}

/** 折线中段连接类型标签相对路径的默认外偏移（px） */
export var EDGE_CENTER_LABEL_OFFSET = 8;
/**
 * 在路径中找到最长的水平段（仅 y1===y2 的段），返回 [pIndex, length]。
 * 若无水平段返回 null。
 */
function findLongestHorizontalSegment(points) {
  var bestIdx = -1;
  var bestLen = 0;
  for (var i = 0; i < points.length - 1; i += 1) {
    var _points$i = _slicedToArray(points[i], 2),
      x1 = _points$i[0],
      y1 = _points$i[1];
    var _points = _slicedToArray(points[i + 1], 2),
      x2 = _points[0],
      y2 = _points[1];
    if (y1 === y2 && x1 !== x2) {
      var len = Math.abs(x2 - x1);
      if (len > bestLen) {
        bestLen = len;
        bestIdx = i;
      }
    }
  }
  return bestIdx >= 0 ? {
    index: bestIdx,
    length: bestLen
  } : null;
}

/** 与 EdgeLayer PortLabel 估算宽度一致 */
export function estimateEdgeLabelWidth(text) {
  var padX = 6;
  return Math.max(32, text.length * 6.5 + padX * 2);
}

/**
 * 在折线指定段上计算标签中心（水平段：标签在线上方；竖直段：标签在线左侧）。
 */
export function getEdgeCenterLabelPositionOnSegment(points, segIndex, options) {
  var _options$offsetPx, _options$perpOffsetPx, _options$along;
  if (points.length < 2 || segIndex < 0 || segIndex >= points.length - 1) {
    return null;
  }
  var offset = (_options$offsetPx = options === null || options === void 0 ? void 0 : options.offsetPx) !== null && _options$offsetPx !== void 0 ? _options$offsetPx : EDGE_CENTER_LABEL_OFFSET;
  var perp = (_options$perpOffsetPx = options === null || options === void 0 ? void 0 : options.perpOffsetPx) !== null && _options$perpOffsetPx !== void 0 ? _options$perpOffsetPx : 0;
  var _points$segIndex = _slicedToArray(points[segIndex], 2),
    x1 = _points$segIndex[0],
    y1 = _points$segIndex[1];
  var _points2 = _slicedToArray(points[segIndex + 1], 2),
    x2 = _points2[0],
    y2 = _points2[1];
  var along = (_options$along = options === null || options === void 0 ? void 0 : options.along) !== null && _options$along !== void 0 ? _options$along : 0.5;
  var x = x1 + (x2 - x1) * along;
  var y = y1 + (y2 - y1) * along;
  var isHorizontal = y1 === y2 && x1 !== x2;
  if (isHorizontal) {
    return {
      x: x,
      y: y - offset - perp
    };
  }
  var isVertical = x1 === x2 && y1 !== y2;
  if (isVertical) {
    return {
      x: x - offset - perp,
      y: y
    };
  }
  return {
    x: x,
    y: y - offset - perp
  };
}

/**
 * 计算边 `label`（如 connection_type）在正交折线上的显示位置。
 * 默认 `target-tail`：靠近路径末端（目标端），避免标在水平总线中段互相重叠。
 */
export function getEdgeCenterLabelPosition(points, options) {
  var _options$offsetPx2, _options$perpOffsetPx2, _options$placement, _options$segIndex, _options$along2;
  if (points.length < 2) return null;
  var offset = (_options$offsetPx2 = options === null || options === void 0 ? void 0 : options.offsetPx) !== null && _options$offsetPx2 !== void 0 ? _options$offsetPx2 : EDGE_CENTER_LABEL_OFFSET;
  var perp = (_options$perpOffsetPx2 = options === null || options === void 0 ? void 0 : options.perpOffsetPx) !== null && _options$perpOffsetPx2 !== void 0 ? _options$perpOffsetPx2 : 0;
  var placement = (_options$placement = options === null || options === void 0 ? void 0 : options.placement) !== null && _options$placement !== void 0 ? _options$placement : 'target-tail';
  if (placement === 'middle') {
    var i = Math.floor(points.length / 2);
    var _points$i2 = _slicedToArray(points[i], 2),
      x = _points$i2[0],
      y = _points$i2[1];
    return {
      x: x,
      y: y - offset - perp
    };
  }
  var segIndex = (_options$segIndex = options === null || options === void 0 ? void 0 : options.segIndex) !== null && _options$segIndex !== void 0 ? _options$segIndex : placement === 'source-tail' ? 0 : Math.max(0, points.length - 2);
  var along = (_options$along2 = options === null || options === void 0 ? void 0 : options.along) !== null && _options$along2 !== void 0 ? _options$along2 : placement === 'source-tail' ? 0.35 : 0.65;
  return getEdgeCenterLabelPositionOnSegment(points, segIndex, {
    along: along,
    offsetPx: offset,
    perpOffsetPx: perp
  });
}

/** 标签防碰撞：中心点最小间距（px，约等于 pill 高度 + 间距） */
export var EDGE_CENTER_LABEL_MIN_GAP = 22;

/** 拓扑水平总线段最短可用长度（px） */
var LABEL_HORIZONTAL_MIN_LEN = 20;
function clampAlong(value) {
  return Math.max(0.12, Math.min(0.9, value));
}

/** 取路径中最长水平段（拓扑总线段），供多边汇入同一目标时错开标签 */
function pickLongestHorizontalSegmentIndex(points) {
  var minLen = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : LABEL_HORIZONTAL_MIN_LEN;
  var bestIndex = null;
  var bestLen = 0;
  for (var i = 0; i < points.length - 1; i += 1) {
    var _points$i3 = _slicedToArray(points[i], 2),
      x1 = _points$i3[0],
      y1 = _points$i3[1];
    var _points3 = _slicedToArray(points[i + 1], 2),
      x2 = _points3[0],
      y2 = _points3[1];
    if (y1 !== y2 || x1 === x2) continue;
    var len = Math.abs(x2 - x1);
    if (len >= minLen && len > bestLen) {
      bestLen = len;
      bestIndex = i;
    }
  }
  return bestIndex;
}
function horizontalSegmentKey(points, segIndex) {
  var _points$segIndex2 = _slicedToArray(points[segIndex], 2),
    x1 = _points$segIndex2[0],
    y1 = _points$segIndex2[1];
  var _points4 = _slicedToArray(points[segIndex + 1], 2),
    x2 = _points4[0],
    y2 = _points4[1];
  var ya = Math.round(y1);
  var xa = Math.round(Math.min(x1, x2));
  var xb = Math.round(Math.max(x1, x2));
  return "h:".concat(ya, ":").concat(xa, "-").concat(xb);
}
function labelBoxesOverlap(a, b) {
  var margin = 4;
  return Math.abs(a.x - b.x) < (a.w + b.w) / 2 + margin && Math.abs(a.y - b.y) < (a.h + b.h) / 2 + margin;
}
function buildPlacementState(item, segIndex, along, perpOffsetPx, offsetPx) {
  var _item$label$trim, _item$label;
  var label = (_item$label$trim = (_item$label = item.label) === null || _item$label === void 0 ? void 0 : _item$label.trim()) !== null && _item$label$trim !== void 0 ? _item$label$trim : '';
  var pos = getEdgeCenterLabelPositionOnSegment(item.points, segIndex, {
    along: along,
    offsetPx: offsetPx,
    perpOffsetPx: perpOffsetPx
  });
  if (!pos) return null;
  return {
    edgeId: item.edgeId,
    targetId: item.targetId,
    label: label,
    points: item.points,
    segIndex: segIndex,
    along: along,
    perpOffsetPx: perpOffsetPx,
    x: pos.x,
    y: pos.y,
    w: estimateEdgeLabelWidth(label),
    h: EDGE_PORT_LABEL_HEIGHT
  };
}

/**
 * 多边汇入同一目标且仍重叠时：把标签竖排在「目标图元正左侧」（紧贴设备外轮廓），
 * pill 中心 x 严格落在设备左边框之外，避免压到设备身上；纵向居中对齐到设备中心。
 */
function placeLabelsBesideTarget(targetRect, group) {
  var sorted = _toConsumableArray(group).sort(function (a, b) {
    return a.edgeId.localeCompare(b.edgeId);
  });
  var out = new Map();
  var h = EDGE_PORT_LABEL_HEIGHT;
  var gap = 4;
  var maxW = sorted.reduce(function (acc, it) {
    var _it$label$trim, _it$label;
    return Math.max(acc, estimateEdgeLabelWidth((_it$label$trim = (_it$label = it.label) === null || _it$label === void 0 ? void 0 : _it$label.trim()) !== null && _it$label$trim !== void 0 ? _it$label$trim : ''));
  }, 32);
  var cx = targetRect.left - 6 - maxW / 2;
  var stackH = sorted.length * (h + gap) - gap;
  var startY = targetRect.top + targetRect.height / 2 - stackH / 2 + h / 2;
  for (var i = 0; i < sorted.length; i += 1) {
    out.set(sorted[i].edgeId, {
      x: cx,
      y: startY + i * (h + gap)
    });
  }
  return out;
}

/**
 * 为多条边的连接类型标签分配互不重叠的位置。
 * - 多边汇入同一目标：优先标在各自「水平总线」段中点（天然错开）
 * - 仍重叠时按 pill 宽高做 bbox 分离；必要时在目标图元左侧纵向堆叠
 */
export function resolveEdgeCenterLabelPositions(items, options) {
  var _options$placement2, _options$offsetPx3;
  var placement = (_options$placement2 = options === null || options === void 0 ? void 0 : options.placement) !== null && _options$placement2 !== void 0 ? _options$placement2 : 'target-tail';
  var offsetPx = (_options$offsetPx3 = options === null || options === void 0 ? void 0 : options.offsetPx) !== null && _options$offsetPx3 !== void 0 ? _options$offsetPx3 : EDGE_CENTER_LABEL_OFFSET;
  var result = new Map();
  var states = [];
  var labeled = items.filter(function (it) {
    var _it$label2;
    return (_it$label2 = it.label) === null || _it$label2 === void 0 ? void 0 : _it$label2.trim();
  });
  var byTarget = new Map();
  var _iterator = _createForOfIteratorHelper(labeled),
    _step;
  try {
    for (_iterator.s(); !(_step = _iterator.n()).done;) {
      var _byTarget$get;
      var it = _step.value;
      var list = (_byTarget$get = byTarget.get(it.targetId)) !== null && _byTarget$get !== void 0 ? _byTarget$get : [];
      list.push(it);
      byTarget.set(it.targetId, list);
    }
  } catch (err) {
    _iterator.e(err);
  } finally {
    _iterator.f();
  }
  var _iterator2 = _createForOfIteratorHelper(byTarget.values()),
    _step2;
  try {
    for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
      var _group = _step2.value;
      var sorted = _toConsumableArray(_group).sort(function (a, b) {
        return a.edgeId.localeCompare(b.edgeId);
      });
      /** 同一水平段上多条边：按 x 顺序分配 along */
      var alongBySegKey = new Map();
      for (var _i2 = 0; _i2 < sorted.length; _i2 += 1) {
        var item = sorted[_i2];
        var hSeg = pickLongestHorizontalSegmentIndex(item.points);
        var segIndex = hSeg !== null && hSeg !== void 0 ? hSeg : placement === 'source-tail' ? 0 : Math.max(0, item.points.length - 2);
        var along = hSeg != null ? 0.5 : placement === 'source-tail' ? 0.35 : 0.68;
        if (hSeg != null) {
          var _alongBySegKey$get;
          var key = horizontalSegmentKey(item.points, hSeg);
          var count = (_alongBySegKey$get = alongBySegKey.get(key)) !== null && _alongBySegKey$get !== void 0 ? _alongBySegKey$get : 0;
          alongBySegKey.set(key, count + 1);
          if (count > 0) {
            var _item$points$hSeg = _slicedToArray(item.points[hSeg], 1),
              x1 = _item$points$hSeg[0];
            var _item$points = _slicedToArray(item.points[hSeg + 1], 1),
              x2 = _item$points[0];
            var segLen = Math.abs(x2 - x1);
            var step = Math.min(0.2, (EDGE_PORT_LABEL_HEIGHT + 6) / Math.max(segLen, 40));
            along = clampAlong(0.5 + (count - 0.5) * step);
          }
        } else if (sorted.length > 1) {
          var _segLen = Math.hypot(item.points[segIndex + 1][0] - item.points[segIndex][0], item.points[segIndex + 1][1] - item.points[segIndex][1]);
          var _step5 = Math.min(0.18, (EDGE_PORT_LABEL_HEIGHT + 8) / Math.max(_segLen, 36));
          along = clampAlong((placement === 'source-tail' ? 0.35 : 0.68) + (_i2 - (sorted.length - 1) / 2) * _step5);
        }
        var perpLane = Math.floor((_i2 + 1) / 2) * 16;
        var perpOffsetPx = sorted.length > 1 ? (_i2 % 2 === 0 ? -1 : 1) * perpLane : 0;
        var _state = buildPlacementState(item, segIndex, along, perpOffsetPx, offsetPx);
        if (_state) states.push(_state);
      }
    }

    /** 全局 bbox 分离（按 pill 宽高，而非中心点距离） */
  } catch (err) {
    _iterator2.e(err);
  } finally {
    _iterator2.f();
  }
  for (var iter = 0; iter < 12; iter += 1) {
    var adjusted = false;
    for (var i = 0; i < states.length; i += 1) {
      for (var j = i + 1; j < states.length; j += 1) {
        var a = states[i];
        var b = states[j];
        if (!labelBoxesOverlap(a, b)) continue;
        var mover = states[j];
        mover.along = clampAlong(mover.along + 0.08);
        mover.perpOffsetPx += (j % 2 === 0 ? -1 : 1) * 12;
        var pos = getEdgeCenterLabelPositionOnSegment(mover.points, mover.segIndex, {
          along: mover.along,
          offsetPx: offsetPx,
          perpOffsetPx: mover.perpOffsetPx
        });
        if (!pos) continue;
        mover.x = pos.x;
        mover.y = pos.y;
        adjusted = true;
      }
    }
    if (!adjusted) break;
  }
  for (var _i = 0, _states = states; _i < _states.length; _i++) {
    var state = _states[_i];
    result.set(state.edgeId, {
      x: state.x,
      y: state.y
    });
  }

  /**
   * 多边汇入同一目标（≥3）：直接改用「目标上方竖排」方案。
   * 经验上 ≥3 条线时即使 channelOffset 错开了线路 y，标签 pill 仍会有视觉重叠，
   * 不如规整堆在设备正上方，与连线对齐到同一根末段竖线，更易读。
   */
  if (options !== null && options !== void 0 && options.rectsById) {
    var _iterator3 = _createForOfIteratorHelper(byTarget.values()),
      _step3;
    try {
      for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
        var group = _step3.value;
        if (group.length < 3) continue;
        var rect = options.rectsById.get(group[0].targetId);
        if (!rect) continue;
        var stacked = placeLabelsBesideTarget(rect, group);
        var _iterator4 = _createForOfIteratorHelper(stacked),
          _step4;
        try {
          for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
            var _step4$value = _slicedToArray(_step4.value, 2),
              id = _step4$value[0],
              _pos = _step4$value[1];
            result.set(id, _pos);
          }
        } catch (err) {
          _iterator4.e(err);
        } finally {
          _iterator4.f();
        }
      }
    } catch (err) {
      _iterator3.e(err);
    } finally {
      _iterator3.f();
    }
  }
  return result;
}

/**
 * 未指定锚点时，选朝向对方最近的一侧
 */
export function pickAutoAnchor(from, to) {
  var a = rectCenter(from);
  var b = rectCenter(to);
  var dx = b.x - a.x;
  var dy = b.y - a.y;
  if (Math.abs(dx) >= Math.abs(dy)) {
    return dx >= 0 ? 'right' : 'left';
  }
  return dy >= 0 ? 'bottom' : 'top';
}

/** 拉线吸附端口时的命中半径（与 {@link EDGE_PORT_MARKER_OUTSET} 同量级） */
export var EDGE_PORT_SNAP_THRESHOLD = 16;
/**
 * 距矩形四边锚点最近的一侧
 */
export function pickNearestAnchorOnRect(pt, rect) {
  var best = 'top';
  var minDist = Infinity;
  var _iterator5 = _createForOfIteratorHelper(ANCHORS),
    _step6;
  try {
    for (_iterator5.s(); !(_step6 = _iterator5.n()).done;) {
      var anchor = _step6.value;
      var ap = getAnchorPoint(rect, anchor);
      var d = Math.hypot(pt.x - ap.x, pt.y - ap.y);
      if (d < minDist) {
        minDist = d;
        best = anchor;
      }
    }
  } catch (err) {
    _iterator5.e(err);
  } finally {
    _iterator5.f();
  }
  return best;
}

/**
 * 拉线过程中解析目标：优先吸附到端口圆点，否则落在图元内时取最近端口。
 */
export function resolveDrawingTargetAtPoint(pt, instanceIds, rectsById, excludeInstanceId) {
  var snapThreshold = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : EDGE_PORT_SNAP_THRESHOLD;
  var bestSnap = null;
  var bestSnapDist = snapThreshold;
  var _iterator6 = _createForOfIteratorHelper(instanceIds),
    _step7;
  try {
    for (_iterator6.s(); !(_step7 = _iterator6.n()).done;) {
      var id = _step7.value;
      if (id === excludeInstanceId) continue;
      var rect = rectsById.get(id);
      if (!rect) continue;
      var _iterator8 = _createForOfIteratorHelper(ANCHORS),
        _step9;
      try {
        for (_iterator8.s(); !(_step9 = _iterator8.n()).done;) {
          var anchor = _step9.value;
          var ap = getAnchorPoint(rect, anchor);
          var d = Math.hypot(pt.x - ap.x, pt.y - ap.y);
          if (d <= bestSnapDist) {
            bestSnapDist = d;
            bestSnap = {
              instanceId: id,
              anchor: anchor
            };
          }
        }
      } catch (err) {
        _iterator8.e(err);
      } finally {
        _iterator8.f();
      }
    }
  } catch (err) {
    _iterator6.e(err);
  } finally {
    _iterator6.f();
  }
  if (bestSnap) return bestSnap;
  var _iterator7 = _createForOfIteratorHelper(instanceIds),
    _step8;
  try {
    for (_iterator7.s(); !(_step8 = _iterator7.n()).done;) {
      var _id = _step8.value;
      if (_id === excludeInstanceId) continue;
      var _rect = rectsById.get(_id);
      if (!_rect) continue;
      var right = _rect.left + _rect.width;
      var bottom = _rect.top + _rect.height;
      if (pt.x >= _rect.left && pt.x <= right && pt.y >= _rect.top && pt.y <= bottom) {
        return {
          instanceId: _id,
          anchor: pickNearestAnchorOnRect(pt, _rect)
        };
      }
    }
  } catch (err) {
    _iterator7.e(err);
  } finally {
    _iterator7.f();
  }
  return null;
}
function sideVector(anchor) {
  switch (anchor) {
    case 'top':
      return {
        dx: 0,
        dy: -1
      };
    case 'bottom':
      return {
        dx: 0,
        dy: 1
      };
    case 'left':
      return {
        dx: -1,
        dy: 0
      };
    case 'right':
    default:
      return {
        dx: 1,
        dy: 0
      };
  }
}

/** 线段是否与矩形内部区域相交（水平/竖直在边框上不算） */
function horizontalSegmentCrossesRect(y, x1, x2, rect) {
  var minX = Math.min(x1, x2);
  var maxX = Math.max(x1, x2);
  var bottom = rect.top + rect.height;
  var right = rect.left + rect.width;
  if (y <= rect.top || y >= bottom) return false;
  return maxX > rect.left && minX < right;
}
function verticalSegmentCrossesRect(x, y1, y2, rect) {
  var minY = Math.min(y1, y2);
  var maxY = Math.max(y1, y2);
  var bottom = rect.top + rect.height;
  var right = rect.left + rect.width;
  if (x <= rect.left || x >= right) return false;
  return maxY > rect.top && minY < bottom;
}
function channelYOutsideRect(rect, preferY, clearance) {
  var above = rect.top - clearance;
  var below = rect.top + rect.height + clearance;
  return Math.abs(preferY - above) <= Math.abs(preferY - below) ? above : below;
}
function channelXOutsideRect(rect, preferX, clearance) {
  var left = rect.left - clearance;
  var right = rect.left + rect.width + clearance;
  return Math.abs(preferX - left) <= Math.abs(preferX - right) ? left : right;
}
function pushRoutePoint(out, point) {
  var last = out[out.length - 1];
  if (!last || last[0] !== point[0] || last[1] !== point[1]) {
    out.push(point);
  }
}

/** 折线是否有线段穿过矩形内部 */
export function pathCrossesRectInterior(points, rect) {
  var bottom = rect.top + rect.height;
  var right = rect.left + rect.width;
  for (var i = 0; i < points.length - 1; i++) {
    var _points$i4 = _slicedToArray(points[i], 2),
      x1 = _points$i4[0],
      y1 = _points$i4[1];
    var _points5 = _slicedToArray(points[i + 1], 2),
      x2 = _points5[0],
      y2 = _points5[1];
    if (y1 === y2) {
      var minX = Math.min(x1, x2);
      var maxX = Math.max(x1, x2);
      if (y1 <= rect.top || y1 >= bottom) continue;
      if (maxX > rect.left && minX < right) return true;
    }
    if (x1 === x2) {
      var minY = Math.min(y1, y2);
      var maxY = Math.max(y1, y2);
      if (x1 <= rect.left || x1 >= right) continue;
      if (maxY > rect.top && minY < bottom) return true;
    }
  }
  return false;
}
function pathCrossesAnyRectInterior(points, rects) {
  return rects.some(function (rect) {
    return pathCrossesRectInterior(points, rect);
  });
}
function manhattanPathLength(points) {
  var sum = 0;
  for (var i = 0; i < points.length - 1; i++) {
    sum += Math.abs(points[i + 1][0] - points[i][0]) + Math.abs(points[i + 1][1] - points[i][1]);
  }
  return sum;
}

/** 去除连续共线冗余点，得到最简折线（每个内部点都是真正的拐角） */
function compressCollinearPoints(points) {
  if (points.length < 3) return points.map(function (p) {
    return _toConsumableArray(p);
  });
  var out = [points[0]];
  for (var i = 1; i < points.length - 1; i++) {
    var a = out[out.length - 1];
    var b = points[i];
    var c = points[i + 1];
    var collinearX = a[0] === b[0] && b[0] === c[0];
    var collinearY = a[1] === b[1] && b[1] === c[1];
    if (collinearX || collinearY) continue;
    out.push(b);
  }
  out.push(points[points.length - 1]);
  return out;
}

/**
 * 路径首末两段长度差（压缩冗余后衡量「Z 型两边对称度」），值越小越对称。
 */
function pathAsymmetry(points) {
  var compressed = compressCollinearPoints(points);
  if (compressed.length < 4) return 0;
  var segLen = function segLen(a, b) {
    return Math.abs(b[0] - a[0]) + Math.abs(b[1] - a[1]);
  };
  var first = segLen(compressed[0], compressed[1]);
  var last = segLen(compressed[compressed.length - 2], compressed[compressed.length - 1]);
  return Math.abs(first - last);
}

/** 拐角数（压缩共线冗余后的内部拐点数量） */
function pathTurnCount(points) {
  var compressed = compressCollinearPoints(points);
  return Math.max(0, compressed.length - 2);
}

/** 端口沿外法线走到图元外侧 clearance 处 */
function outwardClearancePoint(pt, side, rect, clearance) {
  var bottom = rect.top + rect.height;
  var right = rect.left + rect.width;
  switch (side) {
    case 'right':
      return [right + clearance, pt.y];
    case 'left':
      return [rect.left - clearance, pt.y];
    case 'top':
      return [pt.x, rect.top - clearance];
    case 'bottom':
      return [pt.x, bottom + clearance];
    default:
      return [pt.x, pt.y];
  }
}

/** 校验正交：连续两点必须共享 x 或 y */
function isOrthogonalPath(points) {
  for (var i = 0; i < points.length - 1; i++) {
    if (points[i][0] !== points[i + 1][0] && points[i][1] !== points[i + 1][1]) {
      return false;
    }
  }
  return true;
}

/**
 * 端口出口 stub 不能反向：从源 sourcePt 到下一拐点必须沿外法线方向；目标同理。
 */
function exitsAlignWithSides(sourcePt, sourceSide, targetPt, targetSide, pts) {
  if (pts.length < 2) return true;
  var sv = sideVector(sourceSide);
  var sNext = pts[1];
  var sDx = sNext[0] - sourcePt.x;
  var sDy = sNext[1] - sourcePt.y;
  if (sv.dx !== 0) {
    if (Math.sign(sDx) !== sv.dx) return false;
    if (sDy !== 0) return false;
  } else {
    if (Math.sign(sDy) !== sv.dy) return false;
    if (sDx !== 0) return false;
  }
  var tv = sideVector(targetSide);
  var tPrev = pts[pts.length - 2];
  var tDx = targetPt.x - tPrev[0];
  var tDy = targetPt.y - tPrev[1];
  if (tv.dx !== 0) {
    if (Math.sign(tDx) !== -tv.dx) return false;
    if (tDy !== 0) return false;
  } else {
    if (Math.sign(tDy) !== -tv.dy) return false;
    if (tDx !== 0) return false;
  }
  return true;
}

/**
 * 候选正交路径：直 L、Z 型、外侧通道；端点已沿外法线出 clearance 距离。
 * 选「不穿过源/目标矩形 + 端口方向正确」且曼哈顿距离最短的一条。
 */
function buildOrthogonalPathAvoidingRects(sourcePt, sourceSide, targetPt, targetSide, sourceRect, targetRect, clearance) {
  var channelOffset = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 0;
  var rects = [sourceRect, targetRect];
  var sOut = outwardClearancePoint(sourcePt, sourceSide, sourceRect, clearance);
  var tOut = outwardClearancePoint(targetPt, targetSide, targetRect, clearance);

  /** 中段通道偏移：让共享通道的多条线错开，避免视觉重叠 */
  var channelGap = channelOffset;
  var outerGap = Math.abs(channelOffset);
  var aboveY = Math.min.apply(Math, _toConsumableArray(rects.map(function (r) {
    return r.top;
  }))) - clearance - outerGap;
  var belowY = Math.max.apply(Math, _toConsumableArray(rects.map(function (r) {
    return r.top + r.height;
  }))) + clearance + outerGap;
  var leftX = Math.min.apply(Math, _toConsumableArray(rects.map(function (r) {
    return r.left;
  }))) - clearance - outerGap;
  var rightX = Math.max.apply(Math, _toConsumableArray(rects.map(function (r) {
    return r.left + r.width;
  }))) + clearance + outerGap;

  /** s_out → t_out 之间的拐点序列；不含端点本身 */
  var connectors = [];

  /** 直连：先横后竖 / 先竖后横 */
  connectors.push([[tOut[0], sOut[1]]]);
  connectors.push([[sOut[0], tOut[1]]]);

  /** Z 型中点（共享中段加偏移以错开多条线） */
  var midX = (sOut[0] + tOut[0]) / 2 + channelGap;
  var midY = (sOut[1] + tOut[1]) / 2 + channelGap;
  connectors.push([[midX, sOut[1]], [midX, tOut[1]]]);
  connectors.push([[sOut[0], midY], [tOut[0], midY]]);

  /** 外侧公共通道（上/下/左/右），用于源、目标位于直连冲突区时绕过去 */
  for (var _i3 = 0, _arr = [aboveY, belowY]; _i3 < _arr.length; _i3++) {
    var channelY = _arr[_i3];
    connectors.push([[sOut[0], channelY], [tOut[0], channelY]]);
  }
  for (var _i4 = 0, _arr2 = [leftX, rightX]; _i4 < _arr2.length; _i4++) {
    var channelX = _arr2[_i4];
    connectors.push([[channelX, sOut[1]], [channelX, tOut[1]]]);
  }
  var bestPath = null;
  var bestLen = Infinity;
  var bestTurns = Infinity;
  var bestAsym = Infinity;
  for (var _i5 = 0, _connectors = connectors; _i5 < _connectors.length; _i5++) {
    var inner = _connectors[_i5];
    var trial = [];
    pushRoutePoint(trial, [sourcePt.x, sourcePt.y]);
    pushRoutePoint(trial, sOut);
    var _iterator9 = _createForOfIteratorHelper(inner),
      _step10;
    try {
      for (_iterator9.s(); !(_step10 = _iterator9.n()).done;) {
        var p = _step10.value;
        pushRoutePoint(trial, p);
      }
    } catch (err) {
      _iterator9.e(err);
    } finally {
      _iterator9.f();
    }
    pushRoutePoint(trial, tOut);
    pushRoutePoint(trial, [targetPt.x, targetPt.y]);
    if (!isOrthogonalPath(trial)) continue;
    if (!exitsAlignWithSides(sourcePt, sourceSide, targetPt, targetSide, trial)) {
      continue;
    }
    if (pathCrossesAnyRectInterior(trial, rects)) continue;
    var len = manhattanPathLength(trial);
    var turns = pathTurnCount(trial);
    var asym = pathAsymmetry(trial);
    /** 字典序：曼哈顿 → 拐角数 → 对称度 */
    var better = len < bestLen || len === bestLen && turns < bestTurns || len === bestLen && turns === bestTurns && asym < bestAsym;
    if (better) {
      bestLen = len;
      bestTurns = turns;
      bestAsym = asym;
      bestPath = trial;
    }
  }
  if (bestPath) return bestPath;

  /** 兜底：沿外侧上方通道兜一圈（极端情况下两个矩形重叠/相邻仍能输出一条路径） */
  return [[sourcePt.x, sourcePt.y], sOut, [sOut[0], aboveY], [tOut[0], aboveY], tOut, [targetPt.x, targetPt.y]];
}

/**
 * 最短正交折线：从端口锚点直接转折，无固定长度 stub。
 */
function buildShortestOrthogonalPath(sourcePt, sourceSide, targetPt, targetSide) {
  var sourceExitsVertical = sideVector(sourceSide).dx === 0;
  var sameHoriz = (sourceSide === 'left' || sourceSide === 'right') && (targetSide === 'left' || targetSide === 'right');
  var sameVert = (sourceSide === 'top' || sourceSide === 'bottom') && (targetSide === 'top' || targetSide === 'bottom');
  var out = [];
  if (sameHoriz) {
    var midX = (sourcePt.x + targetPt.x) / 2;
    pushRoutePoint(out, [sourcePt.x, sourcePt.y]);
    pushRoutePoint(out, [midX, sourcePt.y]);
    pushRoutePoint(out, [midX, targetPt.y]);
    pushRoutePoint(out, [targetPt.x, targetPt.y]);
    return out;
  }
  if (sameVert) {
    var midY = (sourcePt.y + targetPt.y) / 2;
    pushRoutePoint(out, [sourcePt.x, sourcePt.y]);
    pushRoutePoint(out, [sourcePt.x, midY]);
    pushRoutePoint(out, [targetPt.x, midY]);
    pushRoutePoint(out, [targetPt.x, targetPt.y]);
    return out;
  }
  var pathA = sourceExitsVertical ? [[sourcePt.x, sourcePt.y], [targetPt.x, sourcePt.y], [targetPt.x, targetPt.y]] : [[sourcePt.x, sourcePt.y], [targetPt.x, sourcePt.y], [targetPt.x, targetPt.y]];
  var pathB = sourceExitsVertical ? [[sourcePt.x, sourcePt.y], [sourcePt.x, targetPt.y], [targetPt.x, targetPt.y]] : [[sourcePt.x, sourcePt.y], [sourcePt.x, targetPt.y], [targetPt.x, targetPt.y]];
  var len = function len(pts) {
    var sum = 0;
    for (var i = 0; i < pts.length - 1; i++) {
      sum += Math.abs(pts[i + 1][0] - pts[i][0]) + Math.abs(pts[i + 1][1] - pts[i][1]);
    }
    return sum;
  };
  var chosen = len(pathA) <= len(pathB) ? pathA : pathB;
  var _iterator10 = _createForOfIteratorHelper(chosen),
    _step11;
  try {
    for (_iterator10.s(); !(_step11 = _iterator10.n()).done;) {
      var p = _step11.value;
      pushRoutePoint(out, p);
    }
  } catch (err) {
    _iterator10.e(err);
  } finally {
    _iterator10.f();
  }
  return out;
}

/**
 * 单段穿框时插入最小 U 型绕行（多一折：先垂/平到通道，再平/垂接到对端）。
 */
function buildWrapAroundPoints(a, b, rect, clearance) {
  if (a[1] === b[1]) {
    if (!horizontalSegmentCrossesRect(a[1], a[0], b[0], rect)) {
      return [];
    }
    var bypassY = channelYOutsideRect(rect, a[1], clearance);
    return [[a[0], bypassY], [b[0], bypassY]];
  }
  if (a[0] === b[0]) {
    if (!verticalSegmentCrossesRect(a[0], a[1], b[1], rect)) {
      return [];
    }
    var bypassX = channelXOutsideRect(rect, a[0], clearance);
    return [[bypassX, a[1]], [bypassX, b[1]]];
  }
  return [];
}

/**
 * 在「最短路径」基础上，对穿过障碍矩形的线段做包围式绕行（仅跳过离开源锚点的第一段）。
 */
function wrapPathAroundRects(points, obstacleRects, clearance) {
  if (points.length < 3 || obstacleRects.length === 0) {
    return points;
  }
  var wrapped = [];
  for (var i = 0; i < points.length - 1; i++) {
    var a = points[i];
    var b = points[i + 1];
    pushRoutePoint(wrapped, a);
    var isLeavingSourceAnchor = i === 0;
    if (!isLeavingSourceAnchor) {
      var _iterator11 = _createForOfIteratorHelper(obstacleRects),
        _step12;
      try {
        for (_iterator11.s(); !(_step12 = _iterator11.n()).done;) {
          var rect = _step12.value;
          var detour = buildWrapAroundPoints(a, b, rect, clearance);
          if (detour.length === 0) {
            continue;
          }
          var _iterator12 = _createForOfIteratorHelper(detour),
            _step13;
          try {
            for (_iterator12.s(); !(_step13 = _iterator12.n()).done;) {
              var p = _step13.value;
              pushRoutePoint(wrapped, p);
            }
          } catch (err) {
            _iterator12.e(err);
          } finally {
            _iterator12.f();
          }
          break;
        }
      } catch (err) {
        _iterator11.e(err);
      } finally {
        _iterator11.f();
      }
    }
    pushRoutePoint(wrapped, b);
  }
  return wrapped;
}
/**
 * 正交连线：先最短路径，再对穿框段绕一圈。
 */
export function routeOrthogonal(sourcePt, sourceSide, targetPt, targetSide, /** @deprecated 已取消固定 stub；可传 number（忽略）或 RouteOrthogonalOptions */
stubOrOptions, maybeOptions) {
  var _options$clearance, _options$obstacleRect2;
  var options = _typeof(stubOrOptions) === 'object' && stubOrOptions != null ? stubOrOptions : maybeOptions;
  var clearance = (_options$clearance = options === null || options === void 0 ? void 0 : options.clearance) !== null && _options$clearance !== void 0 ? _options$clearance : EDGE_ROUTE_RECT_CLEARANCE;
  var sourceRect = options === null || options === void 0 ? void 0 : options.sourceRect;
  var targetRect = options === null || options === void 0 ? void 0 : options.targetRect;
  void stubOrOptions;
  if (sourceRect && targetRect) {
    var _options$channelOffse, _options$obstacleRect;
    var path = buildOrthogonalPathAvoidingRects(sourcePt, sourceSide, targetPt, targetSide, sourceRect, targetRect, clearance, (_options$channelOffse = options === null || options === void 0 ? void 0 : options.channelOffset) !== null && _options$channelOffse !== void 0 ? _options$channelOffse : 0);
    var extra = ((_options$obstacleRect = options === null || options === void 0 ? void 0 : options.obstacleRects) !== null && _options$obstacleRect !== void 0 ? _options$obstacleRect : []).filter(function (r) {
      return r !== sourceRect && r !== targetRect;
    });
    if (extra.length > 0) {
      path = wrapPathAroundRects(path, extra, clearance);
    }
    return path;
  }
  var shortest = buildShortestOrthogonalPath(sourcePt, sourceSide, targetPt, targetSide);
  var obstacles = (_options$obstacleRect2 = options === null || options === void 0 ? void 0 : options.obstacleRects) !== null && _options$obstacleRect2 !== void 0 ? _options$obstacleRect2 : targetRect ? [targetRect] : sourceRect ? [sourceRect] : [];
  if (obstacles.length === 0) {
    return shortest;
  }
  return wrapPathAroundRects(shortest, obstacles, clearance);
}

/**
 * 折线转带圆角的 SVG path d
 */
export function pathFromPoints(points) {
  var radius = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 4;
  if (points.length < 2) return '';
  if (points.length === 2) {
    return "M ".concat(points[0][0], " ").concat(points[0][1], " L ").concat(points[1][0], " ").concat(points[1][1]);
  }
  var d = "M ".concat(points[0][0], " ").concat(points[0][1]);
  for (var i = 1; i < points.length - 1; i++) {
    var prev = points[i - 1];
    var curr = points[i];
    var next = points[i + 1];
    var r = Math.min(radius, Math.hypot(curr[0] - prev[0], curr[1] - prev[1]) / 2, Math.hypot(next[0] - curr[0], next[1] - curr[1]) / 2);
    var dx1 = curr[0] - prev[0];
    var dy1 = curr[1] - prev[1];
    var len1 = Math.hypot(dx1, dy1) || 1;
    var dx2 = next[0] - curr[0];
    var dy2 = next[1] - curr[1];
    var len2 = Math.hypot(dx2, dy2) || 1;
    var x1 = curr[0] - dx1 / len1 * r;
    var y1 = curr[1] - dy1 / len1 * r;
    var x2 = curr[0] + dx2 / len2 * r;
    var y2 = curr[1] + dy2 / len2 * r;
    d += " L ".concat(x1, " ").concat(y1, " Q ").concat(curr[0], " ").concat(curr[1], " ").concat(x2, " ").concat(y2);
  }
  var last = points[points.length - 1];
  d += " L ".concat(last[0], " ").concat(last[1]);
  return d;
}
/**
 * 把一条已存在的 path 快照（snapshot）的两端「平移对齐」到新的源/目标锚点位置，
 * 中间路径完全保留。用于「手动编辑过的连线」在图元被拖动后只更新最近两端。
 *
 * 规则：
 * - 首段（snapshot[0] → snapshot[1]）：
 *   - 若是水平段（y0 === y1）：新 snapshot[0] = srcPt；新 snapshot[1] = (原 snapshot[1].x, srcPt.y)
 *   - 若是竖直段（x0 === x1）：新 snapshot[0] = srcPt；新 snapshot[1] = (srcPt.x, 原 snapshot[1].y)
 *   - 若是斜段（理论上 snapshot 都是正交，但兜底）：仅平移 snapshot[0]，snapshot[1] 保持不变
 * - 末段（snapshot[n-2] → snapshot[n-1]）：对称处理
 * - 同时对 snapshot[2] / snapshot[n-3] 做"必要的轴向跟随"：snapshot[1] → snapshot[2] 段必须保持
 *   正交，因此 snapshot[2] 的相邻轴坐标需要跟随 snapshot[1] 更新（同理末端）。
 */
export function alignSnapshotToAnchors(snapshot, srcPt, tgtPt) {
  if (snapshot.length < 2) return snapshot;
  var result = snapshot.map(function (p) {
    return [p[0], p[1]];
  });
  /** 首段 */
  var _result$ = _slicedToArray(result[0], 2),
    x0 = _result$[0],
    y0 = _result$[1];
  var _result$2 = _slicedToArray(result[1], 2),
    x1 = _result$2[0],
    y1 = _result$2[1];
  result[0] = [srcPt.x, srcPt.y];
  if (y0 === y1 && x0 !== x1) {
    /** 首段水平 → 用新 y 替换 snapshot[1].y；snapshot[2] 的 x 与 snapshot[1].x 原本相等（正交垂直段），保持不变 */
    result[1] = [x1, srcPt.y];
  } else if (x0 === x1 && y0 !== y1) {
    result[1] = [srcPt.x, y1];
  } else {
    /** 首段退化为零长度：保留原 snapshot[1] 不变 */
    result[1] = [x1, y1];
  }
  /** 末段 */
  var n = result.length;
  if (n >= 4) {
    var _result = _slicedToArray(result[n - 2], 2),
      xnm2 = _result[0],
      ynm2 = _result[1];
    var _result2 = _slicedToArray(result[n - 1], 2),
      xnm1 = _result2[0],
      ynm1 = _result2[1];
    result[n - 1] = [tgtPt.x, tgtPt.y];
    if (ynm2 === ynm1 && xnm2 !== xnm1) {
      result[n - 2] = [xnm2, tgtPt.y];
    } else if (xnm2 === xnm1 && ynm2 !== ynm1) {
      result[n - 2] = [tgtPt.x, ynm2];
    } else {
      result[n - 2] = [xnm2, ynm2];
    }
  } else if (n === 3) {
    /**
     * L 型 3 点情况：result[1] 同时受首段和末段方向约束。
     * - 若首段水平 + 末段竖直 → result[1] = (tgtPt.x, srcPt.y)
     * - 若首段竖直 + 末段水平 → result[1] = (srcPt.x, tgtPt.y)
     * 否则保留原 result[1]。
     */
    var _snapshot$ = _slicedToArray(snapshot[0], 2),
      xA = _snapshot$[0],
      yA = _snapshot$[1];
    var _snapshot$2 = _slicedToArray(snapshot[1], 2),
      xB = _snapshot$2[0],
      yB = _snapshot$2[1];
    var _snapshot$3 = _slicedToArray(snapshot[2], 2),
      xC = _snapshot$3[0],
      yC = _snapshot$3[1];
    var firstHorizontal = yA === yB && xA !== xB;
    var firstVertical = xA === xB && yA !== yB;
    var lastHorizontal = yB === yC && xB !== xC;
    var lastVertical = xB === xC && yB !== yC;
    var rx = xB;
    var ry = yB;
    if (firstHorizontal) ry = srcPt.y;
    if (firstVertical) rx = srcPt.x;
    if (lastHorizontal) ry = tgtPt.y;
    if (lastVertical) rx = tgtPt.x;
    result[1] = [rx, ry];
    result[2] = [tgtPt.x, tgtPt.y];
  } else if (n === 2) {
    result[1] = [tgtPt.x, tgtPt.y];
  }
  return result;
}

/**
 * 将「中段坐标覆盖」应用到一条正交折线 path 上。
 *
 * 内部段（既非首段也非末段）按顺序对应 `midSegmentPositions[k]`。
 * - 水平段（y1 === y2）→ 把 result[i].y / result[i+1].y 都改成 v
 * - 竖直段（x1 === x2）→ 把 result[i].x / result[i+1].x 都改成 v
 *
 * 移动一个内部段只是平移它的主轴坐标，相邻段（必是另一个方向）会随之延长/缩短，
 * 不会破坏正交性。多余的 override 项被忽略，null/undefined 项跳过。
 */
/** 首/末段拖动后默认插入的 stub 长度（px）；过短时按段长 50% 自动缩小 */
export var DEFAULT_EDGE_STUB_LENGTH = 24;

/**
 * 在路径起点处插入 stub 转角：把首段变成「沿首段实际方向走 stub → 90° 转弯 →
 * 走到原 corner」。
 *
 * 注意：方向取自 *path 首段实际指向*（points[0]→points[1]），而非 sourceAnchor 字段——
 * 这样在 path 已经经过手动编辑、首段方向与锚点 outward 不一致时仍能正确插入。
 *
 * 路径点数 +2；新增 2 个内部段（stub 段 + 「被拖动段」）。被拖动段的初始另一轴
 * 坐标与原首段重合（视觉直线），后续由拖动调整。
 */
export function applySourceStub(points, stubLength, /** @deprecated 仅向后兼容，实际方向由 path 首段决定 */
sourceAnchor) {
  void sourceAnchor;
  if (!stubLength || stubLength <= 0 || points.length < 2) return points;
  var _points$ = _slicedToArray(points[0], 2),
    x0 = _points$[0],
    y0 = _points$[1];
  var _points$2 = _slicedToArray(points[1], 2),
    x1 = _points$2[0],
    y1 = _points$2[1];
  var isHorizontal = y0 === y1 && x0 !== x1;
  var isVertical = x0 === x1 && y0 !== y1;
  if (!isHorizontal && !isVertical) return points;
  var firstLen = isHorizontal ? Math.abs(x1 - x0) : Math.abs(y1 - y0);
  if (firstLen <= 1) return points;
  var stub = Math.min(stubLength, firstLen / 2);
  if (stub <= 0) return points;
  if (isHorizontal) {
    var _sgn = Math.sign(x1 - x0) || 1;
    var sx = x0 + _sgn * stub;
    var _stubEnd = [sx, y0];
    var _midCorner = [sx, y1];
    var _replacedP = [x1, y1];
    return [points[0], _stubEnd, _midCorner, _replacedP].concat(_toConsumableArray(points.slice(2)));
  }
  var sgn = Math.sign(y1 - y0) || 1;
  var sy = y0 + sgn * stub;
  var stubEnd = [x0, sy];
  var midCorner = [x1, sy];
  var replacedP1 = [x1, y1];
  return [points[0], stubEnd, midCorner, replacedP1].concat(_toConsumableArray(points.slice(2)));
}

/**
 * 对称地在路径末端插入 stub 转角。方向取自 *path 末段实际指向*。
 *
 * 路径点数 +2；新增 2 个内部段（「被拖动段」 + stub 段）。
 */
export function applyTargetStub(points, stubLength, /** @deprecated 仅向后兼容，实际方向由 path 末段决定 */
targetAnchor) {
  void targetAnchor;
  if (!stubLength || stubLength <= 0 || points.length < 2) return points;
  var n = points.length;
  var _points6 = _slicedToArray(points[n - 2], 2),
    xA = _points6[0],
    yA = _points6[1];
  var _points7 = _slicedToArray(points[n - 1], 2),
    xT = _points7[0],
    yT = _points7[1];
  var isHorizontal = yA === yT && xA !== xT;
  var isVertical = xA === xT && yA !== yT;
  if (!isHorizontal && !isVertical) return points;
  var lastLen = isHorizontal ? Math.abs(xT - xA) : Math.abs(yT - yA);
  if (lastLen <= 1) return points;
  var stub = Math.min(stubLength, lastLen / 2);
  if (stub <= 0) return points;
  if (isHorizontal) {
    /** stubStart 沿「末段实际指向」反向回退 stub：= tgt - direction * stub */
    var _sgn2 = Math.sign(xT - xA) || 1;
    var sx = xT - _sgn2 * stub;
    var _aMod = [xA, yA];
    var _midCorner2 = [sx, yA];
    var _stubStart = [sx, yT];
    return [].concat(_toConsumableArray(points.slice(0, n - 2)), [_aMod, _midCorner2, _stubStart, points[n - 1]]);
  }
  var sgn = Math.sign(yT - yA) || 1;
  var sy = yT - sgn * stub;
  var aMod = [xA, yA];
  var midCorner = [xA, sy];
  var stubStart = [xT, sy];
  return [].concat(_toConsumableArray(points.slice(0, n - 2)), [aMod, midCorner, stubStart, points[n - 1]]);
}
export function applyMidSegmentOverrides(points, overrides) {
  if (!overrides || overrides.length === 0 || points.length < 4) {
    return points;
  }
  var result = points.map(function (p) {
    return [p[0], p[1]];
  });
  var innerCount = result.length - 3;
  var max = Math.min(innerCount, overrides.length);
  for (var k = 0; k < max; k++) {
    var v = overrides[k];
    if (v == null) continue;
    var i = k + 1;
    var _result$i = _slicedToArray(result[i], 2),
      x1 = _result$i[0],
      y1 = _result$i[1];
    var _result3 = _slicedToArray(result[i + 1], 2),
      x2 = _result3[0],
      y2 = _result3[1];
    if (y1 === y2 && x1 !== x2) {
      result[i] = [x1, v];
      result[i + 1] = [x2, v];
    } else if (x1 === x2 && y1 !== y2) {
      result[i] = [v, y1];
      result[i + 1] = [v, y2];
    }
  }
  return result;
}

/**
 * 返回正交折线中所有「内部段」的元信息（不含首段与末段），
 * 供 UI 渲染中段手柄使用。
 */

export function getEdgeInnerSegments(points) {
  var result = [];
  if (points.length < 4) return result;
  for (var i = 1; i <= points.length - 3; i++) {
    var _points$i5 = _slicedToArray(points[i], 2),
      x1 = _points$i5[0],
      y1 = _points$i5[1];
    var _points8 = _slicedToArray(points[i + 1], 2),
      x2 = _points8[0],
      y2 = _points8[1];
    if (y1 === y2 && x1 !== x2) {
      result.push({
        index: i - 1,
        pointIndex: i,
        axis: 'h',
        position: y1,
        midX: (x1 + x2) / 2,
        midY: y1,
        length: Math.abs(x2 - x1)
      });
    } else if (x1 === x2 && y1 !== y2) {
      result.push({
        index: i - 1,
        pointIndex: i,
        axis: 'v',
        position: x1,
        midX: x1,
        midY: (y1 + y2) / 2,
        length: Math.abs(y2 - y1)
      });
    }
  }
  return result;
}
export function buildEdgeRoute(edge, rectsById, options) {
  var _edge$sourceAnchor, _edge$targetAnchor, _options$routingOverr, _options$channelOffse2;
  var srcRect = rectsById.get(edge.sourceId);
  var tgtRect = rectsById.get(edge.targetId);
  if (!srcRect || !tgtRect) return null;
  var sourceAnchor = (_edge$sourceAnchor = edge.sourceAnchor) !== null && _edge$sourceAnchor !== void 0 ? _edge$sourceAnchor : pickAutoAnchor(srcRect, tgtRect);
  var targetAnchor = (_edge$targetAnchor = edge.targetAnchor) !== null && _edge$targetAnchor !== void 0 ? _edge$targetAnchor : pickAutoAnchor(tgtRect, srcRect);
  var sourcePt = getAnchorPoint(srcRect, sourceAnchor);
  var targetPt = getAnchorPoint(tgtRect, targetAnchor);
  var routing = (_options$routingOverr = options === null || options === void 0 ? void 0 : options.routingOverride) !== null && _options$routingOverr !== void 0 ? _options$routingOverr : edge.routing;
  /** 手动编辑过：snapshot 优先，仅对齐两端 */
  if (routing !== null && routing !== void 0 && routing.pathSnapshot && routing.pathSnapshot.length >= 2) {
    var aligned = alignSnapshotToAnchors(routing.pathSnapshot, sourcePt, targetPt);
    var _points9 = simplifyCollinearPath(aligned);
    return {
      edgeId: edge.id,
      points: _points9,
      pathD: pathFromPoints(_points9)
    };
  }
  var rawPoints = routeOrthogonal(sourcePt, sourceAnchor, targetPt, targetAnchor, {
    sourceRect: srcRect,
    targetRect: tgtRect,
    channelOffset: (_options$channelOffse2 = options === null || options === void 0 ? void 0 : options.channelOffset) !== null && _options$channelOffse2 !== void 0 ? _options$channelOffse2 : 0
  });
  var points = simplifyCollinearPath(rawPoints);
  return {
    edgeId: edge.id,
    points: points,
    pathD: pathFromPoints(points)
  };
}

/**
 * 去掉路径中"三点共线"的冗余中间点（比如 routeOrthogonal 里 srcPt → sOut →
 * 中段折点都在同一 y 上时，sOut 是冗余的）。
 *
 * 保留零长度段：用户拖动 first/last 段创建 stub 时，stubEnd 与 midCorner 短暂
 * 重合（零长度），不能被合并掉，否则会立刻丢失新插入的转角结构。
 */
export function simplifyCollinearPath(points) {
  if (points.length < 3) return points.map(function (p) {
    return [p[0], p[1]];
  });
  var out = [[points[0][0], points[0][1]]];
  for (var i = 1; i < points.length - 1; i++) {
    var prev = out[out.length - 1];
    var curr = points[i];
    var next = points[i + 1];
    /** 零长度段（prev===curr 或 curr===next）一律保留：用户主动创建的结构 */
    var zeroBefore = prev[0] === curr[0] && prev[1] === curr[1];
    var zeroAfter = curr[0] === next[0] && curr[1] === next[1];
    if (zeroBefore || zeroAfter) {
      out.push([curr[0], curr[1]]);
      continue;
    }
    var collinearH = prev[1] === curr[1] && curr[1] === next[1];
    var collinearV = prev[0] === curr[0] && curr[0] === next[0];
    if (collinearH || collinearV) continue;
    out.push([curr[0], curr[1]]);
  }
  var last = points[points.length - 1];
  out.push([last[0], last[1]]);
  return out;
}

/**
 * 按 edge 顺序分配交替偏移，让落到同一通道的多条线错开。
 * 序列：0, +step, -step, +2*step, -2*step, ...
 */
export function getEdgeChannelOffset(index) {
  var step = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 12;
  if (index <= 0) return 0;
  var k = Math.ceil(index / 2);
  return index % 2 === 1 ? k * step : -k * step;
}

/** 命中检测：点到折线最近距离 */
export function distanceToPolyline(px, py, points) {
  var min = Infinity;
  for (var i = 0; i < points.length - 1; i++) {
    var _points$i6 = _slicedToArray(points[i], 2),
      x1 = _points$i6[0],
      y1 = _points$i6[1];
    var _points10 = _slicedToArray(points[i + 1], 2),
      x2 = _points10[0],
      y2 = _points10[1];
    var dx = x2 - x1;
    var dy = y2 - y1;
    var lenSq = dx * dx + dy * dy;
    var t = 0;
    if (lenSq > 0) t = Math.max(0, Math.min(1, ((px - x1) * dx + (py - y1) * dy) / lenSq));
    var nx = x1 + t * dx;
    var ny = y1 + t * dy;
    min = Math.min(min, Math.hypot(px - nx, py - ny));
  }
  return min;
}
export function hitTestEdgeAtPoint(px, py, routes) {
  var threshold = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 8;
  var _iterator13 = _createForOfIteratorHelper(routes),
    _step14;
  try {
    for (_iterator13.s(); !(_step14 = _iterator13.n()).done;) {
      var route = _step14.value;
      if (distanceToPolyline(px, py, route.points) <= threshold) {
        return route.edgeId;
      }
    }
  } catch (err) {
    _iterator13.e(err);
  } finally {
    _iterator13.f();
  }
  return null;
}
export { ANCHORS as FLOOR_MAP_EDGE_ANCHORS };