function detectLeidenCommunities(nodeList, edgeList, options = {}) {
  const resolution = options.resolution ?? 1;
  const minCommunitySize = options.minCommunitySize ?? 3;
  const maxIterations = options.maxIterations ?? 20;
  const graph = buildGraph(nodeList, edgeList);
  if (graph.totalWeight === 0) {
    const fallback = {};
    return fallback;
  }
  let currentGraph = graph;
  let nodeToMeta = /* @__PURE__ */ new Map();
  for (const n of nodeList) {
    nodeToMeta.set(n, n);
  }
  let level = 0;
  while (level < maxIterations) {
    const { partition, refinedPartition } = runLeidenLevel(currentGraph, resolution);
    let changed = false;
    for (const [node, comm] of Object.entries(partition)) {
      if (node !== comm) {
        changed = true;
        break;
      }
    }
    if (!changed) break;
    const nextNodeToMeta = /* @__PURE__ */ new Map();
    for (const [origNode, metaNode] of nodeToMeta.entries()) {
      const currentComm = refinedPartition[metaNode] || partition[metaNode];
      nextNodeToMeta.set(origNode, currentComm);
    }
    nodeToMeta = nextNodeToMeta;
    currentGraph = aggregateGraph(currentGraph, refinedPartition);
    level++;
  }
  const communities = {};
  for (const [origNode, finalComm] of nodeToMeta.entries()) {
    if (!communities[finalComm]) {
      communities[finalComm] = [];
    }
    communities[finalComm].push(origNode);
  }
  const filteredCommunities = {};
  let index = 1;
  for (const [commId, members] of Object.entries(communities)) {
    if (members.length >= minCommunitySize) {
      filteredCommunities[`c${index++}`] = members;
    }
  }
  return filteredCommunities;
}
function buildGraph(nodes, edges) {
  const degrees = /* @__PURE__ */ new Map();
  const neighbors = /* @__PURE__ */ new Map();
  let totalWeight = 0;
  for (const n of nodes) {
    degrees.set(n, 0);
    neighbors.set(n, /* @__PURE__ */ new Map());
  }
  for (const e of edges) {
    const u = e.source;
    const v = e.target;
    const w = e.weight;
    if (!degrees.has(u) || !degrees.has(v) || u === v) continue;
    degrees.set(u, degrees.get(u) + w);
    degrees.set(v, degrees.get(v) + w);
    const uNeighbors = neighbors.get(u);
    uNeighbors.set(v, (uNeighbors.get(v) || 0) + w);
    const vNeighbors = neighbors.get(v);
    vNeighbors.set(u, (vNeighbors.get(u) || 0) + w);
    totalWeight += w;
  }
  return { nodes, degrees, neighbors, totalWeight };
}
function runLeidenLevel(graph, resolution) {
  const partition = runLocalMovement(graph, resolution);
  const refinedPartition = runRefinement(graph, partition, resolution);
  return { partition, refinedPartition };
}
function runLocalMovement(graph, resolution) {
  const partition = {};
  const communityWeights = /* @__PURE__ */ new Map();
  for (const n of graph.nodes) {
    partition[n] = n;
    communityWeights.set(n, graph.degrees.get(n));
  }
  let changed = true;
  let iterations = 0;
  const maxMoveIterations = 10;
  while (changed && iterations < maxMoveIterations) {
    changed = false;
    iterations++;
    const shuffledNodes = [...graph.nodes].sort(() => Math.random() - 0.5);
    for (const node of shuffledNodes) {
      const currentComm = partition[node];
      const nodeWeight = graph.degrees.get(node);
      const nodeNeighbors = graph.neighbors.get(node);
      if (nodeNeighbors.size === 0) continue;
      const commConnections = /* @__PURE__ */ new Map();
      for (const [nbr, w] of nodeNeighbors.entries()) {
        const nbrComm = partition[nbr];
        commConnections.set(nbrComm, (commConnections.get(nbrComm) || 0) + w);
      }
      let bestComm = currentComm;
      let maxDeltaQ = 0;
      for (const [comm, connWeight] of commConnections.entries()) {
        if (comm === currentComm) continue;
        const commWeight = communityWeights.get(comm) || 0;
        const deltaQ = connWeight - resolution * (commWeight * nodeWeight) / (2 * graph.totalWeight);
        if (deltaQ > maxDeltaQ) {
          maxDeltaQ = deltaQ;
          bestComm = comm;
        }
      }
      if (bestComm !== currentComm && maxDeltaQ > 0) {
        partition[node] = bestComm;
        communityWeights.set(currentComm, (communityWeights.get(currentComm) || 0) - nodeWeight);
        communityWeights.set(bestComm, (communityWeights.get(bestComm) || 0) + nodeWeight);
        changed = true;
      }
    }
  }
  return partition;
}
function runRefinement(graph, partition, resolution) {
  const refinedPartition = {};
  const commToNodes = /* @__PURE__ */ new Map();
  for (const [node, comm] of Object.entries(partition)) {
    if (!commToNodes.has(comm)) {
      commToNodes.set(comm, []);
    }
    commToNodes.get(comm).push(node);
  }
  for (const nodes of commToNodes.values()) {
    const subPartition = {};
    const subCommWeights = /* @__PURE__ */ new Map();
    for (const n of nodes) {
      subPartition[n] = n;
      subCommWeights.set(n, graph.degrees.get(n));
    }
    for (const node of nodes) {
      const nodeWeight = graph.degrees.get(node);
      const nodeNeighbors = graph.neighbors.get(node);
      const subCommConnections = /* @__PURE__ */ new Map();
      for (const [nbr, w] of nodeNeighbors.entries()) {
        if (partition[nbr] !== partition[node]) continue;
        const nbrSubComm = subPartition[nbr];
        subCommConnections.set(nbrSubComm, (subCommConnections.get(nbrSubComm) || 0) + w);
      }
      let bestSubComm = subPartition[node];
      let maxDeltaQ = 0;
      for (const [subComm, connWeight] of subCommConnections.entries()) {
        const subCommWeight = subCommWeights.get(subComm) || 0;
        const deltaQ = connWeight - resolution * (subCommWeight * nodeWeight) / (2 * graph.totalWeight);
        if (deltaQ > maxDeltaQ) {
          maxDeltaQ = deltaQ;
          bestSubComm = subComm;
        }
      }
      if (bestSubComm !== subPartition[node] && maxDeltaQ > 0) {
        const currentSubComm = subPartition[node];
        subPartition[node] = bestSubComm;
        subCommWeights.set(currentSubComm, (subCommWeights.get(currentSubComm) || 0) - nodeWeight);
        subCommWeights.set(bestSubComm, (subCommWeights.get(bestSubComm) || 0) + nodeWeight);
      }
    }
    for (const n of nodes) {
      refinedPartition[n] = subPartition[n];
    }
  }
  return refinedPartition;
}
function aggregateGraph(graph, refinedPartition) {
  const metaNodesSet = /* @__PURE__ */ new Set();
  for (const comm of Object.values(refinedPartition)) {
    metaNodesSet.add(comm);
  }
  const metaNodes = Array.from(metaNodesSet);
  const metaEdgesMap = /* @__PURE__ */ new Map();
  for (const mNode of metaNodes) {
    metaEdgesMap.set(mNode, /* @__PURE__ */ new Map());
  }
  for (const [node, nodeNeighbors] of graph.neighbors.entries()) {
    const srcComm = refinedPartition[node];
    for (const [nbr, w] of nodeNeighbors.entries()) {
      const tgtComm = refinedPartition[nbr];
      const srcCommMap = metaEdgesMap.get(srcComm);
      srcCommMap.set(tgtComm, (srcCommMap.get(tgtComm) || 0) + w);
    }
  }
  const metaEdges = [];
  for (const [src, targets] of metaEdgesMap.entries()) {
    for (const [tgt, w] of targets.entries()) {
      metaEdges.push({ source: src, target: tgt, weight: w / 2 });
    }
  }
  return buildGraph(metaNodes, metaEdges);
}
export {
  detectLeidenCommunities
};
