import { evaluateResponsesHostPolicy } from '../responses-host-policy.js';
import { normalizeMessageReasoningTools } from '../../shared/reasoning-tool-normalizer.js';
import { createBridgeActionState, runBridgeActionPipeline } from '../../bridge-actions.js';
import { resolveBridgePolicy, resolvePolicyActions } from '../../bridge-policies.js';
import { buildResponsesOutputFromChat } from '../../shared/responses-output-builder.js';
import { consumeResponsesPayloadSnapshot, consumeResponsesPassthrough } from '../../shared/responses-reasoning-registry.js';
import { sanitizeResponsesFunctionName, stripInternalToolingMetadata } from '../../shared/responses-tool-utils.js';
import { ProviderProtocolError } from '../../provider-protocol-error.js';
import { normalizeResponsesToolCallArgumentsForClientWithNative } from '../../../router/virtual-router/engine-selection/native-hub-pipeline-resp-semantics.js';
import { normalizeChatResponseReasoningToolsWithNative } from '../../../router/virtual-router/engine-selection/native-hub-bridge-action-semantics.js';
function isPlainObject(value) {
    return Boolean(value && typeof value === 'object' && !Array.isArray(value));
}
function deepCloneRecord(value) {
    try {
        const structuredCloneImpl = globalThis.structuredClone;
        if (typeof structuredCloneImpl === 'function') {
            return structuredCloneImpl(value);
        }
    }
    catch {
        // ignore structuredClone failures
    }
    try {
        return JSON.parse(JSON.stringify(value));
    }
    catch {
        return { ...value };
    }
}
function isMissingResponseField(value) {
    if (value === undefined || value === null) {
        return true;
    }
    if (Array.isArray(value)) {
        return value.length === 0;
    }
    return false;
}
function mergeResponseOutputItems(baseOutput, sourceOutput) {
    if (!Array.isArray(baseOutput) || !Array.isArray(sourceOutput)) {
        return baseOutput;
    }
    const sourceById = new Map();
    sourceOutput.forEach((entry) => {
        if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
            return;
        }
        const id = typeof entry.id === 'string' ? entry.id.trim() : '';
        if (!id.length) {
            return;
        }
        sourceById.set(id, entry);
    });
    return baseOutput.map((entry, index) => {
        if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
            return entry;
        }
        const baseItem = deepCloneRecord(entry);
        const baseId = typeof baseItem.id === 'string' ? String(baseItem.id).trim() : '';
        let sourceItem;
        if (baseId.length) {
            sourceItem = sourceById.get(baseId);
        }
        if (!sourceItem) {
            const candidate = sourceOutput[index];
            if (candidate && typeof candidate === 'object' && !Array.isArray(candidate)) {
                sourceItem = candidate;
            }
        }
        if (!sourceItem) {
            return baseItem;
        }
        if (isMissingResponseField(baseItem.content) && sourceItem.content !== undefined) {
            baseItem.content = deepCloneRecord({ value: sourceItem.content }).value;
        }
        if (isMissingResponseField(baseItem.summary) && sourceItem.summary !== undefined) {
            baseItem.summary = deepCloneRecord({ value: sourceItem.summary }).value;
        }
        if (isMissingResponseField(baseItem.encrypted_content) && sourceItem.encrypted_content !== undefined) {
            baseItem.encrypted_content = deepCloneRecord({ value: sourceItem.encrypted_content }).value;
        }
        return baseItem;
    });
}
function mergeResponseTopLevelFields(options) {
    const merged = deepCloneRecord(options.base);
    const source = options.source;
    const sourceWins = options.sourceWinsKeys ?? [];
    for (const key of sourceWins) {
        if (source[key] !== undefined) {
            merged[key] = deepCloneRecord({ value: source[key] }).value;
        }
    }
    const passthroughKeys = ['metadata', 'temperature', 'top_p', 'prompt_cache_key', 'reasoning'];
    for (const key of passthroughKeys) {
        if (source[key] !== undefined && merged[key] === undefined) {
            merged[key] = deepCloneRecord({ value: source[key] }).value;
        }
    }
    if (merged.output !== undefined && source.output !== undefined) {
        merged.output = mergeResponseOutputItems(merged.output, source.output);
    }
    return merged;
}
function tryParseJson(value) {
    if (typeof value !== 'string')
        return null;
    const trimmed = value.trim();
    if (!trimmed)
        return null;
    if (!(trimmed.startsWith('{') || trimmed.startsWith('[')))
        return null;
    try {
        return JSON.parse(trimmed);
    }
    catch {
        return null;
    }
}
function looksLikeApplyPatchText(value) {
    const trimmed = value.trim();
    if (!trimmed)
        return false;
    return trimmed.includes('*** Begin Patch') && trimmed.includes('*** End Patch');
}
function extractFreeformTextFromArgs(argsRaw) {
    if (typeof argsRaw === 'string') {
        const trimmed = argsRaw.trim();
        if (!trimmed)
            return null;
        // If JSON wrapper, extract common fields first.
        const parsed = tryParseJson(trimmed);
        if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
            const rec = parsed;
            for (const key of ['instructions', 'patch', 'input', 'text']) {
                if (typeof rec[key] === 'string' && rec[key].trim().length) {
                    return String(rec[key]).trim();
                }
            }
        }
        // If it's already freeform (not a JSON wrapper), keep.
        if (!trimmed.startsWith('{') && !trimmed.startsWith('[') && looksLikeApplyPatchText(trimmed)) {
            return trimmed;
        }
        // Unknown freeform: return as-is so client can error.
        return trimmed;
    }
    // If arguments is an object (malformed), try to extract patch-like fields.
    if (argsRaw && typeof argsRaw === 'object' && !Array.isArray(argsRaw)) {
        const rec = argsRaw;
        for (const key of ['instructions', 'patch', 'input', 'text']) {
            if (typeof rec[key] === 'string' && rec[key].trim().length) {
                return String(rec[key]).trim();
            }
        }
    }
    return null;
}
function extractJsonSchemaLike(parameters) {
    const required = Array.isArray(parameters.required)
        ? parameters.required.filter((v) => typeof v === 'string' && v.trim().length > 0).map((v) => v.trim())
        : [];
    const props = parameters.properties && typeof parameters.properties === 'object' && !Array.isArray(parameters.properties)
        ? parameters.properties
        : {};
    const properties = Object.keys(props);
    if (!required.length && !properties.length) {
        return null;
    }
    const additionalProperties = typeof parameters.additionalProperties === 'boolean' ? parameters.additionalProperties : true;
    return { required, properties, additionalProperties };
}
function repairToolArgsBySchemaKeys(toolName, record, schema) {
    // Apply known alias mappings only if the target keys are present in schema properties.
    const wants = new Set(schema.properties);
    const out = { ...record };
    if (toolName === 'exec_command') {
        if (wants.has('cmd') && out.command !== undefined && out.cmd === undefined)
            out.cmd = out.command;
        if (wants.has('command') && out.cmd !== undefined && out.command === undefined)
            out.command = out.cmd;
    }
    if (toolName === 'write_stdin') {
        if (wants.has('chars') && out.text !== undefined && out.chars === undefined)
            out.chars = out.text;
        if (wants.has('text') && out.chars !== undefined && out.text === undefined)
            out.text = out.chars;
    }
    if (toolName === 'apply_patch') {
        if (wants.has('instructions') && out.instructions === undefined) {
            if (typeof out.patch === 'string' && out.patch.trim().length)
                out.instructions = out.patch;
            else if (typeof out.input === 'string' && out.input.trim().length)
                out.instructions = out.input;
        }
        if (wants.has('patch') && out.patch === undefined) {
            if (typeof out.instructions === 'string' && out.instructions.trim().length)
                out.patch = out.instructions;
            else if (typeof out.input === 'string' && out.input.trim().length)
                out.patch = out.input;
        }
    }
    for (const key of schema.required) {
        if (!Object.prototype.hasOwnProperty.call(out, key)) {
            return null;
        }
    }
    if (schema.additionalProperties === false && schema.properties.length > 0) {
        for (const key of Object.keys(out)) {
            if (!wants.has(key))
                delete out[key];
        }
    }
    return out;
}
function buildClientToolIndex(toolsRaw) {
    const index = new Map();
    for (const tool of toolsRaw) {
        if (!tool || typeof tool !== 'object' || Array.isArray(tool))
            continue;
        const t = tool;
        const fn = t.function && typeof t.function === 'object' && !Array.isArray(t.function) ? t.function : undefined;
        const nameRaw = (fn && typeof fn.name === 'string' ? fn.name : undefined) ?? (typeof t.name === 'string' ? t.name : undefined);
        const name = typeof nameRaw === 'string' ? nameRaw.trim() : '';
        if (!name)
            continue;
        const formatRaw = (typeof t.format === 'string' ? t.format : undefined) ??
            (fn && typeof fn.format === 'string' ? fn.format : undefined);
        const parametersRaw = (fn && fn.parameters && typeof fn.parameters === 'object' && !Array.isArray(fn.parameters) ? fn.parameters : undefined) ??
            (t.parameters && typeof t.parameters === 'object' && !Array.isArray(t.parameters) ? t.parameters : undefined) ??
            undefined;
        index.set(name, {
            name,
            ...(typeof formatRaw === 'string' && formatRaw.trim().length ? { format: formatRaw.trim() } : {}),
            ...(parametersRaw ? { parameters: parametersRaw } : {})
        });
    }
    return index;
}
function resolveClientToolName(toolIndex, rawToolName) {
    const trimmed = rawToolName.trim();
    if (!trimmed.length)
        return undefined;
    if (toolIndex.has(trimmed))
        return trimmed;
    const lower = trimmed.toLowerCase();
    for (const key of toolIndex.keys()) {
        if (key.toLowerCase() === lower) {
            return key;
        }
    }
    const hasBash = Array.from(toolIndex.keys()).some((key) => key.toLowerCase() === 'bash');
    if (hasBash)
        return undefined;
    if (!['shell_command', 'exec_command', 'shell', 'terminal', 'bash'].includes(lower)) {
        return undefined;
    }
    const preferred = ['shell_command', 'exec_command', 'shell', 'terminal'];
    for (const candidate of preferred) {
        for (const key of toolIndex.keys()) {
            if (key.toLowerCase() === candidate) {
                return key;
            }
        }
    }
    return undefined;
}
function normalizeResponsesToolCallArgumentsForClient(responsesPayload, context) {
    const toolsRaw = Array.isArray(context?.toolsRaw) ? context?.toolsRaw : [];
    if (!toolsRaw.length) {
        return;
    }
    const normalized = normalizeResponsesToolCallArgumentsForClientWithNative(responsesPayload, toolsRaw);
    for (const key of Object.keys(responsesPayload)) {
        if (!Object.prototype.hasOwnProperty.call(normalized, key)) {
            delete responsesPayload[key];
        }
    }
    Object.assign(responsesPayload, normalized);
}
function unwrapData(value) {
    let current = value;
    const seen = new Set();
    while (current && typeof current === 'object' && !Array.isArray(current) && !seen.has(current)) {
        seen.add(current);
        if ('choices' in current || 'message' in current)
            break;
        if ('data' in current && typeof current.data === 'object') {
            current = current.data;
            continue;
        }
        break;
    }
    return current;
}
function resolveSnapshotLookupKey(response, context) {
    if (typeof response?.request_id === 'string') {
        return response.request_id;
    }
    if (typeof context?.requestId === 'string') {
        return context.requestId;
    }
    if (typeof response?.id === 'string') {
        return response.id;
    }
    return undefined;
}
function shouldStripHostManagedFields(context) {
    const result = evaluateResponsesHostPolicy(context, typeof context?.targetProtocol === 'string' ? context?.targetProtocol : undefined);
    return result.shouldStripHostManagedFields;
}
export function buildResponsesPayloadFromChat(payload, context) {
    if (!payload || typeof payload !== 'object')
        return payload;
    const response = unwrapData(payload);
    if (!response || typeof response !== 'object')
        return payload;
    if (response.object === 'response' && Array.isArray(response.output)) {
        return response;
    }
    const snapshotLookupKey = resolveSnapshotLookupKey(response, context);
    const snapshotPayload = snapshotLookupKey ? consumeResponsesPayloadSnapshot(snapshotLookupKey) : undefined;
    const passthroughPayload = snapshotLookupKey ? consumeResponsesPassthrough(snapshotLookupKey) : undefined;
    const sourceForRetention = (passthroughPayload && typeof passthroughPayload === 'object' ? passthroughPayload : undefined) ??
        (snapshotPayload && typeof snapshotPayload === 'object' ? snapshotPayload : undefined);
    const hasChoicesArray = Array.isArray(response.choices);
    const choicesLength = hasChoicesArray ? response.choices.length : 0;
    if (!hasChoicesArray || choicesLength === 0) {
        const rawStatus = response.status;
        const statusCode = typeof rawStatus === 'string' && rawStatus.trim().length
            ? rawStatus.trim()
            : typeof rawStatus === 'number'
                ? String(rawStatus)
                : undefined;
        const message = typeof response.msg === 'string' && response.msg.trim().length
            ? response.msg.trim()
            : typeof response.message === 'string' && response.message.trim().length
                ? response.message.trim()
                : 'Upstream returned non-standard Chat completion payload (missing choices).';
        const out = {
            id: response.id || `resp-${Date.now()}`,
            object: 'response',
            created_at: response.created_at || response.created || Math.floor(Date.now() / 1000),
            model: response.model,
            status: 'failed',
            output: []
        };
        if (message) {
            out.output_text = message;
            out.error = {
                type: 'provider_error',
                code: statusCode,
                message
            };
        }
        if (context) {
            for (const k of ['metadata', 'parallel_tool_calls', 'tool_choice', 'include']) {
                if (context[k] !== undefined)
                    out[k] = context[k];
            }
            if (!shouldStripHostManagedFields(context) && context.store !== undefined) {
                out.store = context.store;
            }
        }
        if (typeof response.request_id === 'string') {
            out.request_id = response.request_id;
        }
        else if (typeof response.id === 'string') {
            out.request_id = response.id;
        }
        else if (typeof context?.requestId === 'string') {
            out.request_id = context.requestId;
        }
        const mergedFallback = sourceForRetention && typeof sourceForRetention === 'object'
            ? mergeResponseTopLevelFields({
                base: out,
                source: sourceForRetention,
                sourceWinsKeys: ['metadata', 'temperature', 'top_p', 'prompt_cache_key', 'reasoning']
            })
            : out;
        if (mergedFallback.metadata) {
            stripInternalToolingMetadata(mergedFallback.metadata);
        }
        return mergedFallback;
    }
    const canonical = normalizeChatResponseReasoningToolsWithNative(response);
    const choices = Array.isArray(canonical?.choices) ? canonical.choices : [];
    const primaryChoice = choices[0] && typeof choices[0] === 'object' ? choices[0] : undefined;
    const message = primaryChoice && typeof primaryChoice.message === 'object' ? primaryChoice.message : undefined;
    if (!message) {
        throw new ProviderProtocolError('Responses bridge could not locate assistant message in Chat completion', {
            code: 'MALFORMED_RESPONSE',
            protocol: 'openai-chat',
            providerType: 'openai',
            details: {
                context: 'buildResponsesPayloadFromChat',
                choicesLength: choices.length,
                requestId: context?.requestId
            }
        });
    }
    if (message) {
        try {
            const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
            const policyActions = resolvePolicyActions(bridgePolicy, 'response_outbound');
            if (policyActions?.length) {
                const actionState = createBridgeActionState({ messages: [message] });
                runBridgeActionPipeline({
                    stage: 'response_outbound',
                    actions: policyActions,
                    protocol: bridgePolicy?.protocol ?? 'openai-responses',
                    moduleType: bridgePolicy?.moduleType ?? 'openai-responses',
                    requestId: context?.requestId,
                    state: actionState
                });
            }
        }
        catch (error) {
            const errorMessage = error instanceof Error
                ? error.message
                : typeof error === 'string'
                    ? error
                    : 'unknown_error';
            try {
                // eslint-disable-next-line no-console
                console.error(`\x1b[31m[responses-bridge][response_outbound] bridge action pipeline failed requestId=${context?.requestId ?? 'unknown'} error=${errorMessage}\x1b[0m`);
            }
            catch {
                // ignore logging failures
            }
        }
    }
    if (message) {
        try {
            normalizeMessageReasoningTools(message, {
                idPrefix: `responses_reasoning_${context?.requestId ?? 'canonical'}`
            });
        }
        catch {
            // best-effort reasoning normalization
        }
    }
    const outputBuild = buildResponsesOutputFromChat({
        response: response,
        message,
        requestId: context?.requestId,
        sanitizeFunctionName: sanitizeResponsesFunctionName
    });
    const out = {
        id: response.id || `resp-${Date.now()}`,
        object: 'response',
        created_at: response.created_at || response.created || Math.floor(Date.now() / 1000),
        model: response.model,
        status: outputBuild.status,
        output: outputBuild.outputItems
    };
    if (typeof outputBuild.outputText === 'string') {
        out.output_text = outputBuild.outputText;
    }
    if (outputBuild.usage !== undefined)
        out.usage = outputBuild.usage;
    if (outputBuild.requiredAction)
        out.required_action = outputBuild.requiredAction;
    normalizeResponsesToolCallArgumentsForClient(out, context);
    if (context) {
        for (const k of ['metadata', 'parallel_tool_calls', 'tool_choice', 'include']) {
            if (context[k] !== undefined)
                out[k] = context[k];
        }
        if (!shouldStripHostManagedFields(context) && context.store !== undefined) {
            out.store = context.store;
        }
    }
    if (typeof response.request_id === 'string') {
        out.request_id = response.request_id;
    }
    else if (typeof response.id === 'string') {
        out.request_id = response.id;
    }
    else if (typeof context?.requestId === 'string') {
        out.request_id = context.requestId;
    }
    const merged = sourceForRetention && typeof sourceForRetention === 'object'
        ? mergeResponseTopLevelFields({
            base: out,
            source: sourceForRetention,
            sourceWinsKeys: ['metadata', 'temperature', 'top_p', 'prompt_cache_key', 'reasoning']
        })
        : out;
    if (merged.metadata) {
        stripInternalToolingMetadata(merged.metadata);
    }
    return merged;
}
export function extractRequestIdFromResponse(response) {
    if (response && typeof response === 'object' && 'metadata' in response && response.metadata && typeof response.metadata === 'object') {
        const meta = response.metadata;
        if (typeof meta.requestId === 'string')
            return meta.requestId;
    }
    return undefined;
}
