// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import type { InvocationContext, McpToolResult } from '@azure/functions';
import { McpContentBlock, McpTextContent, McpToolResponse } from '../mcp/McpToolResponse';
import { warnIfLooksLikeMcpSdkValue } from '../mcp/sdkCompat';
import { shouldCreateStructuredContentMarker } from '../utils/mcpContentMarker';

const multiContentResultType = 'multi_content_result';
const textContentResultType = 'text';

/**
 * Converts a tool handler's return value into the wire-format MCP tool result.
 *
 * Accepted inputs:
 *  - `null` / `undefined` → passed through.
 *  - `McpToolResponse` instance → serialized as-is.
 *  - `McpContentBlock` instance → wrapped in a single-block response.
 *  - Non-empty array of `McpContentBlock` instances → wrapped in a multi-block response.
 *  - Any other value → serialized as a text block. If the value's class is marked with
 *    `@McpContent`, it is also emitted as `structuredContent`.
 *
 * Detection uses `instanceof` exclusively — arbitrary user objects that happen to have a
 * `content`/`type`/`structuredContent` field are treated as plain values.
 *
 * @param context Optional `InvocationContext` used to surface a one-time warning when the
 *   value looks like an `@modelcontextprotocol/sdk` response that is not auto-converted.
 */
export function toMcpToolResult(result: unknown, context?: InvocationContext): McpToolResult | null | undefined {
    if (result === null || result === undefined) {
        return result;
    }

    if (result instanceof McpToolResponse) {
        return serializeToolResponse(result);
    }

    if (result instanceof McpContentBlock) {
        return serializeToolResponse(new McpToolResponse({ content: [result] }));
    }

    if (Array.isArray(result) && result.length > 0 && result.every((b) => b instanceof McpContentBlock)) {
        return serializeToolResponse(new McpToolResponse({ content: result as McpContentBlock[] }));
    }

    // Plain-value path: warn once if the value looks like an MCP SDK shape that
    // the user likely intended to be converted. Behavior is unchanged.
    warnIfLooksLikeMcpSdkValue(result, context);

    const text = typeof result === 'string' ? result : JSON.stringify(result);
    const mcpResult: McpToolResult = {
        type: textContentResultType,
        content: JSON.stringify({ type: textContentResultType, text }),
    };

    if (shouldCreateStructuredContentMarker(result)) {
        mcpResult.structuredContent = JSON.stringify(result);
    }

    return mcpResult;
}

function serializeToolResponse(response: McpToolResponse): McpToolResult {
    const blocks = ensureTextBlockWhenStructured(response);

    let type: string;
    let contentStr: string;
    if (blocks.length === 1) {
        const [block] = blocks as [McpContentBlock];
        type = block.type;
        contentStr = JSON.stringify(block);
    } else {
        type = multiContentResultType;
        contentStr = JSON.stringify(blocks);
    }

    const out: McpToolResult = { type, content: contentStr };

    if (response.structuredContent !== undefined && response.structuredContent !== null) {
        out.structuredContent =
            typeof response.structuredContent === 'string'
                ? response.structuredContent
                : JSON.stringify(response.structuredContent);
    }

    return out;
}

/**
 * Some MCP clients require a text content block alongside structured content for display.
 * If the response declares `structuredContent` but has no `McpTextContent` block, synthesize one.
 */
function ensureTextBlockWhenStructured(response: McpToolResponse): McpContentBlock[] {
    if (response.structuredContent === null || response.structuredContent === undefined) {
        return response.content;
    }

    if (response.content.some((b) => b instanceof McpTextContent)) {
        return response.content;
    }

    const fallbackText =
        typeof response.structuredContent === 'string'
            ? response.structuredContent
            : JSON.stringify(response.structuredContent);

    return [...response.content, new McpTextContent(fallbackText)];
}
