function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
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 _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; }
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 _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
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 _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 _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 _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 _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; }
/**
 * 拓扑图：基于 GET /tenant/device 返回的设备数组，构造 PisellFloorMapLayout 所需的
 * `FloorMapViewConfig` + `dataSources`，无连线、按 `type_code` 动态生成 elementKinds、
 * 用近似正方形网格自动布局。**不依赖 shop floor-plan**。
 */

import { TOPOLOGY_DEVICES_DATA_SOURCE_KEY, TOPOLOGY_MAIN_CANVAS_ID } from "./topologyFloorPlanConstants";

/** 接口返回里单台设备（局部字段，仅用本视图实际用到的） */

/** 自动布局格点尺寸（内容坐标 px）；与 TopologyDeviceCard 紧凑布局对齐 */
var NODE_WIDTH = 132;
var NODE_HEIGHT = 72;
/** 同层水平间距：留得宽，避免多条连线挤在节点之间、标签互相重叠 */
var NODE_GAP = 56;
/** 同 cluster 内层与层之间的垂直距离（略大于节点高，给总线留空即可） */
var LAYER_GAP_Y = 56;
/** cluster 与 cluster（包括孤立点网格）之间的间距 */
var CLUSTER_GAP = 56;
/** 单行排布最大宽度，超过就换行（单位 px，按内容坐标） */
var CLUSTER_ROW_MAX_WIDTH = 1400;
var PADDING = 40;

/** 拓扑设备图元 kind 前缀，避免与平面图通用 kind 冲突 */
export var TOPOLOGY_DEVICE_KIND_PREFIX = 'topologyDevice:';
var DEVICE_CATEGORY_ID = 'devices';

/**
 * 静态 `type_code` 枚举：拓扑图所有可识别的设备类型。
 * 接口返回里若出现未列入的 `type_code`，会落到 `<prefix>unknown` 兜底 kind。
 */
export var TOPOLOGY_DEVICE_TYPE_CODES = ['pos', 'receipt_printer', 'label_printer', 'switch', 'info_display', 'terminal', 'chenlei', 'test1'];
/** type_code → 默认中文 label（label 主要用于 elementKinds palette / 调试，非业务展示） */
var TYPE_CODE_LABEL = {
  pos: 'POS',
  receipt_printer: '小票打印机',
  label_printer: '标签打印机',
  switch: '交换机',
  info_display: '显示屏',
  terminal: '终端',
  chenlei: 'chenlei',
  test1: 'test1'
};
function typeCodeToKindValue(code) {
  return "".concat(TOPOLOGY_DEVICE_KIND_PREFIX).concat(code);
}

/** 拓扑设备所有 kind value（含 unknown 兜底） */
export var TOPOLOGY_DEVICE_KIND_VALUES = [].concat(_toConsumableArray(TOPOLOGY_DEVICE_TYPE_CODES.map(typeCodeToKindValue)), ["".concat(TOPOLOGY_DEVICE_KIND_PREFIX, "unknown")]);

/** 从 HTTP 封装响应中提取 `data` 数组（兼容 `{data: [...]}` / 直接数组） */
export function extractRawDeviceList(response) {
  if (Array.isArray(response)) return response;
  if (response && _typeof(response) === 'object') {
    var arr = response.data;
    if (Array.isArray(arr)) return arr;
  }
  return [];
}

/** 接口返回里单条连接（局部字段，仅用本视图实际用到的） */

/** 从 HTTP 封装响应中提取设备连接 `data` 数组 */
export function extractRawDeviceConnectionList(response) {
  if (Array.isArray(response)) return response;
  if (response && _typeof(response) === 'object') {
    var arr = response.data;
    if (Array.isArray(arr)) return arr;
  }
  return [];
}

/** 多语言名称取值：优先 zh-CN → zh-HK → en → original → 兜底 */
function pickLangText(source, fallback) {
  if (typeof source === 'string') return source.trim() || fallback;
  if (!source || _typeof(source) !== 'object') return fallback;
  var candidates = [source['zh-CN'], source['zh-HK'], source.en, source.original];
  for (var _i = 0, _candidates = candidates; _i < _candidates.length; _i++) {
    var v = _candidates[_i];
    if (typeof v === 'string' && v.trim()) return v.trim();
  }
  return fallback;
}
function deviceDisplayName(device) {
  var _ref, _ref2, _pickLangText, _device$model;
  return (_ref = (_ref2 = (_pickLangText = pickLangText(device.name, undefined)) !== null && _pickLangText !== void 0 ? _pickLangText : pickLangText((_device$model = device.model) === null || _device$model === void 0 ? void 0 : _device$model.name, undefined)) !== null && _ref2 !== void 0 ? _ref2 : device.number) !== null && _ref !== void 0 ? _ref : "#".concat(device.id);
}

/**
 * 设备 IP：与 devicePlanning 详情「网络 → 本地 IP」同源（`data.network.local_ip`）。
 */
export function deviceIpAddress(device) {
  var data = device.data;
  if (data && _typeof(data) === 'object') {
    var network = data.network;
    if (network && _typeof(network) === 'object' && !Array.isArray(network)) {
      var lip = network.local_ip;
      if (typeof lip === 'string' && lip.trim()) return lip.trim();
    }
  }
  return undefined;
}
function deviceStatus(device) {
  switch (device.status) {
    case 'online':
      return 'online';
    case 'offline':
      return 'offline';
    case 'warning':
      return 'warning';
    default:
      return 'unknown';
  }
}

/** 设备数组 → `dataSources.devices`（PisellFloorMapLayout 需要的扁平 record 形式） */
export function buildTopologyDeviceDataSources(rawDevices) {
  var records = rawDevices.map(function (device) {
    var _ref3, _pickLangText2, _device$model2, _device$number;
    return {
      id: String(device.id),
      name: deviceDisplayName(device),
      model: (_ref3 = (_pickLangText2 = pickLangText((_device$model2 = device.model) === null || _device$model2 === void 0 ? void 0 : _device$model2.name, undefined)) !== null && _pickLangText2 !== void 0 ? _pickLangText2 : device.model_code) !== null && _ref3 !== void 0 ? _ref3 : undefined,
      sn: (_device$number = device.number) !== null && _device$number !== void 0 ? _device$number : undefined,
      status: deviceStatus(device),
      ip: deviceIpAddress(device)
    };
  });
  return _defineProperty({}, TOPOLOGY_DEVICES_DATA_SOURCE_KEY, records);
}

/**
 * 基于固定 `TOPOLOGY_DEVICE_TYPE_CODES` 枚举生成图元种类，**不依赖**接口响应。
 * 接口若返回新的 type_code，运行时会落到 `unknown` 兜底 kind（见 `buildTopologyConnectionAwareSceneElements`）。
 */
export function buildTopologyDeviceElementKinds() {
  var typeCodeToKind = new Map();
  var kinds = TOPOLOGY_DEVICE_TYPE_CODES.map(function (code) {
    var _TYPE_CODE_LABEL$code;
    var value = typeCodeToKindValue(code);
    typeCodeToKind.set(code, value);
    return {
      value: value,
      label: (_TYPE_CODE_LABEL$code = TYPE_CODE_LABEL[code]) !== null && _TYPE_CODE_LABEL$code !== void 0 ? _TYPE_CODE_LABEL$code : code,
      categoryId: DEVICE_CATEGORY_ID,
      dataBindingPolicy: 'optional',
      allowedDataSourceKeys: [TOPOLOGY_DEVICES_DATA_SOURCE_KEY],
      showInPalette: false
    };
  });

  /** unknown 兜底 kind，确保接口出现新 type_code 时也能渲染 */
  kinds.push({
    value: "".concat(TOPOLOGY_DEVICE_KIND_PREFIX, "unknown"),
    label: 'Unknown',
    categoryId: DEVICE_CATEGORY_ID,
    dataBindingPolicy: 'optional',
    allowedDataSourceKeys: [TOPOLOGY_DEVICES_DATA_SOURCE_KEY],
    showInPalette: false
  });
  var categories = [{
    id: DEVICE_CATEGORY_ID,
    label: 'Devices',
    order: 0
  }];
  return {
    categories: categories,
    kinds: kinds,
    typeCodeToKind: typeCodeToKind
  };
}

/**
 * 把设备 + 连接 → 无向邻接表（去除自环 / 端点缺失的边）
 */
function buildAdjacency(rawDevices, rawConnections) {
  var adj = new Map();
  var _iterator = _createForOfIteratorHelper(rawDevices),
    _step;
  try {
    for (_iterator.s(); !(_step = _iterator.n()).done;) {
      var d = _step.value;
      adj.set(String(d.id), new Set());
    }
  } catch (err) {
    _iterator.e(err);
  } finally {
    _iterator.f();
  }
  var _iterator2 = _createForOfIteratorHelper(rawConnections),
    _step2;
  try {
    for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
      var c = _step2.value;
      var s = String(c.source_device_id);
      var t = String(c.target_device_id);
      if (s === t) continue;
      if (!adj.has(s) || !adj.has(t)) continue;
      adj.get(s).add(t);
      adj.get(t).add(s);
    }
  } catch (err) {
    _iterator2.e(err);
  } finally {
    _iterator2.f();
  }
  return adj;
}

/** 邻接表 → 连通分量数组（每个分量是 deviceId 列表，保持原数组顺序） */
function findConnectedComponents(rawDevices, adj) {
  var visited = new Set();
  var components = [];
  var _iterator3 = _createForOfIteratorHelper(rawDevices),
    _step3;
  try {
    for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
      var d = _step3.value;
      var id = String(d.id);
      if (visited.has(id)) continue;
      var queue = [id];
      var comp = [];
      while (queue.length) {
        var _adj$get;
        var cur = queue.shift();
        if (visited.has(cur)) continue;
        visited.add(cur);
        comp.push(cur);
        var _iterator4 = _createForOfIteratorHelper((_adj$get = adj.get(cur)) !== null && _adj$get !== void 0 ? _adj$get : []),
          _step4;
        try {
          for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
            var n = _step4.value;
            if (!visited.has(n)) queue.push(n);
          }
        } catch (err) {
          _iterator4.e(err);
        } finally {
          _iterator4.f();
        }
      }
      components.push(comp);
    }
  } catch (err) {
    _iterator3.e(err);
  } finally {
    _iterator3.f();
  }
  return components;
}

/**
 * 设备层级语义（按 type_kind 严格分 3 行，从上到下展示）：
 *   smart   →  顶层（POS / 终端 / 显示屏 等"发起方"智能设备）
 *   dumb    →  中层（小票/标签打印机 / 交换机 等附属哑设备）
 *   integrated → 底层（一体机 / 集成型设备）
 */
var DEVICE_LAYER_ORDER = ['smart', 'dumb', 'integrated'];
function classifyDeviceLayer(device) {
  if (device.type_kind === 'dumb') return 'dumb';
  if (device.type_kind === 'integrated') return 'integrated';
  return 'smart';
}
/**
 * 蛇形居中：度数从大到小排序后，按"中间→右→左→右→左..."的顺序铺开，
 * 让连接最多的设备稳定居中。仅在「最顶层」使用，下层用 barycenter 对齐顶层。
 */
function snakeCenterByDegree(ids, adj) {
  if (ids.length <= 1) return _toConsumableArray(ids);
  var sorted = _toConsumableArray(ids).sort(function (a, b) {
    var _adj$get$size, _adj$get2, _adj$get$size2, _adj$get3;
    var da = (_adj$get$size = (_adj$get2 = adj.get(a)) === null || _adj$get2 === void 0 ? void 0 : _adj$get2.size) !== null && _adj$get$size !== void 0 ? _adj$get$size : 0;
    var db = (_adj$get$size2 = (_adj$get3 = adj.get(b)) === null || _adj$get3 === void 0 ? void 0 : _adj$get3.size) !== null && _adj$get$size2 !== void 0 ? _adj$get$size2 : 0;
    if (db !== da) return db - da;
    return ids.indexOf(a) - ids.indexOf(b);
  });
  var result = [];
  sorted.forEach(function (id, idx) {
    if (idx % 2 === 0) result.push(id);else result.unshift(id);
  });
  return result;
}

/**
 * 节点连到 reference 层的平均索引；无连接者落到最后；多对多取均值。
 */
function avgConnectedIdx(id, adj, referenceIdx, fallbackLen) {
  var neighbors = adj.get(id);
  if (!neighbors || neighbors.size === 0) return fallbackLen;
  var sum = 0;
  var cnt = 0;
  var _iterator5 = _createForOfIteratorHelper(neighbors),
    _step5;
  try {
    for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
      var n = _step5.value;
      var idx = referenceIdx.get(n);
      if (idx == null) continue;
      sum += idx;
      cnt += 1;
    }
  } catch (err) {
    _iterator5.e(err);
  } finally {
    _iterator5.f();
  }
  return cnt === 0 ? fallbackLen : sum / cnt;
}

/**
 * 把 target 层按其连接的 reference 层平均索引排序（in-place）。
 * 让 dumb / integrated 节点自动落到"它连接的 POS 的正下方"，连线大多平行不交叉。
 */
function sortLayerByReference(target, reference, adj) {
  if (target.length <= 1 || reference.length === 0) return;
  var refIdx = new Map(reference.map(function (id, idx) {
    return [id, idx];
  }));
  target.sort(function (a, b) {
    return avgConnectedIdx(a, adj, refIdx, target.length) - avgConnectedIdx(b, adj, refIdx, target.length);
  });
}

/**
 * 单 cluster 多层布局：按 `DEVICE_LAYER_ORDER` 从上到下分行，空层不占垂直空间；
 * 同层 barycenter 重排，多对多连线交叉显著减少。
 */
function layoutClusterByKind(comp, adj, deviceById) {
  if (comp.length === 1) {
    return {
      positions: new Map([[comp[0], {
        x: 0,
        y: 0
      }]]),
      width: NODE_WIDTH,
      height: NODE_HEIGHT,
      size: 1
    };
  }
  var byLayer = {
    smart: [],
    dumb: [],
    integrated: []
  };
  var _iterator6 = _createForOfIteratorHelper(comp),
    _step6;
  try {
    for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
      var id = _step6.value;
      var device = deviceById.get(id);
      if (!device) continue;
      byLayer[classifyDeviceLayer(device)].push(id);
    }

    /**
     * 顶层（smart）按"度数最大居中"蛇形排——重要 POS 放中间。
     * 下层（dumb / integrated）按 barycenter 跟随上一层位置排——
     * 让附属设备自动落在它对应主设备的正下方，连线尽量平行减少交叉。
     */
  } catch (err) {
    _iterator6.e(err);
  } finally {
    _iterator6.f();
  }
  var layers = DEVICE_LAYER_ORDER.map(function (k) {
    return byLayer[k];
  });
  if (layers[0].length > 0) {
    layers[0] = snakeCenterByDegree(layers[0], adj);
  }
  for (var i = 1; i < layers.length; i++) {
    sortLayerByReference(layers[i], layers[i - 1], adj);
  }
  var nonEmpty = layers.filter(function (l) {
    return l.length > 0;
  });
  var widths = nonEmpty.map(function (row) {
    return row.length * NODE_WIDTH + (row.length - 1) * NODE_GAP;
  });
  var width = Math.max.apply(Math, _toConsumableArray(widths).concat([NODE_WIDTH]));
  var height = nonEmpty.length * NODE_HEIGHT + (nonEmpty.length - 1) * LAYER_GAP_Y;
  var positions = new Map();
  var y = 0;
  var _iterator7 = _createForOfIteratorHelper(layers),
    _step7;
  try {
    var _loop = function _loop() {
      var row = _step7.value;
      if (row.length === 0) return 1; // continue
      var rowWidth = row.length * NODE_WIDTH + (row.length - 1) * NODE_GAP;
      var startX = (width - rowWidth) / 2;
      row.forEach(function (id, idx) {
        positions.set(id, {
          x: startX + idx * (NODE_WIDTH + NODE_GAP),
          y: y
        });
      });
      y += NODE_HEIGHT + LAYER_GAP_Y;
    };
    for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
      if (_loop()) continue;
    }
  } catch (err) {
    _iterator7.e(err);
  } finally {
    _iterator7.f();
  }
  return {
    positions: positions,
    width: width,
    height: height,
    size: comp.length
  };
}

/** 多 cluster 横向打包：超过 `CLUSTER_ROW_MAX_WIDTH` 换行；返回每个 cluster 的全局偏移 */
function packClusters(clusters) {
  var rowX = 0;
  var rowY = 0;
  var rowMaxH = 0;
  var totalRight = 0;
  var totalBottom = 0;
  var placements = [];
  var _iterator8 = _createForOfIteratorHelper(clusters),
    _step8;
  try {
    for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
      var cluster = _step8.value;
      if (rowX > 0 && rowX + cluster.width > CLUSTER_ROW_MAX_WIDTH) {
        rowY += rowMaxH + CLUSTER_GAP;
        rowX = 0;
        rowMaxH = 0;
      }
      placements.push({
        cluster: cluster,
        offsetX: rowX,
        offsetY: rowY
      });
      rowX += cluster.width + CLUSTER_GAP;
      rowMaxH = Math.max(rowMaxH, cluster.height);
      totalRight = Math.max(totalRight, rowX - CLUSTER_GAP);
      totalBottom = Math.max(totalBottom, rowY + cluster.height);
    }
  } catch (err) {
    _iterator8.e(err);
  } finally {
    _iterator8.f();
  }
  return {
    placements: placements,
    totalRight: totalRight,
    totalBottom: totalBottom
  };
}

/** 把 deviceId 列表按 type_kind 分到 3 个桶，保持原顺序 */
function splitByLayer(ids, deviceById) {
  var out = {
    smart: [],
    dumb: [],
    integrated: []
  };
  var _iterator9 = _createForOfIteratorHelper(ids),
    _step9;
  try {
    for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
      var id = _step9.value;
      var device = deviceById.get(id);
      if (!device) continue;
      out[classifyDeviceLayer(device)].push(id);
    }
  } catch (err) {
    _iterator9.e(err);
  } finally {
    _iterator9.f();
  }
  return out;
}

/**
 * 孤立设备区域：智能设备一行（上）、附属设备一行（下），数量过多时按
 * `cols = ceil(sqrt(N))` 换成多行网格。
 */
function placeIsolatedRow(ids, startX, startY, positions) {
  if (ids.length === 0) return {
    width: 0,
    height: 0
  };
  var cols = Math.max(1, Math.min(ids.length, Math.ceil(Math.sqrt(ids.length * 1.5))));
  var rows = Math.ceil(ids.length / cols);
  ids.forEach(function (id, idx) {
    var col = idx % cols;
    var row = Math.floor(idx / cols);
    positions.set(id, {
      x: startX + col * (NODE_WIDTH + NODE_GAP),
      y: startY + row * (NODE_HEIGHT + NODE_GAP)
    });
  });
  var width = cols * NODE_WIDTH + (cols - 1) * NODE_GAP;
  var height = rows * NODE_HEIGHT + (rows - 1) * NODE_GAP;
  return {
    width: width,
    height: height
  };
}

/**
 * 连接优先布局：
 * 1. 用连接关系把设备分成连通分量（多对多关系交由 edges 渲染）。
 * 2. 每个有连接的分量按 `type_kind` 分两行：smart / integrated 在上，dumb 在下；
 *    同层节点用 barycenter 启发重排，减少连线交叉。
 * 3. 多个 cluster 横向并排（超出最大行宽换行）。
 * 4. 孤立设备最后放在已布局区域**下方**：上一行 smart、下一行 dumb，整体居左对齐。
 */
export function buildTopologyConnectionAwareSceneElements(rawDevices, rawConnections, typeCodeToKind) {
  if (rawDevices.length === 0) {
    return {
      sceneElements: []
    };
  }
  var deviceById = new Map(rawDevices.map(function (d) {
    return [String(d.id), d];
  }));
  var adj = buildAdjacency(rawDevices, rawConnections);
  var components = findConnectedComponents(rawDevices, adj);
  var connectedComps = components.filter(function (c) {
    return c.length > 1;
  });
  var isolatedIds = components.filter(function (c) {
    return c.length === 1;
  }).map(function (c) {
    return c[0];
  });

  /** 节点数大的 cluster 先排，视觉上"主拓扑"靠左上 */
  var laidClusters = connectedComps.map(function (c) {
    return layoutClusterByKind(c, adj, deviceById);
  }).sort(function (a, b) {
    return b.size - a.size;
  });
  var _packClusters = packClusters(laidClusters),
    placements = _packClusters.placements,
    clusterTotalBottom = _packClusters.totalBottom;

  /** 全局位置表：deviceId → (x, y) */
  var positions = new Map();
  var _iterator10 = _createForOfIteratorHelper(placements),
    _step10;
  try {
    for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
      var p = _step10.value;
      var _iterator12 = _createForOfIteratorHelper(p.cluster.positions),
        _step12;
      try {
        for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
          var _step12$value = _slicedToArray(_step12.value, 2),
            id = _step12$value[0],
            pos = _step12$value[1];
          positions.set(id, {
            x: PADDING + p.offsetX + pos.x,
            y: PADDING + p.offsetY + pos.y
          });
        }
      } catch (err) {
        _iterator12.e(err);
      } finally {
        _iterator12.f();
      }
    }

    /**
     * 孤立设备：按 `DEVICE_LAYER_ORDER`（smart → dumb → integrated）依次成行；
     * 每行内数量过多自动换成多行网格。整体放在已布局区域下方。
     */
  } catch (err) {
    _iterator10.e(err);
  } finally {
    _iterator10.f();
  }
  if (isolatedIds.length > 0) {
    var isoByLayer = splitByLayer(isolatedIds, deviceById);
    var isoY = (clusterTotalBottom > 0 ? clusterTotalBottom + CLUSTER_GAP : 0) + PADDING;
    var _iterator11 = _createForOfIteratorHelper(DEVICE_LAYER_ORDER),
      _step11;
    try {
      for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
        var layer = _step11.value;
        var row = isoByLayer[layer];
        if (row.length === 0) continue;
        var rect = placeIsolatedRow(row, PADDING, isoY, positions);
        isoY += rect.height + LAYER_GAP_Y;
      }
    } catch (err) {
      _iterator11.e(err);
    } finally {
      _iterator11.f();
    }
  }

  /** 把位置写回 sceneElements，保持设备数组原顺序生成稳定 instanceId */
  var sceneElements = rawDevices.map(function (device) {
    var _positions$get, _device$type_code, _ref5;
    var id = String(device.id);
    var pos = (_positions$get = positions.get(id)) !== null && _positions$get !== void 0 ? _positions$get : {
      x: PADDING,
      y: PADDING
    };
    var code = ((_device$type_code = device.type_code) !== null && _device$type_code !== void 0 ? _device$type_code : '').trim();
    var kind = (_ref5 = code && typeCodeToKind.get(code)) !== null && _ref5 !== void 0 ? _ref5 : "".concat(TOPOLOGY_DEVICE_KIND_PREFIX, "unknown");
    return {
      instanceId: "topology-device-".concat(device.id),
      canvasId: TOPOLOGY_MAIN_CANVAS_ID,
      x: pos.x,
      y: pos.y,
      width: NODE_WIDTH,
      height: NODE_HEIGHT,
      elementKind: kind,
      name: deviceDisplayName(device),
      dataBinding: {
        dataSourceKey: TOPOLOGY_DEVICES_DATA_SOURCE_KEY,
        recordId: String(device.id)
      }
    };
  });
  return {
    sceneElements: sceneElements
  };
}

/** sceneElement.instanceId 命名约定：`topology-device-${deviceId}` */
function sceneInstanceIdFromDeviceId(deviceId) {
  return "topology-device-".concat(deviceId);
}
var SCENE_INSTANCE_ID_PREFIX = 'topology-device-';

/** instanceId → deviceId（仅当符合 sceneInstanceIdFromDeviceId 约定） */
function deviceIdFromSceneInstanceId(instanceId) {
  if (!instanceId.startsWith(SCENE_INSTANCE_ID_PREFIX)) return null;
  var rest = instanceId.slice(SCENE_INSTANCE_ID_PREFIX.length);
  return rest.length > 0 ? rest : null;
}

/** type_code → kind value，未识别走 unknown */
function resolveDeviceKind(device, typeCodeToKind) {
  var _device$type_code2, _ref6;
  var code = ((_device$type_code2 = device.type_code) !== null && _device$type_code2 !== void 0 ? _device$type_code2 : '').trim();
  return (_ref6 = code && typeCodeToKind.get(code)) !== null && _ref6 !== void 0 ? _ref6 : "".concat(TOPOLOGY_DEVICE_KIND_PREFIX, "unknown");
}

/**
 * 连接类型 → 视觉编码（描边色 + 虚线）。
 * 用线型/颜色区分 connection_type，避免在线上画文字 pill 互相重叠。
 *
 * - ethernet：默认色，实线（继续走 status 色板）
 * - usb：紫色实线
 * - bluetooth：蓝色虚线
 * - wifi：青色虚线
 * - unknown / 未识别：默认色，由 status 色板决定
 */
var CONNECTION_TYPE_STYLE = {
  ethernet: {},
  lan: {},
  wired: {},
  usb: {
    color: '#8b5cf6'
  },
  bluetooth: {
    color: '#3b82f6',
    dashed: true
  },
  bt: {
    color: '#3b82f6',
    dashed: true
  },
  wifi: {
    color: '#06b6d4',
    dashed: true
  },
  'wi-fi': {
    color: '#06b6d4',
    dashed: true
  },
  wireless: {
    color: '#06b6d4',
    dashed: true
  }
};
export function getConnectionTypeStyle(connectionType) {
  var _CONNECTION_TYPE_STYL;
  if (!connectionType) return {};
  var key = connectionType.trim().toLowerCase();
  return (_CONNECTION_TYPE_STYL = CONNECTION_TYPE_STYLE[key]) !== null && _CONNECTION_TYPE_STYL !== void 0 ? _CONNECTION_TYPE_STYL : {};
}
function connectionStatusToEdgeStatus(status) {
  switch (status) {
    case 'connected':
    case 'online':
      return 'online';
    case 'disconnected':
    case 'offline':
      return 'offline';
    case 'warning':
      return 'warning';
    default:
      return 'unknown';
  }
}

/**
 * connection 数组 → `FloorMapEdge[]`。
 * - 仅保留两端 device 都在当前 device 数组里的连接（避免悬空 edge）。
 * - 锚点强制走 top/bottom：source 所在层在 target 上方时 source.bottom→target.top；
 *   反之 source.top→target.bottom；同层时也用 top↔bottom，避免出现左右锚点连线。
 */
export function buildTopologyEdgesFromConnections(rawConnections, rawDevices) {
  var deviceById = new Map(rawDevices.map(function (d) {
    return [String(d.id), d];
  }));
  var layerOrder = function layerOrder(id) {
    var device = deviceById.get(id);
    if (!device) return 0;
    return DEVICE_LAYER_ORDER.indexOf(classifyDeviceLayer(device));
  };
  return rawConnections.filter(function (c) {
    var sId = String(c.source_device_id);
    var tId = String(c.target_device_id);
    return deviceById.has(sId) && deviceById.has(tId) && sId !== tId;
  }).map(function (c) {
    var _c$config, _c$config$connection_, _c$config2;
    var sId = String(c.source_device_id);
    var tId = String(c.target_device_id);
    var sLayer = layerOrder(sId);
    var tLayer = layerOrder(tId);
    var sourceAnchor;
    var targetAnchor;
    if (sLayer < tLayer) {
      sourceAnchor = 'bottom';
      targetAnchor = 'top';
    } else if (sLayer > tLayer) {
      sourceAnchor = 'top';
      targetAnchor = 'bottom';
    } else {
      /** 同层：约定 source 从 bottom 出、target 从 top 入，避免左右锚点 */
      sourceAnchor = 'bottom';
      targetAnchor = 'top';
    }
    var typeStyle = getConnectionTypeStyle((_c$config = c.config) === null || _c$config === void 0 ? void 0 : _c$config.connection_type);
    return _objectSpread(_objectSpread(_objectSpread({
      id: "topology-connection-".concat(c.id),
      sourceId: sceneInstanceIdFromDeviceId(sId),
      targetId: sceneInstanceIdFromDeviceId(tId),
      sourceAnchor: sourceAnchor,
      targetAnchor: targetAnchor,
      status: connectionStatusToEdgeStatus(c.status)
    }, typeStyle.color ? {
      color: typeStyle.color
    } : {}), typeStyle.dashed ? {
      dashed: true
    } : {}), {}, {
      extensions: {
        connectionType: (_c$config$connection_ = (_c$config2 = c.config) === null || _c$config2 === void 0 ? void 0 : _c$config2.connection_type) !== null && _c$config$connection_ !== void 0 ? _c$config$connection_ : null
      }
    });
  });
}
function topologyMainCanvasConfig() {
  return [{
    id: TOPOLOGY_MAIN_CANVAS_ID,
    name: 'Devices',
    order: 0,
    enabled: true,
    mapLayer: {
      showGrid: false,
      backgroundColor: '#ffffff'
    }
  }];
}

/**
 * 一站式：原始设备数组 + 连接数组 → 阅读态 `FloorMapViewConfig`。
 * `rawConnections` 未传或为空时不渲染连线，仅展示节点。
 *
 * 此函数只做「全量自动布局」，不考虑已保存的 floor-plan 数据。需要做对账请用
 * {@link reconcileTopologyView}。
 */
export function buildTopologyFloorMapConfigFromDevices(rawDevices) {
  var rawConnections = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
  var _buildTopologyDeviceE = buildTopologyDeviceElementKinds(),
    categories = _buildTopologyDeviceE.categories,
    kinds = _buildTopologyDeviceE.kinds,
    typeCodeToKind = _buildTopologyDeviceE.typeCodeToKind;
  var _buildTopologyConnect = buildTopologyConnectionAwareSceneElements(rawDevices, rawConnections, typeCodeToKind),
    sceneElements = _buildTopologyConnect.sceneElements;
  var edges = buildTopologyEdgesFromConnections(rawConnections, rawDevices);
  var config = {
    canvases: topologyMainCanvasConfig(),
    canvasUi: {
      canCreate: false,
      canEdit: false
    },
    elementKindCategories: categories,
    elementKinds: kinds,
    sceneElements: sceneElements,
    edges: edges
  };
  return {
    config: config,
    dataSources: buildTopologyDeviceDataSources(rawDevices)
  };
}

/**
 * 把「新增设备」追加到已有布局下方的「新增区」。
 *
 * 复用 connection-aware 整体布局：先按连线分簇成纵向多层，再把孤立设备
 * 按 DEVICE_LAYER_ORDER 网格化在簇下方。最后把整块结果平移到已存在元素
 * 的最下沿，避免和已保存/ghost/free 节点重叠。
 */
function layoutNewcomerSceneElements(added, rawConnections, keptScene, ghostScene, typeCodeToKind) {
  if (added.length === 0) return [];
  var baseY = PADDING;
  var allExisting = [].concat(_toConsumableArray(keptScene), _toConsumableArray(ghostScene));
  var _iterator13 = _createForOfIteratorHelper(allExisting),
    _step13;
  try {
    for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
      var _el$y2, _el$height;
      var el = _step13.value;
      var bottom = ((_el$y2 = el.y) !== null && _el$y2 !== void 0 ? _el$y2 : 0) + ((_el$height = el.height) !== null && _el$height !== void 0 ? _el$height : NODE_HEIGHT);
      if (bottom > baseY) baseY = bottom;
    }
  } catch (err) {
    _iterator13.e(err);
  } finally {
    _iterator13.f();
  }
  if (allExisting.length > 0) baseY += CLUSTER_GAP * 2;
  var _buildTopologyConnect2 = buildTopologyConnectionAwareSceneElements(added, rawConnections, typeCodeToKind),
    sceneElements = _buildTopologyConnect2.sceneElements;
  var dy = baseY - PADDING;
  return dy === 0 ? sceneElements : sceneElements.map(function (el) {
    var _el$y;
    return _objectSpread(_objectSpread({}, el), {}, {
      y: ((_el$y = el.y) !== null && _el$y !== void 0 ? _el$y : 0) + dy
    });
  });
}
/**
 * 接入 floor-plan 的核心：把「已保存布局」「设备列表」「连接列表」对账成最终 view config。
 *
 * 规则：
 * - 设备同时在 API + 已保存里：保留位置/尺寸，用 API 数据刷新 elementKind / name
 * - 设备只在 API（新设备）：放到「新增区」auto-layout
 * - 设备只在已保存（接口已下线，ghost）：保留 sceneElement，由 UI 侧渲染下线样式
 * - 边：按当前 connections 接口重建；保留 savedEdges 的 routing snapshot（按 id 配对）
 *
 * 当 `savedSceneElements` 为空时退化为整体 auto-layout，等价于 `buildTopologyFloorMapConfigFromDevices`。
 */
export function reconcileTopologyView(params) {
  var rawDevices = params.rawDevices,
    rawConnections = params.rawConnections,
    savedSceneElements = params.savedSceneElements,
    savedEdges = params.savedEdges;
  var _buildTopologyDeviceE2 = buildTopologyDeviceElementKinds(),
    categories = _buildTopologyDeviceE2.categories,
    kinds = _buildTopologyDeviceE2.kinds,
    typeCodeToKind = _buildTopologyDeviceE2.typeCodeToKind;
  var dataSources = buildTopologyDeviceDataSources(rawDevices);

  /** 已保存里没有任何 sceneElement：直接整体 auto-layout，等价首次 */
  if (!savedSceneElements || savedSceneElements.length === 0) {
    var _buildTopologyConnect3 = buildTopologyConnectionAwareSceneElements(rawDevices, rawConnections, typeCodeToKind),
      _sceneElements = _buildTopologyConnect3.sceneElements;
    var _edges = buildTopologyEdgesFromConnections(rawConnections, rawDevices);
    return {
      config: {
        canvases: topologyMainCanvasConfig(),
        canvasUi: {
          canCreate: false,
          canEdit: false
        },
        elementKindCategories: categories,
        elementKinds: kinds,
        sceneElements: _sceneElements,
        edges: _edges
      },
      dataSources: dataSources,
      addedDeviceIds: rawDevices.map(function (d) {
        return String(d.id);
      }),
      ghostInstanceIds: [],
      ghostDeviceIds: [],
      keptDeviceIds: []
    };
  }

  /**
   * 1. 把 saved sceneElements 拆成两组：
   *    - boundById：可解析出 deviceId 的（来自设备同步），参与三态对账
   *    - freeScene：用户在编辑模式手动添加、与设备接口无关（如自定义网络节点），原样透传
   */
  var boundById = new Map();
  var freeScene = [];
  var _iterator14 = _createForOfIteratorHelper(savedSceneElements),
    _step14;
  try {
    for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) {
      var _el$dataBinding;
      var el = _step14.value;
      var fromBinding = (_el$dataBinding = el.dataBinding) === null || _el$dataBinding === void 0 ? void 0 : _el$dataBinding.recordId;
      var id = typeof fromBinding === 'string' && fromBinding.trim() ? fromBinding : deviceIdFromSceneInstanceId(el.instanceId);
      if (id) {
        boundById.set(id, el);
      } else {
        freeScene.push(el);
      }
    }
  } catch (err) {
    _iterator14.e(err);
  } finally {
    _iterator14.f();
  }
  var deviceById = new Map(rawDevices.map(function (d) {
    return [String(d.id), d];
  }));

  /** 2. 三态分桶 */
  var keptScene = [];
  var newDevices = [];
  var keptDeviceIds = [];
  var _iterator15 = _createForOfIteratorHelper(rawDevices),
    _step15;
  try {
    for (_iterator15.s(); !(_step15 = _iterator15.n()).done;) {
      var device = _step15.value;
      var _id = String(device.id);
      var saved = boundById.get(_id);
      if (saved) {
        keptScene.push(_objectSpread(_objectSpread({}, saved), {}, {
          elementKind: resolveDeviceKind(device, typeCodeToKind),
          name: deviceDisplayName(device),
          dataBinding: {
            dataSourceKey: TOPOLOGY_DEVICES_DATA_SOURCE_KEY,
            recordId: _id
          }
        }));
        keptDeviceIds.push(_id);
      } else {
        newDevices.push(device);
      }
    }
  } catch (err) {
    _iterator15.e(err);
  } finally {
    _iterator15.f();
  }
  var ghostScene = [];
  var ghostInstanceIds = [];
  var ghostDeviceIds = [];
  var _iterator16 = _createForOfIteratorHelper(boundById),
    _step16;
  try {
    for (_iterator16.s(); !(_step16 = _iterator16.n()).done;) {
      var _step16$value = _slicedToArray(_step16.value, 2),
        _id2 = _step16$value[0],
        _saved = _step16$value[1];
      if (deviceById.has(_id2)) continue;
      /** 保持原有 sceneElement，让用户能在编辑态删除；渲染层根据 device 是否存在自行降级 */
      ghostScene.push(_saved);
      ghostInstanceIds.push(_saved.instanceId);
      ghostDeviceIds.push(_id2);
    }

    /** 3. 新设备布局：在已布局 + ghost + free 包围盒下方追加 */
  } catch (err) {
    _iterator16.e(err);
  } finally {
    _iterator16.f();
  }
  var newScene = layoutNewcomerSceneElements(newDevices, rawConnections, [].concat(keptScene, freeScene), ghostScene, typeCodeToKind);
  var sceneElements = [].concat(freeScene, keptScene, _toConsumableArray(newScene), ghostScene);

  /** 4. 边：按当前连接接口重建；保留 savedEdges 的 routing snapshot */
  var savedEdgeById = new Map((savedEdges !== null && savedEdges !== void 0 ? savedEdges : []).map(function (e) {
    return [e.id, e];
  }));
  var rebuilt = buildTopologyEdgesFromConnections(rawConnections, rawDevices);
  var edges = rebuilt.map(function (e) {
    var saved = savedEdgeById.get(e.id);
    return saved !== null && saved !== void 0 && saved.routing ? _objectSpread(_objectSpread({}, e), {}, {
      routing: saved.routing
    }) : e;
  });
  var config = {
    canvases: topologyMainCanvasConfig(),
    canvasUi: {
      canCreate: false,
      canEdit: false
    },
    elementKindCategories: categories,
    elementKinds: kinds,
    sceneElements: sceneElements,
    edges: edges
  };
  return {
    config: config,
    dataSources: dataSources,
    addedDeviceIds: newDevices.map(function (d) {
      return String(d.id);
    }),
    ghostInstanceIds: ghostInstanceIds,
    ghostDeviceIds: ghostDeviceIds,
    keptDeviceIds: keptDeviceIds
  };
}