const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
function normalizeModel(value) {
    return typeof value === 'string' ? value.trim().toLowerCase() : '';
}
function isWebSearchRoute(routeId) {
    if (typeof routeId !== 'string') {
        return false;
    }
    const normalized = routeId.trim().toLowerCase();
    return normalized.startsWith('web_search') || normalized.startsWith('search');
}
function hasNonEmptyArray(value) {
    return Array.isArray(value) && value.length > 0;
}
function ensureSystemMessage(messages) {
    const first = messages[0];
    if (isRecord(first) && typeof first.role === 'string' && first.role.toLowerCase() === 'system') {
        return first;
    }
    const sys = { role: 'system', content: '' };
    messages.unshift(sys);
    return sys;
}
function appendSystemText(sys, extra) {
    const prev = typeof sys.content === 'string' ? sys.content : '';
    if (prev.includes('## Tool Calls (Text Markup Mode)')) {
        return;
    }
    const combined = prev ? `${prev}\n\n${extra}` : extra;
    sys.content = combined;
}
function buildToolMarkupInstruction() {
    // Keep this concise. The response-side canonicalizer will harvest these blocks into tool_calls.
    return [
        '## Tool Calls (Text Markup Mode)',
        '',
        'You MAY call tools by emitting one or more XML blocks with this exact format, and nothing else inside the block:',
        '',
        '<tool:exec_command>',
        '<command>...</command>',
        '<timeout_ms>10000</timeout_ms>',
        '</tool:exec_command>',
        '',
        '<tool:write_stdin>',
        '<session_id>...</session_id>',
        '<input>...</input>',
        '</tool:write_stdin>',
        '',
        'Rules:',
        '- Use `<tool:exec_command>` to run shell commands.',
        '- Use `<tool:write_stdin>` to send input to an existing session.',
        '- Do NOT wrap these blocks in code fences.',
        '- Do NOT invent tools; only use exec_command and write_stdin.',
        ''
    ].join('\n');
}
function stringifyToolOutput(value) {
    if (typeof value === 'string')
        return value;
    if (value === null || value === undefined)
        return '';
    try {
        return JSON.stringify(value, null, 2);
    }
    catch {
        return String(value);
    }
}
function coerceToolCallToXmlBlock(toolCall) {
    if (!isRecord(toolCall))
        return null;
    const type = typeof toolCall.type === 'string' ? toolCall.type.trim().toLowerCase() : '';
    if (type && type !== 'function')
        return null;
    const fn = isRecord(toolCall.function) ? toolCall.function : null;
    const name = typeof fn?.name === 'string' ? fn.name.trim() : '';
    if (!name)
        return null;
    const argsRaw = fn?.arguments;
    let args = null;
    if (typeof argsRaw === 'string' && argsRaw.trim().length) {
        try {
            const parsed = JSON.parse(argsRaw);
            args = isRecord(parsed) ? parsed : null;
        }
        catch {
            args = { raw: argsRaw };
        }
    }
    else if (isRecord(argsRaw)) {
        args = argsRaw;
    }
    if (name === 'exec_command') {
        const command = typeof args?.cmd === 'string'
            ? args.cmd
            : typeof args?.command === 'string'
                ? args.command
                : '';
        if (!command)
            return null;
        const timeoutMs = typeof args?.timeout_ms === 'number' && Number.isFinite(args.timeout_ms)
            ? Math.floor(args.timeout_ms)
            : undefined;
        return [
            '<tool:exec_command>',
            `<command>${command}</command>`,
            ...(timeoutMs !== undefined ? [`<timeout_ms>${timeoutMs}</timeout_ms>`] : []),
            '</tool:exec_command>'
        ].join('\n');
    }
    if (name === 'write_stdin') {
        const sessionId = typeof args?.session_id === 'string'
            ? args.session_id
            : typeof args?.sessionId === 'string'
                ? args.sessionId
                : '';
        const input = typeof args?.chars === 'string'
            ? args.chars
            : typeof args?.input === 'string'
                ? args.input
                : '';
        if (!sessionId || !input)
            return null;
        return [
            '<tool:write_stdin>',
            `<session_id>${sessionId}</session_id>`,
            `<input>${input}</input>`,
            '</tool:write_stdin>'
        ].join('\n');
    }
    return null;
}
function rewriteMessageToolSurfaceInPlace(message) {
    const role = typeof message.role === 'string' ? message.role.trim().toLowerCase() : '';
    // Tool result messages are not supported by some iFlow models; convert them into plain text.
    if (role === 'tool') {
        const toolName = typeof message.name === 'string' ? message.name.trim() : '';
        const toolCallId = typeof message.tool_call_id === 'string' ? String(message.tool_call_id).trim() : '';
        const content = stringifyToolOutput(message.content);
        message.role = 'user';
        message.content = [
            'Tool result:',
            ...(toolName ? [`name: ${toolName}`] : []),
            ...(toolCallId ? [`tool_call_id: ${toolCallId}`] : []),
            'output:',
            content
        ].join('\n');
        delete message.name;
        delete message.tool_call_id;
        return;
    }
    // Assistant tool_calls are not supported by some iFlow models; convert them to XML tool markup.
    const toolCallsRaw = message.tool_calls;
    if (hasNonEmptyArray(toolCallsRaw)) {
        const blocks = toolCallsRaw
            .map(coerceToolCallToXmlBlock)
            .filter((b) => typeof b === 'string' && b.length > 0);
        if (blocks.length) {
            const prev = typeof message.content === 'string' ? message.content : '';
            const joined = blocks.join('\n\n');
            message.content = prev && prev.trim().length ? `${prev}\n\n${joined}` : joined;
        }
        delete message.tool_calls;
    }
}
/**
 * Some iFlow models reject OpenAI `tools`/`tool_choice` fields with a non-standard 200 error envelope.
 *
 * For such models, we fall back to "text tool calls":
 * - remove top-level `tools` / `tool_choice` so upstream accepts the request
 * - inject a system instruction telling the model to emit `<tool:...>` blocks
 * - response pipeline will harvest those blocks into canonical `tool_calls`
 */
export function applyIflowToolTextFallback(payload, options) {
    if (!payload || typeof payload !== 'object') {
        return payload;
    }
    const root = structuredClone(payload);
    if (isWebSearchRoute(options?.routeId)) {
        // web_search/search routes are independent transport paths and must not be coupled
        // to model-gated fallback heuristics.
        return root;
    }
    const model = normalizeModel(root.model);
    const models = Array.isArray(options?.models) ? options.models.map((m) => normalizeModel(m)).filter(Boolean) : [];
    if (!model || !models.includes(model)) {
        return root;
    }
    // Only apply when we have a message array to carry the injected instruction.
    const messages = root.messages;
    if (!hasNonEmptyArray(messages)) {
        return root;
    }
    // Always strip tool schemas for fallback models (including followups),
    // otherwise the first turn might succeed but the tool loop breaks.
    if ('tools' in root) {
        delete root.tools;
    }
    if ('tool_choice' in root) {
        delete root.tool_choice;
    }
    // Rewrite message-level tool protocol to plain text so upstream accepts it.
    for (const msg of messages) {
        if (!isRecord(msg))
            continue;
        rewriteMessageToolSurfaceInPlace(msg);
    }
    const sys = ensureSystemMessage(messages);
    appendSystemText(sys, buildToolMarkupInstruction());
    return root;
}
