import { normalizeAssistantTextToToolCalls } from '../../shared/text-markup-normalizer.js';
import { normalizeFunctionCallId } from '../../bridge-id-utils.js';
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
function pickString(...candidates) {
    for (const c of candidates) {
        if (typeof c === 'string' && c.trim().length)
            return c.trim();
    }
    return undefined;
}
function extractResponsesMessageText(item) {
    const parts = [];
    const content = Array.isArray(item.content) ? item.content : [];
    for (const part of content) {
        if (!part || typeof part !== 'object' || Array.isArray(part))
            continue;
        const text = pickString(part.text, part.content, part.value);
        if (text)
            parts.push(text);
    }
    if (typeof item.output_text === 'string' && item.output_text.trim().length) {
        parts.push(String(item.output_text));
    }
    return parts.join('\n').trim();
}
function harvestToolCallsFromResponsesPayloadInPlace(root, config) {
    const output = Array.isArray(root.output) ? root.output : [];
    if (!output.length)
        return false;
    let changed = false;
    const nextOutput = [];
    for (const item of output) {
        if (!isRecord(item)) {
            nextOutput.push(item);
            continue;
        }
        const type = typeof item.type === 'string' ? String(item.type).trim().toLowerCase() : '';
        if (type !== 'message') {
            nextOutput.push(item);
            continue;
        }
        const role = typeof item.role === 'string' ? String(item.role).trim().toLowerCase() : 'assistant';
        if (role !== 'assistant') {
            nextOutput.push(item);
            continue;
        }
        const text = extractResponsesMessageText(item);
        if (!text) {
            nextOutput.push(item);
            continue;
        }
        const normalized = normalizeAssistantTextToToolCalls({ role: 'assistant', content: text }, config?.normalizer);
        const toolCalls = Array.isArray(normalized.tool_calls) ? normalized.tool_calls : [];
        if (!toolCalls.length) {
            nextOutput.push(item);
            continue;
        }
        // Drop the original assistant message item (it usually only contains the tool markup),
        // and append canonical Responses function_call output items.
        changed = true;
        for (const call of toolCalls) {
            if (!call || typeof call !== 'object')
                continue;
            const fn = call.function && typeof call.function === 'object' ? call.function : null;
            const name = typeof fn?.name === 'string' ? String(fn.name).trim() : '';
            if (!name)
                continue;
            const args = typeof fn?.arguments === 'string' ? String(fn.arguments) : '{}';
            const callId = pickString(call.call_id, call.id) ?? `call_${Math.random().toString(36).slice(2, 10)}`;
            const itemId = normalizeFunctionCallId({ callId, fallback: `fc_${callId}` });
            nextOutput.push({
                type: 'function_call',
                id: itemId,
                call_id: callId,
                name,
                arguments: args
            });
        }
    }
    if (changed) {
        root.output = nextOutput;
    }
    return changed;
}
/**
 * Harvest tool calls from assistant textual markup into OpenAI `tool_calls`.
 *
 * Some upstreams (including certain iFlow/hosted models) emit tool calls as plain text tokens
 * (e.g. `<|tool_call_begin|> ...`) instead of structured `tool_calls`.
 *
 * This action is response-only and provider-scoped via compatibility profiles.
 */
export function harvestToolCallsFromText(payload) {
    return harvestToolCallsFromTextWithConfig(payload);
}
export function harvestToolCallsFromTextWithConfig(payload, config) {
    const root = structuredClone(payload);
    const choices = Array.isArray(root.choices) ? root.choices : [];
    // Responses provider payload: harvest into canonical Responses output items first.
    // This allows the normal semantic mapper (Responses → Chat) to surface tool_calls for servertool orchestration.
    if (!choices.length) {
        harvestToolCallsFromResponsesPayloadInPlace(root, config);
        return root;
    }
    for (const choice of choices) {
        if (!isRecord(choice))
            continue;
        const message = choice.message;
        if (!isRecord(message))
            continue;
        const normalized = normalizeAssistantTextToToolCalls(message, config?.normalizer);
        if (normalized !== message) {
            choice.message = normalized;
            const hasToolCalls = Array.isArray(normalized.tool_calls) && normalized.tool_calls.length > 0;
            if (hasToolCalls) {
                const finish = typeof choice.finish_reason === 'string' ? String(choice.finish_reason).trim() : '';
                if (!finish || finish === 'stop') {
                    choice.finish_reason = 'tool_calls';
                }
            }
        }
    }
    return root;
}
