import {
  isPlausibleEntityName,
  linkMemoryToEntity,
  upsertEntity,
  upsertRelation
} from "./chunk-Z6RT5BUO.js";
import {
  getMemory
} from "./chunk-UBPGOOAN.js";

// src/graph/extractor.ts
var PATH_RE = /(?:^|[\s`'"(])((?:\.{0,2}\/)?(?:[A-Za-z0-9_.-]+\/){1,}[A-Za-z0-9_.-]+\.[A-Za-z0-9]{1,8})/g;
var FUNC_RE = /`([A-Za-z_][\w.]{1,}\(\))`/g;
var SCOPED_PACKAGE_RE = /`(@[a-z0-9_-]+\/[a-z0-9_.-]+|[a-z][a-z0-9_-]*(?:-[a-z0-9]+)+)`/g;
var SIMPLE_PACKAGE_RE = /`([a-z][a-z0-9_]{2,30})`/g;
var PACKAGE_STOPWORDS = /* @__PURE__ */ new Set([
  "the",
  "and",
  "but",
  "for",
  "use",
  "uses",
  "used",
  "with",
  "from",
  "this",
  "that",
  "have",
  "has",
  "true",
  "false",
  "null",
  "void",
  "data",
  "code",
  "test",
  "tests",
  "build",
  "run",
  "runs",
  "name",
  "type",
  "types",
  "main",
  "yes",
  "no",
  "ok",
  "all",
  "any",
  "new",
  "old",
  "more",
  "less",
  "always",
  "never",
  "todo",
  "fixme",
  "note",
  "warn",
  "info",
  "error",
  "ts",
  "js"
]);
var CLI_RE = /\b(npm|pnpm|yarn|bun|cargo|go|uv|pip|node|deno|claude|codex|cursor|recall|brew|gh|git)\s+([a-z][a-z-]{0,30})\b/g;
var URL_RE = /\bhttps?:\/\/[^\s<>"'`)]+/g;
var RELATION_CUES = [
  { rx: /\b(?:replaces?|replaced|supersedes?|superseded|deprecates?|deprecated)\b/i, relation: "replaces" },
  { rx: /\b(?:conflicts? with|incompatible with|clashes? with)\b/i, relation: "conflicts_with" },
  { rx: /\b(?:tested by|covered by)\b/i, relation: "tested_by" },
  { rx: /\b(?:depends on|requires|required by)\b/i, relation: "depends_on" },
  { rx: /\b(?:uses?|used|relies on|via)\b/i, relation: "uses" },
  { rx: /\b(?:references?|see also|cf\.)\b/i, relation: "references" },
  { rx: /\b(?:part of|inside|under)\b/i, relation: "part_of" }
];
function heuristic(text) {
  const entities = /* @__PURE__ */ new Map();
  const relations = [];
  const push = (kind, name, weight = 1) => {
    if (!isPlausibleEntityName(kind, name)) return;
    const key = `${kind}|${name}`;
    const existing = entities.get(key);
    if (existing) {
      existing.weight = (existing.weight ?? 1) + weight;
    } else {
      entities.set(key, { kind, name, source: "heuristic", weight });
    }
  };
  for (const m of text.matchAll(PATH_RE)) {
    const raw = m[1];
    if (raw.includes(".") && !raw.endsWith("/")) {
      push("file", raw);
    } else {
      push("repo_path", raw);
    }
  }
  for (const m of text.matchAll(FUNC_RE)) {
    push("function", m[1]);
  }
  for (const m of text.matchAll(SCOPED_PACKAGE_RE)) {
    push("library", m[1]);
  }
  for (const m of text.matchAll(SIMPLE_PACKAGE_RE)) {
    const candidate = m[1];
    if (PACKAGE_STOPWORDS.has(candidate)) continue;
    push("library", candidate, 0.7);
  }
  for (const m of text.matchAll(CLI_RE)) {
    push("tool", m[1]);
    push("command", `${m[1]} ${m[2]}`);
  }
  for (const m of text.matchAll(URL_RE)) {
    push("url", m[0]);
  }
  const sentences = text.split(/(?<=[.!?])\s+|\n+/);
  for (const sentence of sentences) {
    const cue = RELATION_CUES.find((c) => c.rx.test(sentence));
    if (!cue) continue;
    const mentioned = [];
    for (const ent of entities.values()) {
      const idx = sentence.indexOf(ent.name);
      if (idx >= 0) mentioned.push({ kind: ent.kind, name: ent.name, idx });
    }
    if (mentioned.length < 2) continue;
    mentioned.sort((a, b) => a.idx - b.idx);
    relations.push({
      source: { kind: mentioned[0].kind, name: mentioned[0].name },
      target: { kind: mentioned[1].kind, name: mentioned[1].name },
      relation: cue.relation,
      confidence: 0.5
    });
  }
  return { entities: Array.from(entities.values()), relations };
}
function mergeExtractions(...passes) {
  const entities = /* @__PURE__ */ new Map();
  for (const pass of passes) {
    for (const ent of pass.entities) {
      const key = `${ent.kind}|${ent.name}`;
      const prev = entities.get(key);
      if (!prev || prev.source === "heuristic" && ent.source === "llm") {
        entities.set(key, ent);
      }
    }
  }
  const relations = /* @__PURE__ */ new Map();
  for (const pass of passes) {
    for (const rel of pass.relations) {
      const key = `${rel.source.kind}|${rel.source.name}->${rel.relation}->${rel.target.kind}|${rel.target.name}`;
      const prev = relations.get(key);
      if (!prev || rel.confidence > prev.confidence) {
        relations.set(key, rel);
      }
    }
  }
  return {
    entities: Array.from(entities.values()),
    relations: Array.from(relations.values())
  };
}

// src/graph/ingest.ts
function ingestMemoryHeuristic(db, memory) {
  const extraction = heuristic(memory.text);
  return persistExtraction(db, memory, extraction);
}
function ingestMemoryExtraction(db, memory, extraction) {
  return persistExtraction(db, memory, extraction);
}
function mergeAndIngest(db, memory, passes) {
  return persistExtraction(db, memory, mergeExtractions(...passes));
}
function persistExtraction(db, memory, extraction) {
  const entityIndex = /* @__PURE__ */ new Map();
  let entitiesTouched = 0;
  for (const ent of extraction.entities) {
    const row = upsertEntity(db, {
      kind: ent.kind,
      name: ent.name,
      repo: memory.repo,
      first_seen_memory_id: memory.id
    });
    entityIndex.set(`${row.kind}|${row.normalized_name}`, row);
    linkMemoryToEntity(db, {
      memory_id: memory.id,
      entity_id: row.id,
      source: ent.source,
      weight: ent.weight
    });
    entitiesTouched++;
  }
  let relationsTouched = 0;
  const lookupRow = (kind, name) => {
    for (const row of entityIndex.values()) {
      if (row.kind === kind && row.name === name) return row;
    }
    return void 0;
  };
  for (const rel of extraction.relations) {
    const src = lookupRow(rel.source.kind, rel.source.name) ?? upsertEntity(db, { kind: rel.source.kind, name: rel.source.name, repo: memory.repo });
    const tgt = lookupRow(rel.target.kind, rel.target.name) ?? upsertEntity(db, { kind: rel.target.kind, name: rel.target.name, repo: memory.repo });
    if (src.id === tgt.id) continue;
    upsertRelation(db, {
      source_entity_id: src.id,
      target_entity_id: tgt.id,
      relation_type: rel.relation,
      source_memory_id: memory.id,
      confidence: rel.confidence
    });
    relationsTouched++;
  }
  return {
    memory_id: memory.id,
    entities_created_or_updated: entitiesTouched,
    relations_created_or_updated: relationsTouched
  };
}
function safeIngestMemoryById(db, memoryId) {
  try {
    const mem = getMemory(db, memoryId);
    if (!mem) return null;
    return ingestMemoryHeuristic(db, { id: mem.id, text: mem.text, repo: mem.repo });
  } catch (err) {
    console.error(`[recall] graph ingest failed for ${memoryId}:`, err);
    return null;
  }
}

export {
  ingestMemoryHeuristic,
  ingestMemoryExtraction,
  mergeAndIngest,
  safeIngestMemoryById
};
//# sourceMappingURL=chunk-QSJKGEID.js.map