"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var context_exports = {};
__export(context_exports, {
  ContextBuilder: () => ContextBuilder
});
module.exports = __toCommonJS(context_exports);
var fs = __toESM(require("fs"));
var path = __toESM(require("path"));
var import_errors = require("../errors");
var import_query_utils = require("../search/query-utils");
var import_utils = require("../utils");
const EXCLUDED_NODE_KINDS = /* @__PURE__ */ new Set(["import", "export"]);
const DEFAULT_MAX_NODES = 20;
const DEFAULT_MAX_CODE_BLOCKS = 5;
const DEFAULT_MAX_CODE_BLOCK_SIZE = 1500;
const NO_RESULTS_MESSAGE = "No relevant context found for the given query.";
const FEATURE_REQUEST_WORDS = /* @__PURE__ */ new Set([
  "add",
  "create",
  "implement",
  "build",
  "make",
  "new",
  "feature",
  "support",
  "enable",
  "allow",
  "introduce",
  "generate",
  "write",
  "develop",
  "design",
  "extend"
]);
function isFeatureRequest(task) {
  const words = task.toLowerCase().split(/\s+/);
  return words.some((w) => FEATURE_REQUEST_WORDS.has(w));
}
class ContextBuilder {
  constructor(db, resolver, vectors, projectRoot) {
    this.db = db;
    this.resolver = resolver;
    this.vectors = vectors;
    this.projectRoot = projectRoot;
  }
  /**
   * Find relevant nodes for the given query using hybrid search.
   *
   * Pipeline:
   * 1. Extract symbol tokens from query
   * 2. Exact name lookup (priority: 'exact')
   * 3. Semantic search via VectorManager (priority: 'semantic')
   * 4. Full-text search fallback (priority: 'fts')
   * 5. Merge and deduplicate by node ID
   * 6. Resolve import nodes to their definitions via 'imports' edge
   * 7. Exclude 'import' and 'export' node kinds
   * 8. Priority-based trimming when exceeding maxNodes
   */
  async findRelevantContext(query, opts) {
    const maxNodes = opts?.maxNodes ?? DEFAULT_MAX_NODES;
    const tokens = (0, import_query_utils.extractSearchTerms)(query);
    (0, import_errors.logDebug)("ContextBuilder.findRelevantContext", { query, tokens });
    const ranked = [];
    const seen = /* @__PURE__ */ new Map();
    for (const token of tokens) {
      const nodes = this.db.findNodesByExactName(token);
      for (const node of nodes) {
        if (!seen.has(node.id)) {
          seen.set(node.id, "exact");
          ranked.push({ node, priority: "exact" });
        }
      }
    }
    if (this.vectors.isInitialized()) {
      const semanticNodes = await this.vectors.search(query, maxNodes);
      for (const node of semanticNodes) {
        if (!seen.has(node.id)) {
          seen.set(node.id, "semantic");
          ranked.push({ node, priority: "semantic" });
        }
      }
    }
    const ftsNodes = this.db.searchNodes(query, { limit: maxNodes });
    for (const node of ftsNodes) {
      if (!seen.has(node.id)) {
        seen.set(node.id, "fts");
        ranked.push({ node, priority: "fts" });
      }
    }
    const importNodeIds = ranked.filter((r) => r.node.kind === "import").map((r) => r.node.id);
    if (importNodeIds.length > 0) {
      const edges = this.db.getEdgesForNodes(importNodeIds);
      for (const edge of edges) {
        if (edge.kind === "imports" && importNodeIds.includes(edge.source)) {
          const targetNode = this.db.getNode(edge.target);
          if (targetNode && !seen.has(targetNode.id)) {
            const importPriority = seen.get(edge.source) ?? "fts";
            seen.set(targetNode.id, importPriority);
            ranked.push({ node: targetNode, priority: importPriority });
          }
        }
      }
    }
    const filtered = ranked.filter((r) => !EXCLUDED_NODE_KINDS.has(r.node.kind));
    if (filtered.length <= maxNodes) {
      return filtered.map((r) => r.node);
    }
    const exact = filtered.filter((r) => r.priority === "exact");
    const semantic = filtered.filter((r) => r.priority === "semantic");
    const fts = filtered.filter((r) => r.priority === "fts");
    const trimmed = [];
    for (const group of [exact, semantic, fts]) {
      for (const item of group) {
        if (trimmed.length >= maxNodes) break;
        trimmed.push(item);
      }
      if (trimmed.length >= maxNodes) break;
    }
    return trimmed.map((r) => r.node);
  }
  /**
   * Build a full TaskContext for the given task description.
   * Finds relevant nodes, gathers their edges and source snippets, and returns
   * the structured context used by MCP tools and the query API.
   */
  async buildTaskContext(task, opts) {
    const maxNodes = opts?.maxNodes ?? DEFAULT_MAX_NODES;
    const nodes = await this.findRelevantContext(task, { maxNodes });
    const entryPoints = nodes.slice(0, Math.ceil(maxNodes / 2));
    const relatedNodes = nodes.slice(Math.ceil(maxNodes / 2));
    const edges = this.db.getEdgesForNodes(nodes.map((n) => n.id));
    const codeSnippets = /* @__PURE__ */ new Map();
    if (opts?.includeCode !== false) {
      for (const node of nodes.slice(0, 10)) {
        const src = this._readNodeSource(node);
        if (src) codeSnippets.set(node.id, src);
      }
    }
    const hint = isFeatureRequest(task) ? " (feature request \u2014 showing existing patterns and extension points)" : "";
    return {
      task,
      entryPoints,
      relatedNodes,
      edges,
      codeSnippets,
      summary: `Found ${entryPoints.length} entry points and ${relatedNodes.length} related symbols for: "${task}"${hint}`
    };
  }
  _readNodeSource(node) {
    const absPath = (0, import_utils.validatePathWithinRoot)(path.join(this.projectRoot, node.filePath), this.projectRoot);
    if (!absPath) return null;
    try {
      return fs.readFileSync(absPath, "utf8").split("\n").slice(node.startLine - 1, node.endLine).join("\n");
    } catch {
      return null;
    }
  }
  /**
   * Build a formatted context string for the given query.
   *
   * Each node is formatted as:
   *   [filePath:line] kind name
   *   ```
   *   {snippet}
   *   ```
   *
   * Returns NO_RESULTS_MESSAGE when no nodes are found.
   */
  async build(query, opts) {
    const maxCodeBlocks = opts?.maxCodeBlocks ?? DEFAULT_MAX_CODE_BLOCKS;
    const maxCodeBlockSize = opts?.maxCodeBlockSize ?? DEFAULT_MAX_CODE_BLOCK_SIZE;
    const nodes = await this.findRelevantContext(query, opts);
    if (nodes.length === 0) {
      return NO_RESULTS_MESSAGE;
    }
    const parts = [];
    const limit = Math.min(nodes.length, maxCodeBlocks);
    for (let i = 0; i < limit; i++) {
      const node = nodes[i];
      const snippet = getSnippet(node, maxCodeBlockSize);
      parts.push(`[${node.filePath}:${node.startLine}] ${node.kind} ${node.name}
\`\`\`
${snippet}
\`\`\``);
    }
    return parts.join("\n\n");
  }
}
function getSnippet(node, maxSize) {
  const raw = node.signature ?? node.docstring ?? node.name;
  return raw.length > maxSize ? raw.slice(0, maxSize) : raw;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  ContextBuilder
});
//# sourceMappingURL=index.js.map
