import { parseToolResultValue } from "../agentMessageConversion";
import { normalizeToolName, parseJsonObject, stringOrNull, } from "./agentQuestionnaireTranscriptRecovery";
const QUESTION_TYPES = [
    "options",
    "item-picker",
    "item-collection",
    "file-upload",
];
function normalizeQuestionType(value) {
    return QUESTION_TYPES.find((type) => type === value) ?? "options";
}
function toRecapOptions(value) {
    if (!Array.isArray(value)) {
        return [];
    }
    const options = [];
    for (const entry of value) {
        if (typeof entry === "string" && entry.trim()) {
            options.push({ id: entry, label: entry });
            continue;
        }
        if (entry && typeof entry === "object") {
            const id = stringOrNull(entry.id);
            const label = stringOrNull(entry.label);
            if (id || label) {
                options.push({ id: id ?? label ?? "", label: label ?? id ?? "" });
            }
        }
    }
    return options;
}
function toRecapItems(value) {
    if (!Array.isArray(value)) {
        return [];
    }
    const items = [];
    for (const entry of value) {
        if (!entry || typeof entry !== "object")
            continue;
        const item = entry;
        const id = stringOrNull(item.id) ?? undefined;
        const name = stringOrNull(item.name) ?? undefined;
        const path = stringOrNull(item.path) ?? undefined;
        if (!id && !name && !path)
            continue;
        items.push({
            id,
            name,
            path,
            includeSubitems: item.includeSubitems === true,
        });
    }
    return items;
}
function toRecapDocuments(value) {
    if (!Array.isArray(value)) {
        return [];
    }
    const documents = [];
    for (const entry of value) {
        if (!entry || typeof entry !== "object")
            continue;
        const document = entry;
        const fileName = stringOrNull(document.fileName);
        if (!fileName)
            continue;
        documents.push({
            fileName,
            fileSize: typeof document.fileSize === "number" &&
                Number.isFinite(document.fileSize)
                ? document.fileSize
                : undefined,
            contentType: stringOrNull(document.contentType) ?? undefined,
        });
    }
    return documents;
}
function toRecapAnswer(value) {
    if (!value || typeof value !== "object") {
        return null;
    }
    const answer = value;
    const questionId = stringOrNull(answer.questionId);
    if (!questionId) {
        return null;
    }
    return {
        questionId,
        question: stringOrNull(answer.question) ?? undefined,
        selectedOptions: toRecapOptions(answer.selectedOptions),
        selectedItems: toRecapItems(answer.selectedItems),
        uploadedDocuments: toRecapDocuments(answer.uploadedDocuments),
        freetext: stringOrNull(answer.freetext) ?? undefined,
    };
}
function answerHasContent(answer) {
    return (answer.selectedOptions.length > 0 ||
        answer.selectedItems.length > 0 ||
        answer.uploadedDocuments.length > 0 ||
        !!answer.freetext);
}
/**
 * Builds the read-only recap data for a completed (or cancelled)
 * ask-questionnaire tool call.
 *
 * The persisted functionResult is the bare QuestionnaireSuccessResult JSON
 * ({status, answerCount, answers: [{questionId, question, selectedOptions:
 * [{id,label}], selectedItems, uploadedDocuments, freetext}]}) with question
 * prompts and option labels already denormalized by the backend. The question
 * list (incl. skipped optional questions) comes from functionArguments;
 * when arguments are unparsable the rows are synthesized from the answers.
 *
 * Returns null when the tool call is not an ask-questionnaire call or has no
 * parsable result (e.g. validation errors surface via functionError only).
 */
export function parseQuestionnaireRecap(input) {
    if (normalizeToolName(input.functionName) !== "ask-questionnaire") {
        return null;
    }
    const result = parseToolResultValue(input.functionResult);
    if (!result || typeof result !== "object" || Array.isArray(result)) {
        return null;
    }
    const resultRecord = result;
    const status = resultRecord.status;
    if (status !== "success" && status !== "cancelled") {
        return null;
    }
    const answers = (Array.isArray(resultRecord.answers) ? resultRecord.answers : [])
        .map(toRecapAnswer)
        .filter((answer) => !!answer);
    const answersByQuestionId = new Map(answers.map((answer) => [answer.questionId, answer]));
    const parsedArguments = parseJsonObject(input.functionArguments);
    const parameterSource = parseJsonObject(parsedArguments?.parameters) ?? parsedArguments;
    const questions = Array.isArray(parameterSource?.questions)
        ? parameterSource.questions
        : null;
    const rows = [];
    const consumedQuestionIds = new Set();
    if (questions) {
        for (const entry of questions) {
            if (!entry || typeof entry !== "object")
                continue;
            const question = entry;
            const questionId = stringOrNull(question.id);
            if (!questionId)
                continue;
            consumedQuestionIds.add(questionId);
            const answer = answersByQuestionId.get(questionId);
            rows.push({
                id: questionId,
                prompt: stringOrNull(question.prompt) ?? answer?.question ?? questionId,
                type: normalizeQuestionType(question.type),
                optional: question.optional === true,
                answered: !!answer && answerHasContent(answer),
                selectedOptions: answer?.selectedOptions ?? [],
                selectedItems: answer?.selectedItems ?? [],
                uploadedDocuments: answer?.uploadedDocuments ?? [],
                freetext: answer?.freetext,
            });
        }
    }
    // Answers without a matching question definition (or all answers when the
    // arguments were unparsable) still get a row so submitted data stays visible.
    for (const answer of answers) {
        if (consumedQuestionIds.has(answer.questionId))
            continue;
        rows.push({
            id: answer.questionId,
            prompt: answer.question ?? answer.questionId,
            type: answer.selectedItems.length > 0
                ? "item-picker"
                : answer.uploadedDocuments.length > 0
                    ? "file-upload"
                    : "options",
            optional: false,
            answered: answerHasContent(answer),
            selectedOptions: answer.selectedOptions,
            selectedItems: answer.selectedItems,
            uploadedDocuments: answer.uploadedDocuments,
            freetext: answer.freetext,
        });
    }
    if (rows.length === 0) {
        return null;
    }
    return {
        title: stringOrNull(parsedArguments?.title) ?? "Questionnaire",
        description: stringOrNull(parsedArguments?.description) ?? undefined,
        status,
        rows,
    };
}
/**
 * Frontend mirror of the backend QuestionnaireAnswerSummaryBuilder: turns the
 * just-submitted QuestionnaireResult into the same QuestionnaireSuccessResult
 * JSON the backend persists as the tool call's functionResult. Used to bridge
 * the gap between submitting a questionnaire and the backend's toolResult
 * frame arriving, so the recap renders immediately.
 */
export function buildOptimisticQuestionnaireResultJson(parameters, result) {
    if (!result) {
        return JSON.stringify({
            status: "cancelled",
            answerCount: 0,
            answers: [],
        });
    }
    const questions = Array.isArray(parameters?.questions)
        ? parameters.questions
        : [];
    const answers = Array.isArray(result.answers) ? result.answers : [];
    const answerSummaries = answers.map((answer) => {
        const question = questions.find((q) => q.id === answer.questionId);
        const selectedItems = answer.selectedItems ?? [];
        return {
            questionId: answer.questionId,
            question: question?.prompt ?? answer.questionId,
            selectedOptions: (answer.selectedOptions ?? []).map((optionId) => ({
                id: optionId,
                label: question?.options?.find((option) => option.id === optionId)?.label ??
                    optionId,
            })),
            selectedItems,
            selectedItemPaths: selectedItems
                .map((item) => item?.path)
                .filter((path) => !!path?.trim()),
            uploadedDocuments: answer.uploadedDocuments ?? [],
            freetext: answer.freetext,
        };
    });
    return JSON.stringify({
        status: "success",
        answerCount: answerSummaries.length,
        answers: answerSummaries,
    });
}
/**
 * Builds the transcript apply payload for an optimistic questionnaire tool
 * result, or null when nothing should be applied: synthetic dialogs without a
 * toolCallId, no matching transcript tool call, or a call that already carries
 * a real result. Tool calls listed in reapplyToolCallIds hold a PRIOR
 * optimistic result and may be overwritten by a newer resolution (a replayed
 * dialog after a failed submit POST resolves a second time).
 */
export function buildOptimisticQuestionnaireToolResultApply(input) {
    const { transcript, request, result, reapplyToolCallIds } = input;
    if (request.dialogType !== "questionnaire" || !request.toolCallId) {
        return null;
    }
    const toolCallId = request.toolCallId;
    for (let index = transcript.length - 1; index >= 0; index--) {
        const message = transcript[index];
        const toolCall = message?.toolCalls?.find((candidate) => candidate.toolCallId === toolCallId);
        if (!message || !toolCall) {
            continue;
        }
        const isResolved = toolCall.isCompleted ||
            !!toolCall.functionResult ||
            !!toolCall.functionError;
        if (isResolved && !reapplyToolCallIds?.has(toolCallId)) {
            return null;
        }
        return {
            messageId: message.id,
            toolCallId,
            fallbackToolCall: {
                ...toolCall,
                functionResult: buildOptimisticQuestionnaireResultJson(request.parameters, result ?? null),
                functionError: "",
                isCompleted: true,
            },
        };
    }
    return null;
}
//# sourceMappingURL=agentQuestionnaireRecap.js.map