import { getSession } from "../lib/neo4j.js";
import { embed } from "../lib/embeddings.js";
import { randomUUID } from "node:crypto";
// Cross-process agreement: these values must match neo4j-store.ts
const INITIAL_CONFIDENCE = 0.5;
const REINFORCEMENT_INCREMENT = 0.15;
const CONTRADICTION_DECREMENT = 0.3;
const VALID_CATEGORIES = new Set([
    "communication",
    "scheduling",
    "decision",
    "workflow",
    "content",
    "interaction",
]);
const VALID_MODES = new Set(["reinforce", "update", "contradict", "merge"]);
export async function profileUpdate(params) {
    const { accountId, userId, category, key, value, source = "behavioural", mode = "reinforce", conversationId, profileFields, mergeSourceIds, } = params;
    if (!VALID_CATEGORIES.has(category)) {
        throw new Error(`Invalid category "${category}". Must be one of: ${[...VALID_CATEGORIES].join(", ")}`);
    }
    if (!VALID_MODES.has(mode)) {
        throw new Error(`Invalid mode "${mode}". Must be one of: ${[...VALID_MODES].join(", ")}`);
    }
    if (!value || value.trim().length === 0) {
        throw new Error("Value is required and cannot be empty.");
    }
    if (mode === "merge" && (!mergeSourceIds || mergeSourceIds.length === 0)) {
        throw new Error("mergeSourceIds required when mode is 'merge'.");
    }
    const session = getSession();
    const now = new Date().toISOString();
    try {
        // Compute embedding from preference text representation
        const embeddingText = `${category}: ${key} — ${value}`;
        let embedding = null;
        try {
            embedding = await embed(embeddingText);
        }
        catch {
            // Preference will be written without embedding — searchable by
            // property queries but not vector search until reindexed.
        }
        // Update top-level UserProfile fields if provided
        if (profileFields && Object.keys(profileFields).length > 0) {
            const restricted = new Set([
                "accountId",
                "userId",
                "embedding",
                "createdAt",
                "profileVersion",
            ]);
            const safeFields = {};
            for (const [k, v] of Object.entries(profileFields)) {
                if (!restricted.has(k))
                    safeFields[k] = v;
            }
            if (Object.keys(safeFields).length > 0) {
                await session.run(`
          MATCH (up:UserProfile {accountId: $accountId, userId: $userId})
          SET up += $fields, up.updatedAt = $now
          `, { accountId, userId, fields: safeFields, now });
            }
        }
        // Handle merge mode — combines multiple preferences into one
        if (mode === "merge") {
            return await executeMerge(session, accountId, userId, category, key, value, source, embedding, mergeSourceIds, now);
        }
        // Check if preference already exists for this account+category+key
        const existingResult = await session.run(`
      MATCH (up:UserProfile {accountId: $accountId, userId: $userId})-[:HAS_PREFERENCE]->(pref:Preference {category: $category, key: $key})
      RETURN pref.preferenceId AS preferenceId, pref.confidence AS confidence
      `, { accountId, userId, category, key });
        if (existingResult.records.length > 0) {
            // Existing preference — apply mode
            const existingId = existingResult.records[0].get("preferenceId");
            const existingConfidence = existingResult.records[0].get("confidence");
            let newConfidence;
            let action;
            switch (mode) {
                case "reinforce":
                    // Converge toward 1.0: increment scaled by distance from max
                    newConfidence = Math.min(1.0, existingConfidence + REINFORCEMENT_INCREMENT * (1.0 - existingConfidence));
                    action = "reinforced";
                    break;
                case "update":
                    newConfidence = existingConfidence;
                    action = "updated";
                    break;
                case "contradict":
                    newConfidence = Math.max(0.0, existingConfidence - CONTRADICTION_DECREMENT);
                    action = "contradicted";
                    break;
                default:
                    newConfidence = existingConfidence;
                    action = "updated";
            }
            const updateProps = {
                accountId,
                preferenceId: existingId,
                value,
                source,
                confidence: newConfidence,
                observedAt: now,
                updatedAt: now,
            };
            if (embedding)
                updateProps.embedding = embedding;
            await session.run(`
        MATCH (pref:Preference {preferenceId: $preferenceId, accountId: $accountId})
        SET pref.value = $value,
            pref.source = $source,
            pref.confidence = $confidence,
            pref.observedAt = $observedAt,
            pref.updatedAt = $updatedAt
            ${embedding ? ", pref.embedding = $embedding" : ""}
        `, updateProps);
            // Add evidence link if conversationId provided
            if (conversationId) {
                await session.run(`
          MATCH (pref:Preference {preferenceId: $preferenceId})
          MATCH (conv:Conversation {conversationId: $conversationId})
          MERGE (pref)-[:OBSERVED_IN]->(conv)
          `, { preferenceId: existingId, conversationId });
            }
            return { preferenceId: existingId, action, confidence: newConfidence };
        }
        // New preference — create node and link to UserProfile
        const preferenceId = randomUUID();
        const createProps = {
            preferenceId,
            accountId,
            userId,
            category,
            key,
            value,
            confidence: INITIAL_CONFIDENCE,
            source,
            observedAt: now,
            createdAt: now,
            updatedAt: now,
            scope: "admin",
        };
        if (embedding)
            createProps.embedding = embedding;
        await session.run(`
      MATCH (up:UserProfile {accountId: $accountId, userId: $userId})
      CREATE (pref:Preference $props)
      CREATE (up)-[:HAS_PREFERENCE]->(pref)
      `, { accountId, userId, props: createProps });
        // Add evidence link if conversationId provided
        if (conversationId) {
            await session.run(`
        MATCH (pref:Preference {preferenceId: $preferenceId})
        MATCH (conv:Conversation {conversationId: $conversationId})
        MERGE (pref)-[:OBSERVED_IN]->(conv)
        `, { preferenceId, conversationId });
        }
        return {
            preferenceId,
            action: "created",
            confidence: INITIAL_CONFIDENCE,
        };
    }
    finally {
        await session.close();
    }
}
async function executeMerge(session, accountId, userId, category, key, value, source, embedding, mergeSourceIds, now) {
    const txc = session.beginTransaction();
    try {
        // Collect evidence links and max confidence from source preferences
        const sourcesResult = await txc.run(`
      MATCH (pref:Preference)
      WHERE pref.preferenceId IN $sourceIds AND pref.accountId = $accountId
      OPTIONAL MATCH (pref)-[:OBSERVED_IN]->(conv:Conversation)
      RETURN pref.preferenceId AS id, pref.confidence AS confidence,
             collect(DISTINCT conv.conversationId) AS convIds
      `, { sourceIds: mergeSourceIds, accountId });
        const maxConfidence = sourcesResult.records.reduce((max, r) => Math.max(max, r.get("confidence")), INITIAL_CONFIDENCE);
        const allConvIds = new Set();
        for (const r of sourcesResult.records) {
            for (const id of r.get("convIds")) {
                if (id)
                    allConvIds.add(id);
            }
        }
        // Create the merged preference
        const preferenceId = randomUUID();
        const mergedProps = {
            preferenceId,
            accountId,
            userId,
            category,
            key,
            value,
            confidence: maxConfidence,
            source,
            observedAt: now,
            createdAt: now,
            updatedAt: now,
            scope: "admin",
        };
        if (embedding)
            mergedProps.embedding = embedding;
        await txc.run(`
      MATCH (up:UserProfile {accountId: $accountId, userId: $userId})
      CREATE (pref:Preference $props)
      CREATE (up)-[:HAS_PREFERENCE]->(pref)
      `, { accountId, userId, props: mergedProps });
        // Transfer evidence links to the merged preference
        for (const convId of allConvIds) {
            await txc.run(`
        MATCH (pref:Preference {preferenceId: $preferenceId})
        MATCH (conv:Conversation {conversationId: $convId})
        MERGE (pref)-[:OBSERVED_IN]->(conv)
        `, { preferenceId, convId });
        }
        // Delete source preferences and their relationships
        await txc.run(`
      MATCH (pref:Preference)
      WHERE pref.preferenceId IN $sourceIds AND pref.accountId = $accountId
      DETACH DELETE pref
      `, { sourceIds: mergeSourceIds, accountId });
        await txc.commit();
        return {
            preferenceId,
            action: "merged",
            confidence: maxConfidence,
            mergedCount: sourcesResult.records.length,
        };
    }
    catch (err) {
        await txc.rollback();
        throw err;
    }
}
//# sourceMappingURL=profile-update.js.map