function calculateMetrics(store, options = {}) {
  const files = store.getAllFiles(options.repo);
  const edges = store.getAllEdges(options.repo);
  const afferentMap = /* @__PURE__ */ new Map();
  const efferentMap = /* @__PURE__ */ new Map();
  for (const f of files) {
    afferentMap.set(f.path, /* @__PURE__ */ new Set());
    efferentMap.set(f.path, /* @__PURE__ */ new Set());
  }
  for (const e of edges) {
    if (options.verifiedOnly && e.verifiability === "inferred") {
      continue;
    }
    const src = e.source_file;
    const tgt = e.target_file;
    if (src === tgt) continue;
    if (afferentMap.has(tgt)) {
      afferentMap.get(tgt).add(src);
    }
    if (efferentMap.has(src)) {
      efferentMap.get(src).add(tgt);
    }
  }
  const results = [];
  for (const f of files) {
    const path = f.path;
    const lang = f.language;
    if (options.language && lang.toLowerCase() !== options.language.toLowerCase()) {
      continue;
    }
    const ca = afferentMap.get(path)?.size || 0;
    const ce = efferentMap.get(path)?.size || 0;
    const sum = ca + ce;
    const instability = sum > 0 ? ce / sum : 0;
    results.push({
      path,
      language: lang,
      afferent: ca,
      efferent: ce,
      instability
    });
  }
  return results.sort((a, b) => b.instability - a.instability || b.afferent - a.afferent || a.path.localeCompare(b.path));
}
function calculateGraphMetrics(store, repo) {
  const files = store.getAllFiles(repo);
  const edges = store.getAllEdges(repo);
  const fileCount = files.length;
  const edgeCount = edges.length;
  const density = fileCount > 1 ? edgeCount / (fileCount * (fileCount - 1)) : 0;
  const adj = /* @__PURE__ */ new Map();
  for (const f of files) {
    adj.set(f.path, /* @__PURE__ */ new Set());
  }
  for (const e of edges) {
    const src = e.source_file;
    const tgt = e.target_file;
    if (!src || !tgt || src === tgt) continue;
    if (!adj.has(src)) adj.set(src, /* @__PURE__ */ new Set());
    if (!adj.has(tgt)) adj.set(tgt, /* @__PURE__ */ new Set());
    adj.get(src).add(tgt);
    adj.get(tgt).add(src);
  }
  let totalTriplets = 0;
  let closedTriplets = 0;
  for (const neighbors of adj.values()) {
    const k = neighbors.size;
    if (k < 2) continue;
    totalTriplets += k * (k - 1) / 2;
    const neighborArr = Array.from(neighbors);
    for (let i = 0; i < neighborArr.length; i++) {
      for (let j = i + 1; j < neighborArr.length; j++) {
        const u = neighborArr[i];
        const w = neighborArr[j];
        if (adj.get(u)?.has(w)) {
          closedTriplets++;
        }
      }
    }
  }
  const transitivity = totalTriplets > 0 ? closedTriplets / totalTriplets : 0;
  return { density, transitivity };
}
function calculateClusterMetrics(store, repo) {
  const clusters = store.raw.prepare("SELECT name, source FROM clusters WHERE repo = ?").all(repo);
  const memberships = store.raw.prepare("SELECT file_path, cluster_name FROM cluster_membership WHERE repo = ? AND is_primary = 1").all(repo);
  const fileToCluster = /* @__PURE__ */ new Map();
  const clusterToFiles = /* @__PURE__ */ new Map();
  for (const m of memberships) {
    fileToCluster.set(m.file_path, m.cluster_name);
    if (!clusterToFiles.has(m.cluster_name)) {
      clusterToFiles.set(m.cluster_name, []);
    }
    clusterToFiles.get(m.cluster_name).push(m.file_path);
  }
  const edges = store.getAllEdges(repo);
  const results = [];
  for (const c of clusters) {
    const cName = c.name;
    const cFiles = clusterToFiles.get(cName) || [];
    let internalEdges = 0;
    let externalEdges = 0;
    const afferentClusters = /* @__PURE__ */ new Set();
    const efferentClusters = /* @__PURE__ */ new Set();
    for (const e of edges) {
      const src = e.source_file;
      const tgt = e.target_file;
      const srcComm = fileToCluster.get(src);
      const tgtComm = fileToCluster.get(tgt);
      if (srcComm === cName && tgtComm === cName) {
        internalEdges++;
      } else if (srcComm === cName && tgtComm && tgtComm !== cName) {
        externalEdges++;
        efferentClusters.add(tgtComm);
      } else if (tgtComm === cName && srcComm && srcComm !== cName) {
        externalEdges++;
        afferentClusters.add(srcComm);
      }
    }
    const afferentCoupling = afferentClusters.size;
    const efferentCoupling = efferentClusters.size;
    const sum = afferentCoupling + efferentCoupling;
    const instability = sum > 0 ? efferentCoupling / sum : 0;
    const totalEdges = internalEdges + externalEdges;
    const cohesionRatio = totalEdges > 0 ? internalEdges / totalEdges : 1;
    let totalSymbols = 0;
    let abstractSymbols = 0;
    for (const filePath of cFiles) {
      const symbols = store.getSymbolsForFile(filePath);
      totalSymbols += symbols.length;
      abstractSymbols += symbols.filter((s) => s.kind === "interface" || s.kind === "trait").length;
    }
    const abstractness = totalSymbols > 0 ? abstractSymbols / totalSymbols : 0;
    const distanceFromMainSeq = Math.abs(abstractness + instability - 1);
    results.push({
      clusterName: cName,
      fileCount: cFiles.length,
      afferentCoupling,
      efferentCoupling,
      instability,
      internalEdges,
      externalEdges,
      cohesionRatio,
      abstractness,
      distanceFromMainSeq
    });
  }
  return results;
}
function calculateDSM(store, repo) {
  const clusters = store.raw.prepare("SELECT name FROM clusters WHERE repo = ?").all(repo);
  const clusterNames = clusters.map((c) => c.name).sort();
  const clusterIndex = /* @__PURE__ */ new Map();
  clusterNames.forEach((name, idx) => clusterIndex.set(name, idx));
  const matrix = Array.from({ length: clusterNames.length }, () => Array(clusterNames.length).fill(0));
  const typesMap = {};
  const memberships = store.raw.prepare("SELECT file_path, cluster_name FROM cluster_membership WHERE repo = ? AND is_primary = 1").all(repo);
  const fileToCluster = /* @__PURE__ */ new Map();
  for (const m of memberships) {
    fileToCluster.set(m.file_path, m.cluster_name);
  }
  const edges = store.getAllEdges(repo);
  for (const e of edges) {
    const srcComm = fileToCluster.get(e.source_file);
    const tgtComm = fileToCluster.get(e.target_file);
    if (srcComm && tgtComm && srcComm !== tgtComm) {
      const srcIdx = clusterIndex.get(srcComm);
      const tgtIdx = clusterIndex.get(tgtComm);
      if (srcIdx !== void 0 && tgtIdx !== void 0) {
        matrix[srcIdx][tgtIdx]++;
        const cellKey = `${srcIdx}->${tgtIdx}`;
        if (!typesMap[cellKey]) {
          typesMap[cellKey] = {};
        }
        const edgeType = e.edge_type || "call";
        typesMap[cellKey][edgeType] = (typesMap[cellKey][edgeType] || 0) + 1;
      }
    }
  }
  const dominantTypes = Array.from({ length: clusterNames.length }, () => Array(clusterNames.length).fill(""));
  for (const [cellKey, counts] of Object.entries(typesMap)) {
    const [srcIdx, tgtIdx] = cellKey.split("->").map(Number);
    const dominant = Object.entries(counts).sort((a, b) => b[1] - a[1])[0];
    if (dominant) {
      dominantTypes[srcIdx][tgtIdx] = dominant[0];
    }
  }
  return {
    clusterNames,
    matrix,
    dominantTypes
  };
}
export {
  calculateClusterMetrics,
  calculateDSM,
  calculateGraphMetrics,
  calculateMetrics
};
