import { detectLeidenCommunities } from "./leiden.js";
class ClusterEngine {
  constructor(store) {
    this.store = store;
  }
  store;
  detect(repo) {
    const startTime = Date.now();
    const nsRes = this.detectNamespaceClusters(repo);
    const dirRes = this.detectDirectoryClusters(repo, nsRes.memberships);
    const allPrimaryMemberships = /* @__PURE__ */ new Map();
    for (const [f, c] of nsRes.memberships.entries()) {
      allPrimaryMemberships.set(f, c);
    }
    for (const [f, c] of dirRes.memberships.entries()) {
      allPrimaryMemberships.set(f, c);
    }
    const primaryClustersMap = /* @__PURE__ */ new Map();
    for (const c of nsRes.clusters) {
      primaryClustersMap.set(c.name, c);
    }
    for (const c of dirRes.clusters) {
      if (!primaryClustersMap.has(c.name)) {
        primaryClustersMap.set(c.name, c);
      }
    }
    const primaryFileCounts = /* @__PURE__ */ new Map();
    for (const clusterName of allPrimaryMemberships.values()) {
      let current = clusterName;
      while (current) {
        primaryFileCounts.set(current, (primaryFileCounts.get(current) || 0) + 1);
        const idx = current.lastIndexOf(".");
        current = idx !== -1 ? current.substring(0, idx) : null;
      }
    }
    const primaryClusters = Array.from(primaryClustersMap.values()).map((c) => ({
      ...c,
      fileCount: primaryFileCounts.get(c.name) || 0
    }));
    const clusterFileSets = /* @__PURE__ */ new Map();
    for (const c of primaryClusters) {
      clusterFileSets.set(c.name, /* @__PURE__ */ new Set());
    }
    for (const [filePath, clusterName] of allPrimaryMemberships.entries()) {
      let current = clusterName;
      while (current) {
        if (clusterFileSets.has(current)) {
          clusterFileSets.get(current).add(filePath);
        }
        const idx = current.lastIndexOf(".");
        current = idx !== -1 ? current.substring(0, idx) : null;
      }
    }
    const files = this.store.getAllFiles(repo);
    const edges = this.store.getAllEdges(repo);
    const commRes = this.detectCommunityClusters(repo, files, edges, clusterFileSets);
    const layerRes = this.detectLayerClusters(repo, files);
    this.store.inTransaction(() => {
      this.store.clearClusters(repo);
      for (const c of primaryClusters) {
        this.store.insertCluster({
          repo,
          name: c.name,
          label: c.label,
          source: c.source,
          parentName: c.parentName,
          depth: c.depth,
          fileCount: c.fileCount
        });
      }
      for (const [filePath, clusterName] of allPrimaryMemberships.entries()) {
        this.store.insertClusterMembership({
          filePath,
          clusterName,
          repo,
          isPrimary: 1
        });
      }
      for (const c of commRes.clusters) {
        this.store.insertCluster({
          repo,
          name: c.name,
          label: c.label,
          source: c.source,
          parentName: c.parentName,
          depth: c.depth,
          fileCount: c.fileCount
        });
      }
      for (const m of commRes.memberships) {
        this.store.insertClusterMembership({
          filePath: m.filePath,
          clusterName: m.clusterName,
          repo,
          isPrimary: 0
        });
      }
      for (const c of layerRes.clusters) {
        this.store.insertCluster({
          repo,
          name: c.name,
          label: c.label,
          source: c.source,
          parentName: c.parentName,
          depth: c.depth,
          fileCount: c.fileCount,
          layer: c.layer
        });
      }
      for (const m of layerRes.memberships) {
        this.store.insertClusterMembership({
          filePath: m.filePath,
          clusterName: m.clusterName,
          repo,
          isPrimary: 0
        });
      }
    });
    const namespaceCount = primaryClusters.filter((c) => c.source === "namespace").length;
    const directoryCount = primaryClusters.filter((c) => c.source === "directory").length;
    return {
      clustersFound: primaryClusters.length + commRes.clusters.length + layerRes.clusters.length,
      namespaceClusters: namespaceCount,
      directoryClusters: directoryCount,
      communityClusters: commRes.clusters.length,
      layerClusters: layerRes.clusters.length,
      filesAssigned: allPrimaryMemberships.size,
      durationMs: Date.now() - startTime
    };
  }
  detectNamespaceClusters(repo) {
    const files = this.store.getAllFiles(repo);
    const clustersMap = /* @__PURE__ */ new Map();
    const memberships = /* @__PURE__ */ new Map();
    for (const f of files) {
      let ns = f.namespace || null;
      if (!ns && f.metadata) {
        try {
          const meta = JSON.parse(f.metadata);
          ns = meta.namespace || null;
        } catch {
        }
      }
      if (ns) {
        const normalized = ns.replace(/\\/g, ".").replace(/^\.|\.$/g, "");
        if (!normalized) continue;
        memberships.set(f.path, normalized);
        let current = normalized;
        while (current) {
          if (!clustersMap.has(current)) {
            const parts = current.split(".");
            const parentName = parts.slice(0, -1).join(".") || null;
            const depth = parts.length - 1;
            clustersMap.set(current, {
              name: current,
              label: parts.join(" / "),
              source: "namespace",
              parentName,
              depth,
              fileCount: 0
            });
          }
          const idx = current.lastIndexOf(".");
          current = idx !== -1 ? current.substring(0, idx) : "";
        }
      }
    }
    return { clusters: Array.from(clustersMap.values()), memberships };
  }
  detectDirectoryClusters(repo, existingPrimaryMemberships) {
    const files = this.store.getAllFiles(repo);
    const dirFileCount = /* @__PURE__ */ new Map();
    for (const f of files) {
      const filePath = f.path;
      const parts = filePath.split("/");
      if (parts.length <= 1) continue;
      for (let i = 1; i < parts.length; i++) {
        const dirPath = parts.slice(0, i).join("/");
        if (!dirFileCount.has(dirPath)) {
          dirFileCount.set(dirPath, /* @__PURE__ */ new Set());
        }
        dirFileCount.get(dirPath).add(filePath);
      }
    }
    const clustersMap = /* @__PURE__ */ new Map();
    const memberships = /* @__PURE__ */ new Map();
    for (const [dirPath, fileSet] of dirFileCount.entries()) {
      if (fileSet.size >= 2) {
        const clusterName = dirPath.replace(/\//g, ".");
        const parts = clusterName.split(".");
        const parentName = parts.slice(0, -1).join(".") || null;
        const depth = parts.length;
        clustersMap.set(clusterName, {
          name: clusterName,
          label: parts.join(" / "),
          source: "directory",
          parentName,
          depth,
          fileCount: 0
        });
      }
    }
    for (const f of files) {
      const filePath = f.path;
      if (existingPrimaryMemberships.has(filePath)) continue;
      const parts = filePath.split("/");
      if (parts.length <= 1) continue;
      for (let i = parts.length - 1; i >= 1; i--) {
        const dirPath = parts.slice(0, i).join("/");
        const clusterName = dirPath.replace(/\//g, ".");
        if (clustersMap.has(clusterName)) {
          memberships.set(filePath, clusterName);
          break;
        }
      }
    }
    return { clusters: Array.from(clustersMap.values()), memberships };
  }
  detectCommunityClusters(repo, files, edges, existingClusterFileSets) {
    const filesList = files.map((f) => f.path).sort();
    const leidenEdges = [];
    for (const e of edges) {
      const src = e.source_file;
      const tgt = e.target_file;
      if (src === tgt) continue;
      let weight = 1;
      const type = (e.edge_type || "").toLowerCase();
      if (type === "call") weight = 3;
      else if (type === "import" || type === "require") weight = 2;
      else if (type === "extends" || type === "implements") weight = 2;
      else if (type === "relation") weight = 2;
      else if (type === "type_reference" || type === "return_type" || type === "param_type") weight = 1;
      leidenEdges.push({ source: src, target: tgt, weight });
    }
    const communities = detectLeidenCommunities(filesList, leidenEdges, {
      resolution: 1.3,
      minCommunitySize: 2,
      maxIterations: 20
    });
    const clusters = [];
    const memberships = [];
    let communityIndex = 1;
    const sortedGroupKeys = Object.keys(communities).sort();
    for (const key of sortedGroupKeys) {
      const filePaths = communities[key];
      if (filePaths.length < 2) continue;
      const commName = `community_${communityIndex++}`;
      clusters.push({
        name: commName,
        label: `Community ${commName.split("_")[1]}`,
        source: "community",
        parentName: null,
        depth: 0,
        fileCount: filePaths.length
      });
      for (const f of filePaths) {
        memberships.push({ filePath: f, clusterName: commName });
      }
    }
    return { clusters, memberships };
  }
  /**
   * Assign an architectural layer to a file based on its path and filename.
   *
   * The rules are applied in priority order; the first match wins.
   */
  static assignFileLayer(filePath) {
    const lower = filePath.toLowerCase();
    const basename = lower.split("/").pop() ?? lower;
    const parts = lower.split("/");
    if (parts.some((p) => p === "tests" || p === "test" || p === "__tests__" || p === "spec" || p === "specs") || /\.(test|spec)\.[a-z]+$/.test(lower)) return "test";
    if (parts.some((p) => p === "agents" || p === "agent") || basename.startsWith("agent") || basename.endsWith(".agent.ts") || basename.startsWith("agents.")) return "agents";
    if (parts.some((p) => p === "docs" || p === "documentation") || basename.endsWith(".md") || basename.endsWith(".mdx")) return "docs";
    if (parts.some((p) => p === "config" || p === "configs" || p === "configuration") || /\.(config|env|rc)\.[a-z]+$/.test(lower) || basename.endsWith(".env") || [
      "tsconfig.json",
      "jsconfig.json",
      "vite.config.ts",
      "vitest.config.ts",
      "webpack.config.js",
      "rollup.config.js",
      "jest.config.js",
      "babel.config.js",
      ".eslintrc",
      "turbo.json",
      "nx.json",
      "package.json",
      "cargo.toml",
      "go.mod",
      "pyproject.toml",
      "composer.json",
      "build.gradle",
      "pom.xml",
      "makefile"
    ].includes(basename)) return "config";
    if (parts.some((p) => p === "types" || p === "interfaces" || p === "typings") || basename.endsWith(".d.ts") || basename === "types.ts" || basename === "interfaces.ts" || /\.types\.[a-z]+$/.test(lower) || /\.interface\.[a-z]+$/.test(lower)) return "types";
    if (parts.some((p) => p === "scripts" || p === "tools" || p === "build" || p === "tasks") || parts[0] === "scripts") return "scripts";
    if (parts.some(
      (p) => p === "ui" || p === "frontend" || p === "web" || p === "client" || p === "views" || p === "pages" || p === "components" || p === "widgets" || p === "app" || p === "public" || p === "assets" || p === "static"
    ) || /\.(vue|svelte|jsx|tsx)$/.test(lower) || basename.endsWith(".html") || basename.endsWith(".css")) return "ui";
    if (parts.some((p) => p === "exporters" || p === "export" || p === "exports") || basename.includes("exporter") || basename.includes("export")) return "exporters";
    if (parts.some((p) => p === "parsers" || p === "parser" || p === "languages" || p === "language") || basename.includes("parser") || basename.includes("-parser")) return "parsers";
    if (parts.some((p) => p === "frameworks" || p === "framework" || p === "plugins" || p === "adapters") || basename.includes("framework") || basename.includes("detector")) return "frameworks";
    if (parts.some(
      (p) => p === "api" || p === "routes" || p === "route" || p === "controllers" || p === "controller" || p === "handlers" || p === "handler" || p === "endpoints"
    ) || basename.includes("route") || basename.includes("controller") || basename.includes("handler")) return "api";
    if (parts.some(
      (p) => p === "stores" || p === "store" || p === "db" || p === "database" || p === "models" || p === "model" || p === "data" || p === "repositories" || p === "repository" || p === "migrations" || p === "seeds"
    ) || basename.startsWith("store") || basename.includes("repository") || basename.includes("migration")) return "data";
    if (parts.some(
      (p) => p === "utils" || p === "util" || p === "helpers" || p === "helper" || p === "shared" || p === "common" || p === "lib" || p === "libs"
    ) || basename.startsWith("util") || basename.startsWith("helper") || basename.includes("utils") || basename.includes("helpers")) return "utils";
    if (parts.some((p) => p === "core" || p === "domain" || p === "services" || p === "service") || basename.includes("service") || basename.includes("engine") || basename.includes("manager")) return "core";
    if (basename === "cli.ts" || basename === "cli.js" || basename === "main.ts" || basename === "main.js" || basename === "index.ts" || basename === "index.js" || parts.some((p) => p === "cli" || p === "bin" || p === "commands" || p === "cmd") || basename.includes("command") || basename.startsWith("cmd")) return "entry";
    return "other";
  }
  /**
   * Layer labels surfaced in the UI (ordered from high-level entry points down
   * to foundational infrastructure).
   */
  static LAYER_LABELS = {
    // Universal
    entry: "Entry Points",
    config: "Configuration",
    types: "Types / Interfaces",
    shared: "Shared / Utilities",
    test: "Tests",
    docs: "Documentation",
    other: "Other",
    // Backend
    api: "API / Routes",
    middleware: "Middleware",
    service: "Services / Use Cases",
    data: "Data / Persistence",
    integration: "Integrations / SDKs",
    auth: "Authentication / Security",
    // Frontend
    pages: "Pages / Views",
    components: "Components",
    state: "State Management",
    hooks: "Custom Hooks",
    styles: "Styles / Themes",
    assets: "Static Assets",
    // Tool/Library
    cli: "CLI / Commands",
    core: "Core Library Logic",
    parsers: "Parsers / Translators",
    plugins: "Plugins / Adapters",
    // Legacy aliases
    utils: "Utilities",
    ui: "UI / Frontend",
    exporters: "Exporters",
    agents: "Agents",
    frameworks: "Frameworks",
    scripts: "Scripts / Build"
  };
  detectLayerClusters(repo, files) {
    const layerGroups = /* @__PURE__ */ new Map();
    for (const f of files) {
      const filePath = f.path;
      const layer = f.role || ClusterEngine.assignFileLayer(filePath);
      if (!layerGroups.has(layer)) {
        layerGroups.set(layer, []);
      }
      layerGroups.get(layer).push(filePath);
    }
    const clusters = [];
    const memberships = [];
    for (const [layer, filePaths] of layerGroups.entries()) {
      if (filePaths.length === 0) continue;
      const clusterName = `layer:${layer}`;
      clusters.push({
        name: clusterName,
        label: ClusterEngine.LAYER_LABELS[layer] || String(layer),
        source: "layer",
        parentName: null,
        depth: 0,
        fileCount: filePaths.length,
        layer
      });
      for (const fp of filePaths) {
        memberships.push({ filePath: fp, clusterName });
      }
    }
    return { clusters, memberships };
  }
}
export {
  ClusterEngine
};
