import { getSession } from "../lib/neo4j.js";
import { computeEmbedding, buildPersonEmbeddingText } from "../lib/embedding.js";
import { resolveApiKey, extractFromConversation, } from "../lib/anthropic.js";
/**
 * Merge two DiscoveryNotes objects: new non-null values overwrite,
 * null values in the new extraction preserve existing values.
 */
function mergeDiscoveryNotes(existing, incoming) {
    return {
        businessType: incoming.businessType ?? existing.businessType,
        role: incoming.role ?? existing.role,
        needs: incoming.needs ?? existing.needs,
        challenges: incoming.challenges ?? existing.challenges,
        interests: incoming.interests ?? existing.interests,
        engagementQuality: incoming.engagementQuality ?? existing.engagementQuality,
        otherSignals: incoming.otherSignals ?? existing.otherSignals,
    };
}
async function createOrUpdatePerson(extraction, accountId, conversationId) {
    const { email, givenName: rawGiven, familyName, discoveryNotes } = extraction;
    const hasName = !!rawGiven;
    const status = hasName ? "waitlist" : "review";
    const givenName = rawGiven ?? "Unknown";
    const session = getSession();
    try {
        const createdOn = new Date().toISOString();
        const notesJson = discoveryNotes ? JSON.stringify(discoveryNotes) : null;
        const setProps = [
            "p.givenName = $givenName",
            ...(familyName ? ["p.familyName = $familyName"] : []),
            "p.source = 'waitlist'",
            "p.status = $status",
            "p.createdOn = $createdOn",
            "p.sessionId = $conversationId",
            ...(notesJson ? ["p.discoveryNotes = $notesJson"] : []),
        ].join(",\n         ");
        // ON MATCH SET: update name and status only when:
        // - the existing node has no real name (givenName is "Unknown" or missing)
        // - the new extraction provides a real name
        // This preserves admin-reviewed names while backfilling from Haiku.
        const matchProps = hasName
            ? [
                "p.givenName = CASE WHEN p.givenName = 'Unknown' OR p.givenName IS NULL THEN $givenName ELSE p.givenName END",
                ...(familyName
                    ? [
                        "p.familyName = CASE WHEN p.givenName = 'Unknown' OR p.givenName IS NULL THEN $familyName ELSE COALESCE(p.familyName, $familyName) END",
                    ]
                    : []),
                "p.status = CASE WHEN p.givenName = 'Unknown' OR p.givenName IS NULL THEN $status ELSE p.status END",
            ].join(",\n         ")
            : "";
        const onMatchClause = matchProps
            ? `ON MATCH SET ${matchProps}`
            : "";
        const result = await session.run(`MERGE (p:Person {email: $email, accountId: $accountId})
       ON CREATE SET ${setProps}
       ${onMatchClause}
       RETURN p.createdOn = $createdOn AS created,
              p.givenName AS currentName,
              p.discoveryNotes AS existingNotes`, {
            email,
            accountId,
            givenName,
            ...(familyName ? { familyName } : {}),
            status,
            createdOn,
            conversationId,
            ...(notesJson ? { notesJson } : {}),
        });
        const record = result.records[0];
        if (!record)
            throw new Error(`MERGE returned no record for ${email}`);
        const created = record.get("created");
        const currentName = record.get("currentName");
        const existingNotesRaw = record.get("existingNotes");
        const updated = !created && currentName === givenName && givenName !== "Unknown";
        // On MATCH: merge discoveryNotes (new non-null fields enrich existing)
        let finalNotes = discoveryNotes;
        if (!created && discoveryNotes) {
            let existingNotes = null;
            if (existingNotesRaw) {
                try {
                    existingNotes = JSON.parse(existingNotesRaw);
                }
                catch {
                    // Malformed existing notes — overwrite with fresh extraction
                }
            }
            finalNotes = existingNotes
                ? mergeDiscoveryNotes(existingNotes, discoveryNotes)
                : discoveryNotes;
            const mergedJson = JSON.stringify(finalNotes);
            await session.run(`MATCH (p:Person {email: $email, accountId: $accountId})
         SET p.discoveryNotes = $mergedJson`, { email, accountId, mergedJson });
        }
        // Recompute embedding with discovery context
        if (created || updated || discoveryNotes) {
            try {
                const finalNotesJson = finalNotes ? JSON.stringify(finalNotes) : null;
                const textForEmbedding = buildPersonEmbeddingText(givenName, familyName ?? null, email, status, finalNotesJson);
                const embedding = await computeEmbedding(textForEmbedding);
                if (embedding.length > 0) {
                    await session.run(`MATCH (p:Person {email: $email, accountId: $accountId})
             SET p.embedding = $embedding`, { email, accountId, embedding });
                }
            }
            catch {
                // Non-fatal — Person stored without embedding
            }
        }
        return { email, givenName, created, updated, status };
    }
    finally {
        await session.close();
    }
}
async function queryPublicConversations(accountId) {
    const session = getSession();
    try {
        const result = await session.run(`MATCH (c:Conversation {agentType: 'public', accountId: $accountId})
       <-[:PART_OF]-(m:Message {role: 'user'})
       WITH c, m ORDER BY m.createdAt ASC
       RETURN c.conversationId AS conversationId,
              collect(m.content) AS messages,
              c.createdAt AS createdAt
       ORDER BY createdAt ASC`, { accountId });
        return result.records.map((r) => ({
            conversationId: r.get("conversationId"),
            messages: r.get("messages"),
        }));
    }
    finally {
        await session.close();
    }
}
export async function waitlistExtract(accountId) {
    const apiKey = await resolveApiKey();
    const conversations = await queryPublicConversations(accountId);
    const totalMessages = conversations.reduce((sum, c) => sum + c.messages.length, 0);
    if (conversations.length === 0) {
        return {
            conversations: 0,
            messages: 0,
            created: 0,
            updated: 0,
            existing: 0,
            flaggedForReview: 0,
            withDiscoveryNotes: 0,
            errors: 0,
        };
    }
    let created = 0;
    let updated = 0;
    let existing = 0;
    let flaggedForReview = 0;
    let withDiscoveryNotes = 0;
    let errors = 0;
    for (const conv of conversations) {
        try {
            const extraction = await extractFromConversation(conv.messages, apiKey);
            if (!extraction)
                continue;
            if (extraction.discoveryNotes)
                withDiscoveryNotes++;
            const result = await createOrUpdatePerson(extraction, accountId, conv.conversationId);
            if (result.created) {
                if (result.status === "review") {
                    flaggedForReview++;
                }
                else {
                    created++;
                }
            }
            else if (result.updated) {
                updated++;
            }
            else {
                existing++;
            }
        }
        catch (err) {
            console.error(`[waitlist-extract] conv=${conv.conversationId} error=${err instanceof Error ? err.message : String(err)}`);
            errors++;
        }
    }
    return {
        conversations: conversations.length,
        messages: totalMessages,
        created,
        updated,
        existing,
        flaggedForReview,
        withDiscoveryNotes,
        errors,
    };
}
//# sourceMappingURL=waitlist-extract.js.map