"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 resolution_exports = {};
__export(resolution_exports, {
  ReferenceResolver: () => ReferenceResolver
});
module.exports = __toCommonJS(resolution_exports);
var import_name_matcher = require("./name-matcher");
var import_errors = require("../errors");
class ReferenceResolver {
  constructor(db, config) {
    // Warm caches for O(1) lookup
    this.nameCache = /* @__PURE__ */ new Map();
    this.qualifiedNameCache = /* @__PURE__ */ new Map();
    this.kindCache = /* @__PURE__ */ new Map();
    this.lowerNameCache = /* @__PURE__ */ new Map();
    this.nodeByIdCache = /* @__PURE__ */ new Map();
    // namespace prefix → first non-import node ID (for C#/Java namespace-level imports)
    this.namespacePrefixCache = /* @__PURE__ */ new Map();
    this.cacheWarmed = false;
    this.db = db;
    this.config = config;
  }
  /**
   * Pre-load all nodes into Maps for O(1) lookup.
   * Must be called before resolveUnresolvedRefs() for best performance.
   */
  warmCaches() {
    this.nameCache.clear();
    this.qualifiedNameCache.clear();
    this.kindCache.clear();
    this.lowerNameCache.clear();
    this.nodeByIdCache.clear();
    this.namespacePrefixCache.clear();
    const rawDb = this.db.db;
    const rows = rawDb.all("SELECT * FROM nodes");
    for (const row of rows) {
      const node = this._rowToNode(row);
      const nameList = this.nameCache.get(node.name) ?? [];
      nameList.push(node);
      this.nameCache.set(node.name, nameList);
      if (node.qualifiedName) {
        this.qualifiedNameCache.set(node.qualifiedName, node);
      }
      const kindList = this.kindCache.get(node.kind) ?? [];
      kindList.push(node);
      this.kindCache.set(node.kind, kindList);
      const lower = node.name.toLowerCase();
      const lowerList = this.lowerNameCache.get(lower) ?? [];
      lowerList.push(node);
      this.lowerNameCache.set(lower, lowerList);
      this.nodeByIdCache.set(node.id, node);
    }
    for (const [qualName, node] of this.qualifiedNameCache) {
      if (node.kind === "class" || node.kind === "interface" || node.kind === "namespace" || node.kind === "module") {
        const parts = qualName.split(".");
        for (let k = 1; k < parts.length; k++) {
          const prefix = parts.slice(0, k).join(".");
          if (!this.namespacePrefixCache.has(prefix)) {
            this.namespacePrefixCache.set(prefix, node.id);
          }
        }
      }
    }
    this.cacheWarmed = true;
    (0, import_errors.logDebug)(`ReferenceResolver: warmed caches with ${rows.length} nodes`);
  }
  /**
   * Resolve all pending unresolved references.
   * Warms caches first, then processes all pending refs.
   * Returns a ResolutionResult with counts and duration.
   */
  async resolveAll(onProgress) {
    const start = Date.now();
    this.warmCaches();
    const resolvedCount = await this.resolveUnresolvedRefs(onProgress);
    const rawDb = this.db.db;
    const remaining = rawDb.get("SELECT COUNT(*) as c FROM unresolved_refs")?.c ?? 0;
    const total = resolvedCount + remaining;
    const durationMs = Date.now() - start;
    (0, import_errors.logDebug)(`ReferenceResolver: resolved ${resolvedCount}/${total} refs in ${durationMs}ms`);
    return {
      resolved: resolvedCount,
      unresolved: remaining,
      total,
      durationMs
    };
  }
  /**
   * Process all pending unresolved references using warm caches.
   * Returns the count of newly resolved edges.
   */
  async resolveUnresolvedRefs(onProgress) {
    if (!this.cacheWarmed) {
      this.warmCaches();
    }
    const rawDb = this.db.db;
    const refs = rawDb.all("SELECT * FROM unresolved_refs");
    let resolved = 0;
    const total = refs.length;
    const BATCH_SIZE = 1e3;
    const FUZZY_ALL_NODES_LIMIT = 2e5;
    const skipFullFuzzy = this.nodeByIdCache.size > FUZZY_ALL_NODES_LIMIT;
    if (skipFullFuzzy) {
      (0, import_errors.logDebug)(`ReferenceResolver: skipping full-scan fuzzy (${this.nodeByIdCache.size} nodes exceeds ${FUZZY_ALL_NODES_LIMIT} limit)`);
    }
    for (let batchStart = 0; batchStart < refs.length; batchStart += BATCH_SIZE) {
      const batchEnd = Math.min(batchStart + BATCH_SIZE, refs.length);
      rawDb.run("BEGIN");
      try {
        for (let i = batchStart; i < batchEnd; i++) {
          const ref = refs[i];
          onProgress?.(i + 1, total);
          const {
            id: refId,
            source_id: sourceId,
            ref_name: refName,
            ref_kind: refKind,
            file_path: filePath,
            line,
            column
          } = ref;
          const attemptedStrategies = [];
          let targetId = null;
          if (refKind === "import") {
            attemptedStrategies.push("import-path");
            targetId = this._resolveImportPath(refName, filePath);
          } else if (refKind === "extends" || refKind === "implements") {
            attemptedStrategies.push("type-name");
            targetId = this._resolveTypeName(refName);
          } else {
            attemptedStrategies.push("framework");
            attemptedStrategies.push("qualified");
            const qualifiedNode = this.qualifiedNameCache.get(refName);
            if (qualifiedNode) {
              targetId = qualifiedNode.id;
            }
            if (!targetId && refName.includes(".")) {
              attemptedStrategies.push("method");
              const methodPart = refName.slice(refName.lastIndexOf(".") + 1);
              const methodCandidates = this.nameCache.get(methodPart) ?? [];
              if (methodCandidates.length > 0) {
                targetId = methodCandidates[0].id;
              }
            }
            if (!targetId) {
              attemptedStrategies.push("exact");
              const exactCandidates = this.nameCache.get(refName) ?? [];
              if (exactCandidates.length > 0) {
                targetId = exactCandidates[0].id;
              }
            }
            if (!targetId) {
              attemptedStrategies.push("fuzzy");
              const threshold = this.config.fuzzyResolutionThreshold ?? 0.5;
              const lowerRef = refName.toLowerCase();
              const fuzzyCandidates = this.lowerNameCache.get(lowerRef) ?? [];
              if (fuzzyCandidates.length > 0) {
                const match = (0, import_name_matcher.matchReference)(refName, fuzzyCandidates, threshold);
                if (match) {
                  targetId = match.nodeId;
                }
              }
              if (!targetId && !skipFullFuzzy) {
                const allCandidates = [...this.nodeByIdCache.values()];
                const match = (0, import_name_matcher.matchReference)(refName, allCandidates, threshold);
                if (match) {
                  targetId = match.nodeId;
                }
              }
            }
          }
          if (targetId) {
            const edgeKind = refKind === "import" ? "imports" : refKind === "extends" ? "extends" : refKind === "implements" ? "implements" : "calls";
            const edge = {
              source: sourceId,
              target: targetId,
              kind: edgeKind,
              line: line ?? void 0,
              column: column ?? void 0
            };
            this.db.insertEdge(edge);
            rawDb.run("DELETE FROM unresolved_refs WHERE id = ?", [refId]);
            resolved++;
          } else {
            const strategiesJson = JSON.stringify(attemptedStrategies);
            try {
              rawDb.run(
                "UPDATE unresolved_refs SET attempted_strategies = ? WHERE id = ?",
                [strategiesJson, refId]
              );
            } catch {
              (0, import_errors.logWarn)(`ReferenceResolver: failed to record attempted strategies for ref ${refId}`);
            }
          }
        }
        rawDb.run("COMMIT");
      } catch (err) {
        try {
          rawDb.run("ROLLBACK");
        } catch {
        }
        (0, import_errors.logWarn)(`ReferenceResolver: batch ${batchStart}-${batchEnd} failed: ${err instanceof Error ? err.message : String(err)}`);
      }
    }
    return resolved;
  }
  /**
   * Invalidate cache entries for all nodes belonging to the given file.
   * Call this when a file is re-indexed to prevent stale cache hits.
   */
  invalidateFile(filePath) {
    const nodesToRemove = [];
    for (const node of this.nodeByIdCache.values()) {
      if (node.filePath === filePath) {
        nodesToRemove.push(node);
      }
    }
    for (const node of nodesToRemove) {
      this.nodeByIdCache.delete(node.id);
      const nameList = this.nameCache.get(node.name);
      if (nameList) {
        const filtered = nameList.filter((n) => n.id !== node.id);
        if (filtered.length === 0) {
          this.nameCache.delete(node.name);
        } else {
          this.nameCache.set(node.name, filtered);
        }
      }
      if (node.qualifiedName) {
        const cached = this.qualifiedNameCache.get(node.qualifiedName);
        if (cached?.id === node.id) {
          this.qualifiedNameCache.delete(node.qualifiedName);
        }
      }
      const kindList = this.kindCache.get(node.kind);
      if (kindList) {
        const filtered = kindList.filter((n) => n.id !== node.id);
        if (filtered.length === 0) {
          this.kindCache.delete(node.kind);
        } else {
          this.kindCache.set(node.kind, filtered);
        }
      }
      const lower = node.name.toLowerCase();
      const lowerList = this.lowerNameCache.get(lower);
      if (lowerList) {
        const filtered = lowerList.filter((n) => n.id !== node.id);
        if (filtered.length === 0) {
          this.lowerNameCache.delete(lower);
        } else {
          this.lowerNameCache.set(lower, filtered);
        }
      }
    }
    (0, import_errors.logDebug)(`ReferenceResolver: invalidated ${nodesToRemove.length} cache entries for ${filePath}`);
  }
  // ── Private helpers ────────────────────────────────────────────────────────
  /**
   * Resolve an import path to a node ID.
   *
   * Handles three cases:
   *  1. Relative paths (JS/TS/Go/…) — resolved via the file tree.
   *  2. Fully-qualified type imports (Java `import com.example.Foo`) — exact qualifiedName lookup,
   *     then name+namespace-prefix match.
   *  3. Namespace-level imports (C# `using MyApp.Services`) — namespace node lookup, then
   *     namespace prefix cache (O(1)) to find any class in that namespace.
   */
  _resolveImportPath(importPath, sourceFilePath) {
    if (importPath.startsWith(".")) {
      const sourceDir = sourceFilePath.replace(/[^/]+$/, "");
      const segments = (sourceDir + importPath).split("/");
      const normalized = [];
      for (const seg of segments) {
        if (seg === "..") normalized.pop();
        else if (seg !== ".") normalized.push(seg);
      }
      const basePath = normalized.join("/");
      const candidates = [
        basePath,
        basePath + ".ts",
        basePath + ".tsx",
        basePath + ".js",
        basePath + ".jsx",
        basePath + "/index.ts",
        basePath + "/index.tsx",
        basePath + "/index.js"
      ];
      const rawDb = this.db.db;
      for (const candidate of candidates) {
        const row = rawDb.get("SELECT id FROM nodes WHERE file_path = ? LIMIT 1", [candidate]);
        if (row) return row.id;
      }
      return null;
    }
    if (importPath.endsWith(".*") || importPath.endsWith("*")) {
      const ns = importPath.replace(/\.\*$/, "").replace(/\*$/, "");
      return this.namespacePrefixCache.get(ns) ?? null;
    }
    const exact = this.qualifiedNameCache.get(importPath);
    if (exact) return exact.id;
    const lastDot = importPath.lastIndexOf(".");
    if (lastDot > 0) {
      const typeName = importPath.slice(lastDot + 1);
      const namespace = importPath.slice(0, lastDot);
      const candidates = this.nameCache.get(typeName) ?? [];
      for (const candidate of candidates) {
        if (candidate.qualifiedName && candidate.qualifiedName.startsWith(namespace)) {
          return candidate.id;
        }
      }
    }
    const nsCandidates = this.nameCache.get(importPath.split(".").pop() ?? "") ?? [];
    for (const candidate of nsCandidates) {
      if (candidate.kind === "namespace" && candidate.name === importPath) return candidate.id;
    }
    return this.namespacePrefixCache.get(importPath) ?? null;
  }
  /**
   * Resolve a bare type name to a node ID.
   * Used for extends/implements refs where we have only the unqualified type name.
   * Prefers class/interface/trait/protocol nodes over other kinds.
   */
  _resolveTypeName(name) {
    const candidates = this.nameCache.get(name) ?? [];
    if (candidates.length > 0) {
      const preferred = candidates.find(
        (n) => n.kind === "class" || n.kind === "interface" || n.kind === "trait" || n.kind === "protocol"
      );
      return (preferred ?? candidates[0]).id;
    }
    const qualified = this.qualifiedNameCache.get(name);
    if (qualified) return qualified.id;
    return null;
  }
  /**
   * Convert a raw DB row to a Node object.
   */
  _rowToNode(row) {
    return {
      id: row.id,
      kind: row.kind,
      name: row.name,
      qualifiedName: row.qualified_name,
      filePath: row.file_path,
      language: row.language,
      startLine: row.start_line,
      endLine: row.end_line,
      startColumn: row.start_column,
      endColumn: row.end_column,
      docstring: row.docstring ?? void 0,
      signature: row.signature ?? void 0,
      visibility: row.visibility ?? void 0,
      isExported: row.is_exported === 1,
      isAsync: row.is_async === 1,
      isStatic: row.is_static === 1,
      isAbstract: row.is_abstract === 1,
      decorators: row.decorators ? JSON.parse(row.decorators) : void 0,
      typeParameters: row.type_parameters ? JSON.parse(row.type_parameters) : void 0,
      updatedAt: row.updated_at
    };
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  ReferenceResolver
});
//# sourceMappingURL=index.js.map
