import { getSession } from "../lib/neo4j.js";
import { embed, embedBatch } from "../lib/embeddings.js";
import { buildTextRepresentation } from "./memory-write.js";
import { withWriteEvent } from "../lib/log-ingest.js";
import { DOCUMENT_ARCHIVE_LABELS } from "../../../../../lib/graph-write/dist/index.js";
import neo4j from "neo4j-driver";
// Document/archive labels embed their bounded `summary`, never the raw body —
// a multi-KB body blob is exactly what aborted the old batch loop. Non-doc
// labels drop the heaviest blob fields; embeddings.ts' cap is the final
// backstop.
function reindexText(labels, props) {
    const isDoc = labels.some((l) => DOCUMENT_ARCHIVE_LABELS.has(l));
    if (isDoc && typeof props.summary === "string" && props.summary.length > 0) {
        return buildTextRepresentation(labels, { summary: props.summary });
    }
    const { body: _b, text: _t, ...rest } = props;
    return buildTextRepresentation(labels, rest);
}
export async function memoryReindex(accountId) {
    return withWriteEvent({ tool: "memory-reindex", accountId }, () => memoryReindexImpl(accountId));
}
async function memoryReindexImpl(accountId) {
    const session = getSession();
    const batchSize = 50;
    let nodesProcessed = 0;
    let nodesSkipped = 0;
    let scopeBackfilled = 0;
    const errors = [];
    // Skipped/failed nodes keep `embedding IS NULL`, so the LIMIT query would
    // re-return them forever. Exclude them explicitly so the loop drains and
    // terminates instead of spinning on a poison node.
    const excludeIds = new Set();
    const setEmbedding = async (nodeId, embedding, setScope) => {
        if (setScope) {
            await session.run(`MATCH (n) WHERE elementId(n) = $nodeId SET n.embedding = $embedding, n.scope = "shared"`, { nodeId, embedding });
            scopeBackfilled++;
        }
        else {
            await session.run(`MATCH (n) WHERE elementId(n) = $nodeId SET n.embedding = $embedding`, { nodeId, embedding });
        }
    };
    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");
            console.log(`[reindex] op=done processed=0 skipped=0`);
            return { nodesProcessed: 0, nodesSkipped: 0, scopeBackfilled: 0, errors: [] };
        }
        console.log(`memory-reindex: ${total} nodes need embedding backfill`);
        for (;;) {
            const batchResult = await session.run(`MATCH (n)
         WHERE n.accountId = $accountId AND n.embedding IS NULL
           AND NOT elementId(n) IN $excludeIds
         RETURN elementId(n) AS nodeId, labels(n) AS nodeLabels, properties(n) AS props
         LIMIT $batchSize`, { accountId, batchSize: neo4j.int(batchSize), excludeIds: [...excludeIds] });
            if (batchResult.records.length === 0)
                break;
            const rows = [];
            for (const record of batchResult.records) {
                const nodeId = record.get("nodeId");
                const labels = record.get("nodeLabels");
                const props = record.get("props");
                // Strip accountId — it should not be part of the embedding text.
                const { accountId: _acct, ...contentProps } = props;
                const text = reindexText(labels, contentProps);
                if (text.length < 5) {
                    nodesSkipped++;
                    excludeIds.add(nodeId);
                    console.log(`[reindex] op=skip-error nodeId=${nodeId} reason=text-too-short`);
                    continue;
                }
                rows.push({
                    nodeId,
                    labels,
                    text,
                    setScope: props.scope === null || props.scope === undefined,
                });
            }
            // Every record skipped — the exclude-set guarantees they won't be
            // re-fetched, so continue rather than spin.
            if (rows.length === 0)
                continue;
            // Happy path: one batch embed. On batch failure, fall back to per-node
            // so a single poison node is isolated and skipped, not the whole batch.
            let embeddings = null;
            try {
                embeddings = await embedBatch(rows.map((r) => r.text));
            }
            catch {
                embeddings = null;
            }
            if (embeddings) {
                for (let i = 0; i < rows.length; i++) {
                    await setEmbedding(rows[i].nodeId, embeddings[i], rows[i].setScope);
                    nodesProcessed++;
                    console.log(`[reindex] op=embedded nodeId=${rows[i].nodeId} label=${rows[i].labels.join(",")}`);
                }
            }
            else {
                for (const r of rows) {
                    try {
                        const v = await embed(r.text);
                        await setEmbedding(r.nodeId, v, r.setScope);
                        nodesProcessed++;
                        console.log(`[reindex] op=embedded nodeId=${r.nodeId} label=${r.labels.join(",")}`);
                    }
                    catch (err) {
                        nodesSkipped++;
                        excludeIds.add(r.nodeId);
                        const reason = err instanceof Error ? err.message : String(err);
                        errors.push(`Node ${r.nodeId}: ${reason}`);
                        console.log(`[reindex] op=skip-error nodeId=${r.nodeId} reason=${reason}`);
                    }
                }
            }
            console.log(`memory-reindex: progress (${nodesProcessed + nodesSkipped}/${total})`);
        }
        console.log(`memory-reindex: complete — ${nodesProcessed} processed, ${nodesSkipped} skipped, ${scopeBackfilled} scope backfilled, ${errors.length} errors`);
        console.log(`[reindex] op=done processed=${nodesProcessed} skipped=${nodesSkipped}`);
        return { nodesProcessed, nodesSkipped, scopeBackfilled, errors };
    }
    finally {
        await session.close();
    }
}
//# sourceMappingURL=memory-reindex.js.map