import crypto from "node:crypto";
import { AiMode, } from "@superblocksteam/library-shared/types";
import { generateShortId } from "../../ids.js";
import { getErrorMeta } from "../../util/logger.js";
import { AGENT_NEEDS_USER_INPUT, AGENT_PLANNED, } from "../state-machine/clark-fsm.js";
import { sendUserMessageChannel } from "../state-machine/helpers/peer.js";
import { AppSkillsManager } from "./manager.js";
/**
 * Resolves which mode to restore after a fact-capture detour.
 * Falls back to BUILD if no prior mode was recorded.
 */
export const getModeAfterFactCapture = (ctx) => ctx.modeBeforeFactCapture ??
    (ctx.currentMode === AiMode.FACT ? AiMode.BUILD : ctx.currentMode) ??
    AiMode.BUILD;
/**
 * Persist approved facts to disk and send an `app_facts_saved` message.
 * On failure, sends a user-visible error message instead.
 */
export async function saveFactsAndNotify(clark, chatSessionStore, appRootDirPath, facts, logLabel) {
    const sendUserMessage = sendUserMessageChannel(clark, chatSessionStore);
    try {
        const manager = new AppSkillsManager(appRootDirPath);
        let savedCount = 0;
        for (const fact of facts) {
            const action = fact.action ?? "create";
            if (action === "delete") {
                const targetName = (fact.targetFactName ?? fact.name).trim();
                const existing = await manager.findFactByName(targetName);
                if (!existing) {
                    clark.logger.warn("[app-skills] Skip delete: target app fact not found", { targetFactName: targetName });
                    continue;
                }
                await manager.deleteFact(existing.path);
                savedCount += 1;
                continue;
            }
            if (action === "update") {
                const targetName = (fact.targetFactName ?? fact.name).trim();
                const existing = await manager.findFactByName(targetName);
                if (!existing) {
                    clark.logger.warn("[app-skills] Skip update: target app fact not found", { targetFactName: targetName });
                    continue;
                }
                await manager.updateFact(existing.path, {
                    name: fact.name,
                    description: fact.description,
                    content: fact.content,
                });
                savedCount += 1;
                continue;
            }
            await manager.saveFact({
                name: fact.name,
                description: fact.description,
                content: fact.content,
            });
            savedCount += 1;
        }
        if (savedCount > 0) {
            await sendUserMessage({
                type: "app_facts_saved",
                text: `Saved ${savedCount} app fact${savedCount === 1 ? "" : "s"}.`,
                factsCount: savedCount,
            });
        }
        clark.logger.info(`[app-skills] App facts saved (${logLabel})`, {
            count: savedCount,
        });
    }
    catch (err) {
        clark.logger.error(`[app-skills] Failed to save app facts (${logLabel})`, getErrorMeta(err));
        try {
            await sendUserMessage({
                type: "text",
                text: "I wasn't able to save that fact due to an error. Please try again.",
            });
        }
        catch {
            // socket may have dropped; nothing further to do
        }
    }
}
/**
 * Handle a user's approval or rejection of pending app facts from the
 * agent-planning handler. Resolves the pending facts, saves if approved,
 * and transitions to the appropriate next state.
 *
 * @returns `true` if this was a fact-approval flow (caller should `return`),
 *          `false` if the response was unrelated to pending facts.
 */
export async function handleAppFactsApprovalResponse(clark, params, request) {
    const responseMetadata = request.responseMetadata;
    if (responseMetadata?.type !== "multi_choice_response" &&
        responseMetadata?.type !== "confirm_response") {
        return false;
    }
    const responseToMessageId = responseMetadata.responseToMessageId;
    let factsToProcess;
    let selectedFactIndices = [];
    if (clark.context.pendingAppFacts?.messageId === responseToMessageId) {
        factsToProcess = clark.context.pendingAppFacts.facts;
    }
    else if (responseMetadata.type === "multi_choice_response") {
        const messages = await params.chatSessionStore.getMessages();
        const originalMessage = messages.find((msg) => msg.type === "multi_choice" && msg.id === responseToMessageId);
        if (originalMessage?.factCandidates?.length) {
            factsToProcess = originalMessage.factCandidates;
        }
    }
    else {
        const messages = await params.chatSessionStore.getMessages();
        const originalMessage = messages.find((msg) => msg.type === "confirm" && msg.id === responseToMessageId);
        if (originalMessage?.fact) {
            factsToProcess = [originalMessage.fact];
        }
    }
    if (!factsToProcess) {
        return false;
    }
    let selectedFacts = factsToProcess;
    let approved = false;
    if (responseMetadata.type === "multi_choice_response") {
        const selected = Array.isArray(responseMetadata.selectedChoiceIndices)
            ? responseMetadata.selectedChoiceIndices
            : [];
        if (selected.length === 0 && request.prompt.trim().length > 0) {
            return false;
        }
        const denyIndex = factsToProcess.length;
        const denyAll = selected.includes(denyIndex);
        selectedFactIndices = denyAll
            ? []
            : selected.filter((i) => i >= 0 && i < factsToProcess.length);
        selectedFacts = selectedFactIndices.map((i) => factsToProcess[i]);
        approved = selectedFacts.length > 0;
    }
    else {
        approved = Boolean(responseMetadata.approved);
    }
    const persistedMessageId = await params.chatSessionStore.recordUserResponse(request.prompt, responseMetadata.type, responseMetadata.type === "multi_choice_response"
        ? {
            responseToMessageId,
            selectedChoiceIndices: responseMetadata.selectedChoiceIndices,
        }
        : {
            responseToMessageId,
            approved: responseMetadata.approved,
        });
    if (persistedMessageId) {
        clark.updateContext({
            chatmessageId: persistedMessageId,
        });
    }
    const resumeMode = getModeAfterFactCapture(clark.context);
    clark.updateContext((ctx) => ({
        ...ctx,
        pendingAppFacts: undefined,
        rememberFactsCandidatesForTurn: undefined,
        explicitRememberIntentForTurn: undefined,
        modeBeforeFactCapture: undefined,
        currentMode: getModeAfterFactCapture(ctx),
        interruptOriginalPrompt: undefined,
    }));
    if (approved) {
        await saveFactsAndNotify(clark, params.chatSessionStore, params.appRootDirPath, selectedFacts, responseMetadata.type === "multi_choice_response"
            ? "multi_choice"
            : "confirm");
    }
    if (resumeMode === AiMode.PLAN) {
        void clark.send({
            type: AGENT_PLANNED,
            userPrompt: "Continue.",
            promptContext: request.promptContext,
            isUserPrompt: false,
            knowledgeMessageId: generateShortId(),
        });
    }
    else {
        void clark.send({
            type: AGENT_NEEDS_USER_INPUT,
        });
    }
    clark.logger.info("[app-skills] Resumed after app fact approval response", {
        approved,
        resumedMode: resumeMode,
        didAutoContinue: resumeMode === AiMode.PLAN,
        responseType: responseMetadata.type,
        selectedFactIndices: responseMetadata.type === "multi_choice_response"
            ? selectedFactIndices
            : undefined,
    });
    return true;
}
/**
 * After an LLM turn in FACT mode, if Clark called `rememberFacts`, build and
 * send the approval prompt (confirm for single fact, multi_choice for many)
 * and transition to `$AwaitingUser`.
 *
 * @returns `true` if there were candidates to present (caller should `return`),
 *          `false` otherwise.
 */
export async function presentFactApprovalMessage(clark, chatSessionStore) {
    const candidates = clark.context.rememberFactsCandidatesForTurn;
    if (!candidates?.length) {
        return false;
    }
    const messageId = crypto.randomUUID();
    const confirmPayload = candidates.length === 1 && candidates[0]
        ? {
            type: "confirm",
            text: candidates[0].action === "update"
                ? "Should I update this knowledge entry?"
                : "Should I remember this knowledge entry?",
            role: "assistant",
            content: candidates[0].action === "update"
                ? "Should I update this knowledge entry?"
                : "Should I remember this knowledge entry?",
            id: messageId,
            fact: {
                name: candidates[0].name,
                description: candidates[0].description,
                content: candidates[0].content,
                action: candidates[0].action ?? "create",
                targetFactName: candidates[0].targetFactName,
            },
        }
        : {
            type: "multi_choice",
            text: "Should I remember any of these knowledge entries?",
            role: "assistant",
            content: "Should I remember any of these knowledge entries?",
            id: messageId,
            choices: [
                ...candidates.map((c) => ({
                    text: c.name,
                    subText: c.description,
                    action: "approve",
                })),
                { text: "Don't save any", action: "deny" },
            ],
            selectionType: "multiple",
            factCandidates: candidates,
        };
    clark.updateContext({
        pendingAppFacts: { messageId, facts: candidates },
        activeInteractiveMessage: {
            id: messageId,
            payload: confirmPayload,
        },
        rememberFactsCandidatesForTurn: undefined,
    });
    const sendUserMessage = sendUserMessageChannel(clark, chatSessionStore);
    await sendUserMessage(confirmPayload);
    clark.logger.info("[app-skills] Presenting fact draft for user approval", {
        candidateCount: candidates.length,
    });
    void clark.send({
        type: AGENT_NEEDS_USER_INPUT,
    });
    return true;
}
//# sourceMappingURL=helpers.js.map