/**
 * memory-rank — LLM-reasoned entity ranking.
 *
 * Flow:
 *   1. memorySearch(..., limit = candidatePoolSize) to retrieve candidates
 *      using the existing hybrid vector+BM25 pipeline and scope/agent filters.
 *   2. rankCandidates(candidates, criteria, accountId) — passes the pool to
 *      Claude Haiku with a sandboxed ranking prompt, parses the response, and
 *      filters any hallucinated nodeIds.
 *   3. Merge reasoning back onto the original memory-search results and slice
 *      to `limit`.
 *   4. On LLM unavailability (no key, API error, malformed response, etc.),
 *      fall back to the unranked memory-search results prefixed with a
 *      "[Ranking unavailable: <reason>]" notice. Degraded success — not an
 *      error response. This makes the failure visible to the agent so it can
 *      relay to the user.
 */
import { memorySearch } from "./memory-search.js";
import { rankCandidates } from "../lib/llm-ranker.js";
// ---------------------------------------------------------------------------
// Defaults
// ---------------------------------------------------------------------------
const DEFAULT_LIMIT = 10;
const DEFAULT_POOL = 20;
const MAX_POOL = 50;
const DEFAULT_EXPAND = 1;
// ---------------------------------------------------------------------------
// Main entry point
// ---------------------------------------------------------------------------
export async function memoryRank(params) {
    const limit = Math.max(1, Math.min(params.limit ?? DEFAULT_LIMIT, MAX_POOL));
    const pool = Math.max(1, Math.min(params.candidatePoolSize ?? DEFAULT_POOL, MAX_POOL));
    const expandHops = params.expandHops ?? DEFAULT_EXPAND;
    // --- Step 1: retrieve candidates via memory-search ---
    const candidates = await memorySearch({
        query: params.query,
        labels: params.labels,
        accountId: params.accountId,
        limit: pool,
        expandHops,
        allowedScopes: params.allowedScopes,
        keywords: params.keywords,
        keywordMatch: params.keywordMatch,
        agentSlug: params.agentSlug,
        keywordSubscriptions: params.keywordSubscriptions,
    });
    process.stderr.write(`[memory-rank] [${params.accountId}] candidates=${candidates.length} (requested pool=${pool}, limit=${limit})\n`);
    if (candidates.length === 0) {
        return {
            results: [],
            hallucinated: 0,
            candidatePoolSize: 0,
        };
    }
    // --- Step 2: LLM ranking ---
    const rankCandidatePayload = candidates.map((c) => ({
        nodeId: c.nodeId,
        labels: c.labels,
        properties: c.properties,
        related: c.related,
    }));
    const rankResult = await rankCandidates(rankCandidatePayload, params.criteria, params.accountId);
    // --- Step 3a: fallback path — return original memory-search order unranked ---
    if (rankResult.kind === "fallback") {
        const sliced = candidates.slice(0, limit).map((c) => ({
            nodeId: c.nodeId,
            labels: c.labels,
            properties: c.properties,
            score: c.score,
            related: c.related,
        }));
        return {
            results: sliced,
            fallbackReason: rankResult.reason,
            hallucinated: 0,
            candidatePoolSize: candidates.length,
        };
    }
    // --- Step 3b: ranked path — merge reasoning back onto memory-search results ---
    const candidateByNodeId = new Map(candidates.map((c) => [c.nodeId, c]));
    const rankedResults = [];
    for (const ranking of rankResult.rankings) {
        const candidate = candidateByNodeId.get(ranking.nodeId);
        if (!candidate)
            continue; // defensive — should never hit since llm-ranker already filtered
        rankedResults.push({
            nodeId: candidate.nodeId,
            labels: candidate.labels,
            properties: candidate.properties,
            score: candidate.score,
            related: candidate.related,
            rank: ranking.rank,
            reasoning: ranking.reasoning,
        });
    }
    return {
        results: rankedResults.slice(0, limit),
        hallucinated: rankResult.hallucinated,
        candidatePoolSize: candidates.length,
    };
}
//# sourceMappingURL=memory-rank.js.map