"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var queries_exports = {};
__export(queries_exports, {
  GraphQueryManager: () => GraphQueryManager
});
module.exports = __toCommonJS(queries_exports);
var import_traversal = require("./traversal");
class GraphQueryManager {
  constructor(db) {
    this.db = db;
    this.traverser = new import_traversal.GraphTraverser(db);
  }
  /** Get full context for a node (node + ancestors + children + callers + callees). */
  async getContext(nodeId) {
    const ctx = this.db.getNodeContext(nodeId);
    if (!ctx) {
      const node = this.db.getNode(nodeId);
      if (!node) throw new Error(`Node not found: ${nodeId}`);
      return { node, ancestors: [], children: [], callers: [], callees: [] };
    }
    return ctx;
  }
  /** Get all nodes that call the given node (depth=1, incoming 'calls' edges). */
  async getCallers(nodeId, limit) {
    return this.traverser.traverseBFS(nodeId, {
      direction: "incoming",
      edgeKinds: ["calls"],
      maxDepth: 1,
      limit
    });
  }
  /** Get all nodes called by the given node (depth=1, outgoing 'calls' edges). */
  async getCallees(nodeId, limit) {
    return this.traverser.traverseBFS(nodeId, {
      direction: "outgoing",
      edgeKinds: ["calls"],
      maxDepth: 1,
      limit
    });
  }
  /** Get the full call graph rooted at nodeId up to the given depth. */
  async getCallGraph(nodeId, depth = 3) {
    const nodes = await this.traverser.traverseBFS(nodeId, {
      direction: "outgoing",
      edgeKinds: ["calls"],
      maxDepth: depth,
      includeStart: true
    });
    const nodeIds = nodes.map((n) => n.id);
    const edges = this.db.getEdgesForNodes(nodeIds).filter(
      (e) => nodeIds.includes(e.source) && nodeIds.includes(e.target) && e.kind === "calls"
    );
    return { nodes, edges, entryPoints: [nodeId] };
  }
  /**
   * Get the type hierarchy for a node.
   * direction: 'up' = base types, 'down' = derived types, 'both' = all.
   */
  async getTypeHierarchy(nodeId, direction = "both") {
    const traversalDirection = direction === "up" ? "outgoing" : direction === "down" ? "incoming" : "both";
    return this.traverser.traverseBFS(nodeId, {
      direction: traversalDirection,
      edgeKinds: ["extends", "implements"]
    });
  }
  /** Get all nodes that would be impacted by changing nodeId (incoming edges). */
  async getImpactRadius(nodeId, depth = 2) {
    return this.traverser.traverseBFS(nodeId, {
      direction: "incoming",
      maxDepth: depth
    });
  }
  /** Find the shortest path between two nodes via BFS. */
  async findPath(fromId, toId, maxDepth = 10) {
    if (fromId === toId) {
      const node = this.db.getNode(fromId);
      return node ? [node] : [];
    }
    const prev = /* @__PURE__ */ new Map();
    const queue = [[fromId, 0]];
    const visited = /* @__PURE__ */ new Set([fromId]);
    let found = false;
    outer: while (queue.length > 0) {
      const [currentId, depth] = queue.shift();
      if (depth >= maxDepth) continue;
      const edges = this.db.getEdgesForNodes([currentId]);
      for (const edge of edges) {
        const neighborId = edge.source === currentId ? edge.target : edge.source;
        if (!visited.has(neighborId)) {
          visited.add(neighborId);
          prev.set(neighborId, currentId);
          if (neighborId === toId) {
            found = true;
            break outer;
          }
          queue.push([neighborId, depth + 1]);
        }
      }
    }
    if (!found) return [];
    const pathIds = [];
    let cur = toId;
    while (cur !== void 0) {
      pathIds.unshift(cur);
      cur = prev.get(cur);
    }
    const result = [];
    for (const id of pathIds) {
      const node = this.db.getNode(id);
      if (node) result.push(node);
    }
    return result;
  }
  /** Get all ancestor containers of a node (incoming 'contains' edges). */
  async getAncestors(nodeId) {
    return this.traverser.traverseBFS(nodeId, {
      direction: "incoming",
      edgeKinds: ["contains"]
    });
  }
  /** Get direct children of a node (outgoing 'contains' edges, depth=1). */
  async getChildren(nodeId) {
    return this.traverser.traverseBFS(nodeId, {
      direction: "outgoing",
      edgeKinds: ["contains"],
      maxDepth: 1
    });
  }
  /**
   * Find test files that are transitively affected by changes to the given source files.
   * BFS-traverses import/call dependents to discover which test files depend on changed code.
   */
  getAffectedTests(changedFiles, opts) {
    const picomatch = require("picomatch");
    const depth = opts?.depth ?? 5;
    const isTest = picomatch(
      opts?.testPattern ?? "{**/*.spec.*,**/*.test.*,**/*_test.*,**/*Test.*,**/*Spec.*,**/*.t.sol,**/*.bats,**/e2e/**,**/test/**,**/tests/**,**/spec/**,**/__tests__/**,**/src/test/**}"
    );
    const results = /* @__PURE__ */ new Set();
    for (const file of changedFiles) {
      const rel = file.replace(/\\/g, "/").replace(/^\.\//, "");
      if (isTest(rel)) {
        results.add(rel);
        continue;
      }
      const visited = /* @__PURE__ */ new Set([rel]);
      let frontier = [rel];
      for (let d = 0; d < depth; d++) {
        if (frontier.length === 0) break;
        const next = [];
        for (const f of frontier) {
          for (const dep of this.db.getDependentFiles(f)) {
            if (!visited.has(dep)) {
              visited.add(dep);
              next.push(dep);
              if (isTest(dep)) results.add(dep);
            }
          }
        }
        frontier = next;
      }
    }
    return [...results].sort();
  }
  /** Find all circular import dependencies. Returns arrays of file paths forming cycles. */
  async findCircularDependencies() {
    return this.db.findCircularDependencies();
  }
  /** Find nodes with no incoming edges (potential dead code). */
  async findDeadCode(limit = 50) {
    return this.db.findDeadCode(limit);
  }
  /** Get metrics for a node (edge counts, callers, callees, children). */
  async getNodeMetrics(nodeId) {
    return this.db.getNodeMetrics(nodeId);
  }
  /**
   * Build a subgraph from a set of node IDs, optionally traversing further
   * with the given TraversalOptions.
   */
  async getFilteredSubgraph(nodeIds, opts) {
    const allNodeIds = new Set(nodeIds);
    if (opts) {
      for (const startId of nodeIds) {
        const traversed = await this.traverser.traverseBFS(startId, opts);
        for (const node of traversed) {
          allNodeIds.add(node.id);
        }
      }
    }
    const ids = [...allNodeIds];
    const nodes = [];
    for (const id of ids) {
      const node = this.db.getNode(id);
      if (node) nodes.push(node);
    }
    const edges = this.db.getEdgesForNodes(ids).filter(
      (e) => allNodeIds.has(e.source) && allNodeIds.has(e.target)
    );
    return { nodes, edges, entryPoints: nodeIds };
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  GraphQueryManager
});
//# sourceMappingURL=queries.js.map
