import {
  and,
  entities,
  entityRelations,
  eq,
  inArray,
  like,
  memoryEntities,
  or,
  sql
} from "./chunk-QDA5M3AJ.js";

// src/graph/store.ts
import { randomUUID } from "crypto";

// src/graph/normalize.ts
function normalizeName(kind, raw) {
  const trimmed = raw.trim();
  switch (kind) {
    case "file":
    case "repo_path":
      return trimmed.replace(/\\/g, "/").replace(/^[./]+/, "").replace(/\/+$/, "").toLowerCase();
    case "function":
      return trimmed.replace(/\(.*\)/, "").replace(/\s+/g, "").toLowerCase();
    case "library":
    case "tool":
    case "command":
      return trimmed.toLowerCase();
    case "url":
      try {
        const u = new URL(trimmed);
        return `${u.hostname.toLowerCase()}${u.pathname.replace(/\/+$/, "")}`;
      } catch {
        return trimmed.toLowerCase();
      }
    case "concept":
    default:
      return trimmed.toLowerCase().replace(/\s+/g, " ");
  }
}
function isPlausibleEntityName(kind, name) {
  const n = name.trim();
  if (n.length === 0 || n.length > 200) return false;
  if (/^[0-9]+$/.test(n)) return false;
  if (kind === "concept" && n.split(/\s+/).length > 8) return false;
  if ((kind === "library" || kind === "tool") && n.length < 2) return false;
  return true;
}

// src/graph/store.ts
function upsertEntity(db, input) {
  const normalized = normalizeName(input.kind, input.name);
  const repo = input.repo ?? null;
  const now = (/* @__PURE__ */ new Date()).toISOString();
  const existing = findEntityByNormalized(db, input.kind, normalized, repo);
  if (existing) {
    db.update(entities).set({
      mention_count: existing.mention_count + 1,
      updated_at: now,
      // Backfill description / first_seen if not previously set.
      description: existing.description ?? input.description ?? null,
      first_seen_memory_id: existing.first_seen_memory_id ?? input.first_seen_memory_id ?? null
    }).where(eq(entities.id, existing.id)).run();
    return {
      ...existing,
      mention_count: existing.mention_count + 1,
      updated_at: now,
      description: existing.description ?? input.description ?? null,
      first_seen_memory_id: existing.first_seen_memory_id ?? input.first_seen_memory_id ?? null
    };
  }
  const id = randomUUID();
  db.insert(entities).values({
    id,
    kind: input.kind,
    name: input.name.trim(),
    normalized_name: normalized,
    repo,
    description: input.description ?? null,
    first_seen_memory_id: input.first_seen_memory_id ?? null,
    mention_count: 1,
    created_at: now,
    updated_at: now
  }).run();
  return {
    id,
    kind: input.kind,
    name: input.name.trim(),
    normalized_name: normalized,
    repo,
    description: input.description ?? null,
    mention_count: 1,
    first_seen_memory_id: input.first_seen_memory_id ?? null,
    created_at: now,
    updated_at: now
  };
}
function findEntityByNormalized(db, kind, normalized, repo) {
  const row = db.select().from(entities).where(
    and(
      eq(entities.kind, kind),
      eq(entities.normalized_name, normalized),
      repo === null ? sql`${entities.repo} IS NULL` : eq(entities.repo, repo)
    )
  ).get();
  return row ?? null;
}
function getEntity(db, id) {
  const row = db.select().from(entities).where(eq(entities.id, id)).get();
  return row ?? null;
}
function listEntities(db, opts = {}) {
  const conditions = [];
  if (opts.repo) conditions.push(eq(entities.repo, opts.repo));
  if (opts.kind) conditions.push(eq(entities.kind, opts.kind));
  if (opts.search) {
    const needle = `%${opts.search.toLowerCase()}%`;
    conditions.push(
      or(
        like(entities.normalized_name, needle),
        like(sql`lower(${entities.name})`, needle)
      )
    );
  }
  const where = conditions.length === 1 ? conditions[0] : conditions.length > 1 ? and(...conditions) : void 0;
  const builder = where ? db.select().from(entities).where(where) : db.select().from(entities);
  return builder.orderBy(sql`${entities.mention_count} DESC`).limit(opts.limit ?? 200).offset(opts.offset ?? 0).all();
}
function linkMemoryToEntity(db, input) {
  const existing = db.select({ id: memoryEntities.id }).from(memoryEntities).where(
    and(
      eq(memoryEntities.memory_id, input.memory_id),
      eq(memoryEntities.entity_id, input.entity_id)
    )
  ).get();
  if (existing) return;
  db.insert(memoryEntities).values({
    id: randomUUID(),
    memory_id: input.memory_id,
    entity_id: input.entity_id,
    source: input.source,
    weight: input.weight ?? 1,
    created_at: (/* @__PURE__ */ new Date()).toISOString()
  }).run();
}
function listEntitiesForMemory(db, memoryId) {
  return db.select({
    id: entities.id,
    kind: entities.kind,
    name: entities.name,
    normalized_name: entities.normalized_name,
    repo: entities.repo,
    description: entities.description,
    mention_count: entities.mention_count,
    first_seen_memory_id: entities.first_seen_memory_id,
    created_at: entities.created_at,
    updated_at: entities.updated_at
  }).from(memoryEntities).innerJoin(entities, eq(entities.id, memoryEntities.entity_id)).where(eq(memoryEntities.memory_id, memoryId)).all();
}
function listMemoryIdsForEntity(db, entityId) {
  const rows = db.select({ memory_id: memoryEntities.memory_id }).from(memoryEntities).where(eq(memoryEntities.entity_id, entityId)).all();
  return rows.map((r) => r.memory_id);
}
function upsertRelation(db, input) {
  const sourceMem = input.source_memory_id ?? null;
  const existing = db.select().from(entityRelations).where(
    and(
      eq(entityRelations.source_entity_id, input.source_entity_id),
      eq(entityRelations.target_entity_id, input.target_entity_id),
      eq(entityRelations.relation_type, input.relation_type),
      sourceMem === null ? sql`${entityRelations.source_memory_id} IS NULL` : eq(entityRelations.source_memory_id, sourceMem)
    )
  ).get();
  if (existing) {
    if (input.confidence !== void 0 && input.confidence > existing.confidence) {
      db.update(entityRelations).set({ confidence: input.confidence }).where(eq(entityRelations.id, existing.id)).run();
      return { ...existing, confidence: input.confidence };
    }
    return existing;
  }
  const id = randomUUID();
  const now = (/* @__PURE__ */ new Date()).toISOString();
  db.insert(entityRelations).values({
    id,
    source_entity_id: input.source_entity_id,
    target_entity_id: input.target_entity_id,
    relation_type: input.relation_type,
    source_memory_id: sourceMem,
    confidence: input.confidence ?? 0.6,
    created_at: now
  }).run();
  return {
    id,
    source_entity_id: input.source_entity_id,
    target_entity_id: input.target_entity_id,
    relation_type: input.relation_type,
    source_memory_id: sourceMem,
    confidence: input.confidence ?? 0.6,
    created_at: now
  };
}
function neighborsOf(db, entityId, options = {}) {
  const maxHops = Math.max(1, Math.min(5, options.hops ?? 1));
  const visited = /* @__PURE__ */ new Set([entityId]);
  let frontier = [entityId];
  const collectedRelations = /* @__PURE__ */ new Map();
  for (let hop = 0; hop < maxHops; hop++) {
    if (frontier.length === 0) break;
    const next = [];
    const relationFilter = options.relationTypes;
    const outgoing = db.select().from(entityRelations).where(
      relationFilter && relationFilter.length > 0 ? and(
        inArray(entityRelations.source_entity_id, frontier),
        inArray(entityRelations.relation_type, relationFilter)
      ) : inArray(entityRelations.source_entity_id, frontier)
    ).all();
    const incoming = db.select().from(entityRelations).where(
      relationFilter && relationFilter.length > 0 ? and(
        inArray(entityRelations.target_entity_id, frontier),
        inArray(entityRelations.relation_type, relationFilter)
      ) : inArray(entityRelations.target_entity_id, frontier)
    ).all();
    for (const rel of [...outgoing, ...incoming]) {
      collectedRelations.set(rel.id, rel);
      for (const candidate of [rel.source_entity_id, rel.target_entity_id]) {
        if (!visited.has(candidate)) {
          visited.add(candidate);
          next.push(candidate);
        }
      }
    }
    frontier = next;
  }
  const entityRows = visited.size === 0 ? [] : db.select().from(entities).where(inArray(entities.id, Array.from(visited))).all();
  return {
    entities: entityRows,
    relations: Array.from(collectedRelations.values())
  };
}
function listAllRelations(db, opts = {}) {
  const limit = Math.min(Math.max(opts.limit ?? 2e3, 1), 1e4);
  if (!opts.repo) {
    return db.select().from(entityRelations).limit(limit).all();
  }
  const repoEntityIds = db.select({ id: entities.id }).from(entities).where(eq(entities.repo, opts.repo)).all().map((r) => r.id);
  if (repoEntityIds.length === 0) return [];
  const idSet = new Set(repoEntityIds);
  const all = db.select().from(entityRelations).where(
    and(
      inArray(entityRelations.source_entity_id, repoEntityIds),
      inArray(entityRelations.target_entity_id, repoEntityIds)
    )
  ).limit(limit).all();
  return all.filter(
    (r) => idSet.has(r.source_entity_id) && idSet.has(r.target_entity_id)
  );
}
function countEntities(db) {
  const row = db.select({ c: sql`count(*)` }).from(entities).get();
  return Number(row?.c ?? 0);
}
function countRelations(db) {
  const row = db.select({ c: sql`count(*)` }).from(entityRelations).get();
  return Number(row?.c ?? 0);
}

export {
  isPlausibleEntityName,
  upsertEntity,
  findEntityByNormalized,
  getEntity,
  listEntities,
  linkMemoryToEntity,
  listEntitiesForMemory,
  listMemoryIdsForEntity,
  upsertRelation,
  neighborsOf,
  listAllRelations,
  countEntities,
  countRelations
};
//# sourceMappingURL=chunk-Z6RT5BUO.js.map