import { getSession } from "../lib/neo4j.js";
import { embedBatch } from "../lib/embeddings.js";
import { buildTextRepresentation } from "./memory-write.js";
import neo4j from "neo4j-driver";
export async function memoryReindex(accountId) {
    const session = getSession();
    const batchSize = 50;
    let nodesProcessed = 0;
    let nodesSkipped = 0;
    let scopeBackfilled = 0;
    const errors = [];
    try {
        const countResult = await session.run(`MATCH (n) WHERE n.accountId = $accountId AND n.embedding IS NULL RETURN count(n) AS total`, { accountId });
        const total = countResult.records[0].get("total").toNumber();
        if (total === 0) {
            console.log("memory-reindex: all nodes have embeddings, nothing to do");
            return { nodesProcessed: 0, nodesSkipped: 0, scopeBackfilled: 0, errors: [] };
        }
        console.log(`memory-reindex: ${total} nodes need embedding backfill`);
        // Each batch sets embeddings on matched nodes, removing them from the
        // IS NULL filter. SKIP 0 is correct — processed nodes naturally leave
        // the result set, so the next batch always starts from the front.
        for (;;) {
            const batchResult = await session.run(`MATCH (n)
         WHERE n.accountId = $accountId AND n.embedding IS NULL
         RETURN elementId(n) AS nodeId, labels(n) AS nodeLabels, properties(n) AS props
         LIMIT $batchSize`, { accountId, batchSize: neo4j.int(batchSize) });
            if (batchResult.records.length === 0)
                break;
            const texts = [];
            const nodeIds = [];
            const nodeScopeMissing = [];
            for (const record of batchResult.records) {
                const nodeId = record.get("nodeId");
                const labels = record.get("nodeLabels");
                const props = record.get("props");
                // Strip accountId from props — buildTextRepresentation doesn't
                // exclude it, and it shouldn't be part of the embedding text.
                const { accountId: _acct, ...contentProps } = props;
                const text = buildTextRepresentation(labels, contentProps);
                if (text.length < 5) {
                    nodesSkipped++;
                    continue;
                }
                texts.push(text);
                nodeIds.push(nodeId);
                nodeScopeMissing.push(props.scope === null || props.scope === undefined);
            }
            if (texts.length > 0) {
                try {
                    const embeddings = await embedBatch(texts);
                    for (let i = 0; i < nodeIds.length; i++) {
                        const setScope = nodeScopeMissing[i];
                        if (setScope) {
                            await session.run(`MATCH (n) WHERE elementId(n) = $nodeId SET n.embedding = $embedding, n.scope = "shared"`, { nodeId: nodeIds[i], embedding: embeddings[i] });
                            scopeBackfilled++;
                        }
                        else {
                            await session.run(`MATCH (n) WHERE elementId(n) = $nodeId SET n.embedding = $embedding`, { nodeId: nodeIds[i], embedding: embeddings[i] });
                        }
                    }
                    nodesProcessed += texts.length;
                }
                catch (err) {
                    errors.push(`Batch at offset ${nodesProcessed}: ${err instanceof Error ? err.message : String(err)}`);
                    break; // Stop on batch error to avoid infinite retry of the same failing batch
                }
            }
            else {
                // All records in this batch were skipped (text < 5 chars).
                // Since these nodes still have embedding IS NULL, the next LIMIT
                // query would return the same nodes — break to avoid an infinite loop.
                break;
            }
            console.log(`memory-reindex: batch processed (${nodesProcessed + nodesSkipped}/${total} complete)`);
        }
        console.log(`memory-reindex: complete — ${nodesProcessed} processed, ${nodesSkipped} skipped, ${scopeBackfilled} scope backfilled, ${errors.length} errors`);
        return { nodesProcessed, nodesSkipped, scopeBackfilled, errors };
    }
    finally {
        await session.close();
    }
}
//# sourceMappingURL=memory-reindex.js.map