import { normalizeFunctionCallId, normalizeFunctionCallOutputId } from '../bridge-id-utils.js';
import { normalizeContentPart } from './output-content-normalizer.js';
import { expandResponsesMessageItem } from '../../sse/shared/responses-output-normalizer.js';
import { normalizeResponsesUsageWithNative } from '../../router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js';
function isStableToolCallId(raw) {
    return /^((fc|call)_[A-Za-z0-9_-]+)$/i.test(raw);
}
function buildToolOutputIndex(response) {
    const ids = new Set();
    try {
        const primary = Array.isArray(response.tool_outputs)
            ? response.tool_outputs
            : [];
        for (const entry of primary) {
            if (!entry || typeof entry !== 'object')
                continue;
            const raw = entry.tool_call_id ||
                entry.call_id ||
                entry.id;
            if (typeof raw === 'string' && raw.trim().length) {
                const trimmed = raw.trim();
                // 记录原始 ID（例如 OpenAI 的 toolu_ 前缀），以兼容直接使用
                // tool_call_id 的客户端；同时记录归一化后的 fc_ 形式，保证与
                // buildFunctionCallOutput 中 normalizeFunctionCallId 的结果对齐。
                ids.add(trimmed);
                try {
                    const normalized = normalizeFunctionCallId({ callId: trimmed, fallback: trimmed });
                    if (normalized && normalized !== trimmed) {
                        ids.add(normalized);
                    }
                }
                catch {
                    // 归一化失败不应影响主流程
                }
            }
        }
    }
    catch {
        // best-effort: 不因索引构建失败影响主流程
    }
    return ids;
}
function appendReasoningSegments(target, raw) {
    if (typeof raw !== 'string' || !raw.length) {
        return;
    }
    const segments = raw.split('\n');
    let previousValue;
    for (const segment of segments) {
        if (!segment.length)
            continue;
        let value = segment;
        if (previousValue) {
            const prevLast = previousValue.charAt(previousValue.length - 1) || '';
            const currFirst = segment.charAt(0) || '';
            const prevWord = /[A-Za-z0-9_]$/.test(prevLast);
            const currWord = /^[A-Za-z0-9]/.test(currFirst);
            const currQuote = currFirst === '"' || currFirst === "'";
            const prevPunct = /[:;]/.test(prevLast);
            const prevEndsWhitespace = /\s$/.test(previousValue);
            const prevSentenceEnd = /[.?!"]$/.test(prevLast);
            if (((prevWord || prevSentenceEnd) && currWord && !prevEndsWhitespace) ||
                (prevPunct && currQuote)) {
                value = ` ${segment}`;
            }
        }
        target.push(value);
        previousValue = value;
    }
}
export function buildResponsesOutputFromChat(options) {
    const { response, message, requestId, sanitizeFunctionName } = options;
    const outputItems = [];
    const allocateOutputId = (prefix) => `${prefix}_${requestId ?? Date.now()}_${outputItems.length + 1}`;
    const role = message?.role || 'assistant';
    const content = message?.content;
    let toolCalls = Array.isArray(message?.tool_calls) ? message.tool_calls : [];
    try {
        toolCalls = toolCalls.filter((it) => {
            const nm = (it && typeof it === 'object') ? (it?.function?.name || it.name) : undefined;
            return typeof nm === 'string' && nm.trim().length > 0 && nm.toLowerCase() !== 'tool';
        });
    }
    catch {
        /* ignore */
    }
    const hasToolCalls = toolCalls.length > 0;
    const reasoningChunks = [];
    const preservedReasoning = response?.__responses_reasoning;
    const preservedSummary = preservedReasoning?.summary ?? [];
    const preservedContent = preservedReasoning?.content ?? [];
    const preservedEncrypted = preservedReasoning?.encrypted_content;
    if (preservedContent.length) {
        for (const entry of preservedContent) {
            if (!entry || typeof entry !== 'object')
                continue;
            const text = String(entry.text ?? '').trim();
            if (text.length) {
                reasoningChunks.push(text);
            }
        }
    }
    else {
        appendReasoningSegments(reasoningChunks, message?.reasoning_content);
    }
    const convertedContent = convertChatContentToResponses(content);
    const shouldEmitMessage = Boolean(message) && (convertedContent.length > 0 ||
        reasoningChunks.length > 0 ||
        preservedSummary.length > 0 ||
        !hasToolCalls);
    if (shouldEmitMessage) {
        const responsesMessage = {
            id: allocateOutputId('message'),
            type: 'message',
            status: 'completed',
            role,
            content: convertedContent.length > 0 ? convertedContent : []
        };
        const expandedItems = expandResponsesMessageItem(responsesMessage, {
            requestId: requestId ?? 'responses_outbound',
            outputIndex: outputItems.length,
            extraReasoning: reasoningChunks
        });
        const summaryItems = preservedSummary
            .map((entry) => ({ type: 'summary_text', text: String(entry.text ?? '') }))
            .filter((entry) => entry.text.trim().length > 0);
        let reasoningItem = expandedItems.find((item) => item.type === 'reasoning');
        if (!reasoningItem && summaryItems.length > 0) {
            reasoningItem = {
                id: `${responsesMessage.id}_reasoning`,
                type: 'reasoning',
                summary: summaryItems,
                content: undefined,
                ...(typeof preservedEncrypted === 'string' ? { encrypted_content: preservedEncrypted } : {})
            };
            expandedItems.unshift(reasoningItem);
        }
        else if (reasoningItem) {
            if (summaryItems.length > 0) {
                reasoningItem.summary = summaryItems;
            }
            if (typeof preservedEncrypted === 'string') {
                reasoningItem.encrypted_content = preservedEncrypted;
            }
        }
        for (const expanded of expandedItems) {
            outputItems.push(expanded);
        }
    }
    const normalizedToolCalls = [];
    let toolFallbackCounter = 0;
    for (const call of toolCalls) {
        const outputEntry = buildFunctionCallOutput(call, allocateOutputId, sanitizeFunctionName, outputItems.length, ++toolFallbackCounter);
        if (outputEntry) {
            outputItems.push(outputEntry.output);
            normalizedToolCalls.push({
                id: outputEntry.call_id,
                name: outputEntry.name,
                args: outputEntry.arguments
            });
        }
    }
    const usage = normalizeUsage(response.usage);
    const outputTextMeta = response?.__responses_output_text_meta;
    const outputText = resolveOutputText(convertedContent, outputTextMeta);
    // 如果顶层 tool_outputs 已经为所有 tool_calls 提供了结果，说明这些函数调用
    // 已在服务端（例如 server-side web_search）完成，不应再对客户端暴露
    // required_action/submit_tool_outputs。此时只需返回 completed 状态即可，避免
    // 再触发一轮工具回合。
    const executedIds = buildToolOutputIndex(response);
    const pendingToolCalls = normalizedToolCalls.filter((entry) => !executedIds.has(entry.id));
    const hasNormalizedToolCalls = pendingToolCalls.length > 0;
    if (hasNormalizedToolCalls) {
        const pendingIds = new Set(pendingToolCalls.map((entry) => entry.id));
        for (const item of outputItems) {
            const type = item.type;
            if (type === 'message') {
                item.status = 'in_progress';
            }
            if (type === 'function_call') {
                const callId = item.call_id;
                if (typeof callId === 'string' && pendingIds.has(callId)) {
                    item.status = 'in_progress';
                }
            }
        }
    }
    const requiredAction = hasNormalizedToolCalls
        ? buildRequiredActionFromNormalized(pendingToolCalls)
        : undefined;
    const status = hasNormalizedToolCalls ? 'requires_action' : 'completed';
    return {
        outputItems,
        outputText,
        status,
        requiredAction,
        usage
    };
}
function normalizeUsage(usageRaw) {
    if (usageRaw === null || usageRaw === undefined) {
        return undefined;
    }
    const normalized = normalizeResponsesUsageWithNative(usageRaw);
    if (normalized === null) {
        return undefined;
    }
    return normalized;
}
function buildFunctionCallOutput(call, allocateOutputId, sanitizeFunctionName, baseCount, offset) {
    try {
        const fn = call?.function || {};
        const callName = typeof fn?.name === 'string' && fn.name.trim().length
            ? fn.name
            : (typeof call.name === 'string' ? call.name : 'tool');
        const sanitized = sanitizeFunctionName(callName);
        if (!sanitized || sanitized.toLowerCase() === 'tool')
            return null;
        const rawArgs = fn?.arguments ?? call.arguments ?? {};
        const argsStr = typeof rawArgs === 'string'
            ? rawArgs
            : (() => {
                try {
                    return JSON.stringify(rawArgs ?? {});
                }
                catch {
                    return '{}';
                }
            })();
        const originalCallId = typeof call.id === 'string' && call.id.trim().length
            ? String(call.id)
            : (typeof call.call_id === 'string' && call.call_id.trim().length ? String(call.call_id) : undefined);
        const stableOriginalCallId = typeof originalCallId === 'string' && originalCallId.trim().length && isStableToolCallId(originalCallId.trim())
            ? originalCallId.trim()
            : undefined;
        // Preserve original tool call IDs when they already follow supported call-id styles.
        // Otherwise normalize to fc_* to keep tool_call_id invariants stable across outputs.
        const callId = typeof stableOriginalCallId === 'string' && stableOriginalCallId.length
            ? stableOriginalCallId
            : normalizeFunctionCallId({
                callId: originalCallId,
                fallback: `fc_call_${baseCount + offset}`
            });
        const outputId = normalizeFunctionCallOutputId({
            callId,
            fallback: allocateOutputId('fc')
        });
        const output = {
            id: outputId,
            type: 'function_call',
            status: 'completed',
            name: sanitized,
            call_id: callId,
            arguments: argsStr
        };
        return { output, call_id: callId, name: sanitized, arguments: argsStr };
    }
    catch {
        return null;
    }
}
function buildRequiredActionFromNormalized(calls) {
    if (!calls.length)
        return undefined;
    const submitCalls = calls.map((entry) => ({
        // Internal + client compat:
        // - keep OpenAI-style `id` + `function`
        // - also expose `tool_call_id` + top-level `name` for legacy/bridge clients
        id: entry.id,
        tool_call_id: entry.id,
        type: 'function',
        name: entry.name,
        arguments: entry.args,
        function: {
            name: entry.name,
            arguments: entry.args
        }
    }));
    return { type: 'submit_tool_outputs', submit_tool_outputs: { tool_calls: submitCalls } };
}
function convertChatContentToResponses(content) {
    if (content == null)
        return [];
    if (Array.isArray(content)) {
        return content
            .map((part) => normalizeContentPart(part, []))
            .filter((entry) => !!entry);
    }
    const normalized = normalizeContentPart(content, []);
    return normalized ? [normalized] : [];
}
function extractOutputText(parts) {
    if (!parts.length)
        return '';
    const reasoningCollector = [];
    const normalizedTexts = parts
        .map((part) => normalizeContentPart(part, reasoningCollector))
        .filter((entry) => !!entry)
        .map((entry) => (typeof entry.text === 'string' ? entry.text : ''))
        .filter(Boolean);
    const text = normalizedTexts.join('\n').trim();
    return text.length ? text : '';
}
function resolveOutputText(parts, meta) {
    if (meta && typeof meta === 'object') {
        const hasField = Boolean(meta.hasField);
        if (hasField) {
            const rawValue = meta.raw;
            if (typeof rawValue === 'string') {
                return rawValue;
            }
            const fallbackValue = meta.value;
            if (typeof fallbackValue === 'string') {
                return fallbackValue;
            }
            return '';
        }
        return undefined;
    }
    const text = extractOutputText(parts);
    return text.length ? text : undefined;
}
