import { tracers, ATTR, withSpan } from "../../telemetry/index.js";
import { logger } from "../../utils/logger.js";
import { buildMessagesArray, buildMultimodalMessagesArray, } from "../../utils/messageBuilder.js";
/**
 * Compute total content length across all messages for span attributes.
 */
function computeTotalContentLength(messages) {
    let total = 0;
    for (const msg of messages) {
        if (typeof msg.content === "string") {
            total += msg.content.length;
        }
        else if (Array.isArray(msg.content)) {
            for (const part of msg.content) {
                if ("text" in part &&
                    typeof part.text === "string") {
                    total += part.text.length;
                }
            }
        }
    }
    return total;
}
/**
 * Check whether input contains multimodal content (images, files, PDFs, CSVs,
 * audio, video). #284: audioFiles/videoFiles were previously ignored here, so a
 * request carrying only audio or video was misrouted to the text-only path.
 *
 * Intentional (round-2 review): routing audio/video through `isMultimodal`
 * does NOT mean vision is required. `buildMultimodalMessagesArray` folds
 * `audioFiles`/`videoFiles` into `inp.files` for auto-detection, and
 * `appendDetectedFileResult` injects their content as plain text markers
 * (`## Audio File:` / `## Video File:`), not image/vision blocks — see
 * `test/continuous-test-suite-bugfixes.ts` ("MessageBuilder #284: ...").
 * "Multimodal" here means "needs the multimodal builder so the payload isn't
 * dropped", not "needs a vision model".
 */
function detectMultimodal(opts) {
    const input = opts.input;
    const hasImages = !!input?.images?.length;
    const hasContent = !!input?.content?.length;
    const hasCSVFiles = !!input?.csvFiles?.length;
    const hasPdfFiles = !!input?.pdfFiles?.length;
    const hasFiles = !!input?.files?.length;
    const hasAudioFiles = !!input?.audioFiles?.length;
    const hasVideoFiles = !!input?.videoFiles?.length;
    return {
        isMultimodal: hasImages ||
            hasContent ||
            hasCSVFiles ||
            hasPdfFiles ||
            hasFiles ||
            hasAudioFiles ||
            hasVideoFiles,
        hasImages,
        hasFiles: hasCSVFiles || hasPdfFiles || hasFiles || hasAudioFiles || hasVideoFiles,
    };
}
/**
 * MessageBuilder class - Handles message construction for AI providers
 */
export class MessageBuilder {
    providerName;
    modelName;
    constructor(providerName, modelName) {
        this.providerName = providerName;
        this.modelName = modelName;
    }
    /**
     * Build messages array for generation
     * Detects multimodal input and routes to appropriate message builder
     */
    async buildMessages(options) {
        return withSpan({
            name: "neurolink.message.build",
            tracer: tracers.sdk,
            attributes: {
                [ATTR.NL_PROVIDER]: this.providerName,
                [ATTR.NL_MODEL]: this.modelName,
            },
        }, async (span) => {
            const { isMultimodal, hasImages, hasFiles } = detectMultimodal(options);
            span.setAttribute(ATTR.MSG_IS_MULTIMODAL, isMultimodal);
            let messages;
            if (isMultimodal) {
                if (process.env.NEUROLINK_DEBUG === "true") {
                    logger.debug("Detected multimodal input, using multimodal message builder");
                }
                const input = options.input;
                const multimodalOptions = {
                    input: {
                        text: options.prompt || options.input?.text || "",
                        images: input?.images,
                        content: input?.content,
                        csvFiles: input?.csvFiles,
                        pdfFiles: input?.pdfFiles,
                        // #284: audioFiles/videoFiles must be forwarded too, or the
                        // multimodal builder receives an empty payload despite
                        // detectMultimodal() correctly routing audio/video-only
                        // requests here.
                        audioFiles: input?.audioFiles,
                        videoFiles: input?.videoFiles,
                        files: input?.files,
                    },
                    csvOptions: options.csvOptions,
                    pdfOptions: options.pdfOptions,
                    provider: options.provider,
                    model: options.model,
                    temperature: options.temperature,
                    maxTokens: options.maxTokens,
                    systemPrompt: options.systemPrompt,
                    enableAnalytics: options.enableAnalytics,
                    enableEvaluation: options.enableEvaluation,
                    context: options.context,
                    conversationHistory: options.conversationMessages,
                    schema: options.schema,
                    output: options.output,
                    fileRegistry: options.fileRegistry,
                };
                messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
                // Propagate any systemPrompt augmentation (e.g. inline-file
                // handling guidance from processUnifiedFilesArray) back to the
                // caller's options. Providers like GoogleVertex's native
                // @google/genai stream path read `options.systemPrompt` directly
                // — without this propagation the augmentation lives only on the
                // local `multimodalOptions` clone and never reaches the model.
                if (multimodalOptions.systemPrompt &&
                    multimodalOptions.systemPrompt !== options.systemPrompt) {
                    options.systemPrompt = multimodalOptions.systemPrompt;
                }
            }
            else {
                if (process.env.NEUROLINK_DEBUG === "true") {
                    logger.debug("No multimodal input detected, using standard message builder");
                }
                messages = await buildMessagesArray(options);
            }
            // Convert messages to Vercel AI SDK format
            // Preserve providerOptions (e.g. Anthropic cache_control) through conversion
            const coreMessages = messages.map((msg) => {
                const providerOptions = msg
                    .providerOptions;
                if (typeof msg.content === "string") {
                    return {
                        role: msg.role,
                        content: msg.content,
                        ...(providerOptions && { providerOptions }),
                    };
                }
                else {
                    return {
                        role: msg.role,
                        content: msg.content.map((item) => {
                            const itemProviderOptions = item.providerOptions;
                            if (item.type === "text") {
                                return {
                                    type: "text",
                                    text: item.text || "",
                                    ...(itemProviderOptions && {
                                        providerOptions: itemProviderOptions,
                                    }),
                                };
                            }
                            else if (item.type === "image") {
                                return {
                                    type: "image",
                                    image: item.image || "",
                                    ...(itemProviderOptions && {
                                        providerOptions: itemProviderOptions,
                                    }),
                                };
                            }
                            return item;
                        }),
                        ...(providerOptions && { providerOptions }),
                    };
                }
            });
            span.setAttribute(ATTR.MSG_COUNT, coreMessages.length);
            span.setAttribute(ATTR.MSG_HAS_IMAGES, hasImages);
            span.setAttribute(ATTR.MSG_HAS_FILES, hasFiles);
            span.setAttribute(ATTR.MSG_HAS_SYSTEM_PROMPT, !!options.systemPrompt);
            span.setAttribute(ATTR.MSG_TOTAL_CONTENT_LENGTH, computeTotalContentLength(coreMessages));
            return coreMessages;
        });
    }
    /**
     * Build messages array for streaming operations
     * This is a protected helper method that providers can use to build messages
     * with automatic multimodal detection, eliminating code duplication
     *
     * @param options - Stream options or text generation options
     * @returns Promise resolving to ModelMessage array ready for AI SDK
     */
    async buildMessagesForStream(options) {
        return withSpan({
            name: "neurolink.message.build_for_stream",
            tracer: tracers.sdk,
            attributes: {
                [ATTR.NL_PROVIDER]: this.providerName,
                [ATTR.NL_MODEL]: this.modelName,
            },
        }, async (span) => {
            const { isMultimodal, hasImages, hasFiles } = detectMultimodal(options);
            span.setAttribute(ATTR.MSG_IS_MULTIMODAL, isMultimodal);
            let messages;
            if (isMultimodal) {
                if (process.env.NEUROLINK_DEBUG === "true") {
                    logger.debug(`${this.providerName}: Detected multimodal input, using multimodal message builder`);
                }
                const input = options.input;
                const multimodalOptions = {
                    input: {
                        text: options.prompt ||
                            options.input?.text ||
                            "",
                        images: input?.images,
                        content: input?.content,
                        csvFiles: input?.csvFiles,
                        pdfFiles: input?.pdfFiles,
                        // #284: forward audioFiles/videoFiles here too — see the
                        // matching comment in buildMessages() above.
                        audioFiles: input?.audioFiles,
                        videoFiles: input?.videoFiles,
                        files: input?.files,
                    },
                    csvOptions: options.csvOptions,
                    pdfOptions: options.pdfOptions,
                    provider: options.provider,
                    model: options.model,
                    temperature: options.temperature,
                    maxTokens: options.maxTokens,
                    systemPrompt: options.systemPrompt,
                    enableAnalytics: options.enableAnalytics,
                    enableEvaluation: options.enableEvaluation,
                    context: options.context,
                    conversationHistory: options
                        .conversationMessages,
                    schema: options.schema,
                    output: options.output,
                    fileRegistry: options.fileRegistry,
                };
                messages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
                // Propagate any systemPrompt augmentation (e.g. inline-file
                // handling guidance from processUnifiedFilesArray) back to the
                // caller's options. Providers like GoogleVertex's native
                // @google/genai stream path read `options.systemPrompt` directly
                // — without this propagation the augmentation lives only on the
                // local `multimodalOptions` clone and never reaches the model.
                if (multimodalOptions.systemPrompt &&
                    multimodalOptions.systemPrompt !== options.systemPrompt) {
                    options.systemPrompt = multimodalOptions.systemPrompt;
                }
            }
            else {
                if (process.env.NEUROLINK_DEBUG === "true") {
                    logger.debug(`${this.providerName}: No multimodal input detected, using standard message builder`);
                }
                messages = await buildMessagesArray(options);
            }
            // Convert messages to Vercel AI SDK format
            // Preserve providerOptions (e.g. Anthropic cache_control) through conversion
            const coreMessages = messages.map((msg) => {
                const providerOptions = msg
                    .providerOptions;
                if (typeof msg.content === "string") {
                    return {
                        role: msg.role,
                        content: msg.content,
                        ...(providerOptions && { providerOptions }),
                    };
                }
                else {
                    return {
                        role: msg.role,
                        content: msg.content.map((item) => {
                            const itemProviderOptions = item.providerOptions;
                            if (item.type === "text") {
                                return {
                                    type: "text",
                                    text: item.text || "",
                                    ...(itemProviderOptions && {
                                        providerOptions: itemProviderOptions,
                                    }),
                                };
                            }
                            else if (item.type === "image") {
                                return {
                                    type: "image",
                                    image: item.image || "",
                                    ...(itemProviderOptions && {
                                        providerOptions: itemProviderOptions,
                                    }),
                                };
                            }
                            return item;
                        }),
                        ...(providerOptions && { providerOptions }),
                    };
                }
            });
            span.setAttribute(ATTR.MSG_COUNT, coreMessages.length);
            span.setAttribute(ATTR.MSG_HAS_IMAGES, hasImages);
            span.setAttribute(ATTR.MSG_HAS_FILES, hasFiles);
            span.setAttribute(ATTR.MSG_HAS_SYSTEM_PROMPT, !!options.systemPrompt);
            span.setAttribute(ATTR.MSG_TOTAL_CONTENT_LENGTH, computeTotalContentLength(coreMessages));
            return coreMessages;
        });
    }
}
