/**
 * Auto Knowledge Graph Expansion
 *
 * When a new entity is created, find semantically similar existing entities
 * and automatically create `relates_to` relations between them.
 */
/**
 * Automatically expand the knowledge graph by linking a newly-created entity
 * to semantically similar existing entities via `relates_to` relations.
 *
 * Non-fatal: errors are caught and silently ignored so callers are never blocked.
 */
export async function autoExpandGraph(client, entityId, title, content, _tags, workspaceId, projectId, maxRelations = 5) {
    try {
        // Build a search query from the title + first 200 chars of content
        const contentSnippet = content.slice(0, 200).trim();
        const query = [title, contentSnippet].filter(Boolean).join(" ");
        let candidates = [];
        // First attempt
        const { entities } = await client.searchMemoryEntities(workspaceId, query, {
            project_id: projectId,
            limit: 20,
        });
        candidates = entities
            .filter((e) => e.id !== entityId)
            .slice(0, maxRelations);
        // Retry once after 2s if no candidates found (handles embedding generation race)
        if (candidates.length === 0) {
            await new Promise((resolve) => setTimeout(resolve, 2000));
            const retry = await client.searchMemoryEntities(workspaceId, query, {
                project_id: projectId,
                limit: 20,
            });
            candidates = retry.entities
                .filter((e) => e.id !== entityId)
                .slice(0, maxRelations);
        }
        let relationsCreated = 0;
        for (const candidate of candidates) {
            try {
                await client.createMemoryRelation({
                    source_id: entityId,
                    target_id: candidate.id,
                    relation_type: "relates_to",
                    confidence: 0.6,
                });
                relationsCreated++;
            }
            catch (err) {
                // Silently skip 409 Conflict (relation already exists) and other errors
                const status = err?.status;
                if (status !== 409) {
                    // Non-409 errors are still non-fatal; just skip
                }
            }
        }
        return { relationsCreated };
    }
    catch {
        // Never block callers due to graph expansion failures
        return { relationsCreated: 0 };
    }
}
/**
 * Find entities semantically similar to the given title+content using
 * hybrid FTS+vector search. Returns entities sorted by relevance.
 */
export async function findSimilarEntities(client, title, content, workspaceId, options) {
    const contentSnippet = content.slice(0, 200).trim();
    const query = [title, contentSnippet].filter(Boolean).join(" ");
    try {
        const { entities } = await client.searchMemoryEntities(workspaceId, query, {
            project_id: options?.projectId,
            limit: options?.limit ?? 20,
            type: options?.type,
        });
        const minScore = options?.minRrfScore ?? 0;
        const excludeSet = new Set(options?.excludeIds || []);
        return entities.filter((e) => {
            if (excludeSet.has(e.id))
                return false;
            if (minScore > 0 && (e.rrf_score ?? 0) < minScore)
                return false;
            return true;
        });
    }
    catch {
        return [];
    }
}
/**
 * Causal lookup table: maps an entity type to the target types it should
 * be linked to, along with the relation type and direction.
 *
 * "forward" means source=newEntity → target=match.
 * "reverse" means source=match → target=newEntity.
 */
const CAUSAL_LOOKUP = [
    {
        sourceType: "error",
        targetType: "solution",
        relation: "resolved_by",
        direction: "forward",
    },
    {
        sourceType: "solution",
        targetType: "error",
        relation: "resolved_by",
        direction: "reverse",
    },
    {
        sourceType: "lesson",
        targetType: "error",
        relation: "learned_from",
        direction: "forward",
    },
];
/**
 * Link a newly created entity to causally-related entities of *other* types.
 *
 * For example, a new `error` entity gets linked to similar `solution` entities
 * via `resolved_by`, enabling agents to discover "what solved similar problems."
 *
 * Non-fatal: all errors are caught silently.
 */
export async function linkCrossTypeNeighbors(client, entityId, entityType, title, content, workspaceId, projectId) {
    const rules = CAUSAL_LOOKUP.filter((r) => r.sourceType === entityType);
    if (rules.length === 0)
        return { relationsCreated: 0 };
    let relationsCreated = 0;
    for (const rule of rules) {
        try {
            const matches = await findSimilarEntities(client, title, content, workspaceId, {
                projectId,
                limit: 10,
                minRrfScore: 0.04,
                excludeIds: [entityId],
                type: rule.targetType,
            });
            // Cap at 3 matches per target type
            for (const match of matches.slice(0, 3)) {
                const sourceId = rule.direction === "forward" ? entityId : match.id;
                const targetId = rule.direction === "forward" ? match.id : entityId;
                try {
                    await client.createMemoryRelation({
                        source_id: sourceId,
                        target_id: targetId,
                        relation_type: rule.relation,
                        confidence: 0.65,
                    });
                    relationsCreated++;
                }
                catch {
                    // Skip duplicate/failed relations silently
                }
            }
        }
        catch {
            // Non-fatal: search failure for one rule shouldn't block others
        }
    }
    return { relationsCreated };
}
