import { isJsonObject, jsonClone } from '../../types/json.js';
import { captureResponsesContext, buildChatRequestFromResponses, buildResponsesRequestFromChat } from '../../../responses/responses-openai-bridge.js';
import { maybeAugmentApplyPatchErrorContent } from './chat-mapper.js';
import { mapReqInboundBridgeToolsToChatWithNative, mapReqInboundResumeToolOutputsDetailedWithNative } from '../../../../router/virtual-router/engine-selection/native-hub-pipeline-req-inbound-semantics.js';
const RESPONSES_PARAMETER_KEYS = [
    'model',
    'temperature',
    'top_p',
    'top_k',
    'prompt_cache_key',
    'reasoning',
    'max_tokens',
    'max_output_tokens',
    'response_format',
    'tool_choice',
    'parallel_tool_calls',
    'service_tier',
    'truncation',
    'include',
    'store',
    'user',
    'logit_bias',
    'seed',
    'stop',
    'stop_sequences',
    'modalities'
];
const RESPONSES_SUBMIT_ENDPOINT = '/v1/responses.submit_tool_outputs';
function mapToolOutputs(entries, missing) {
    if (!entries || !entries.length)
        return undefined;
    const outputs = [];
    entries.forEach((entry, index) => {
        if (!isJsonObject(entry)) {
            missing.push({ path: `tool_outputs[${index}]`, reason: 'invalid_entry', originalValue: jsonClone(entry) });
            return;
        }
        const callId = entry.tool_call_id || entry.call_id || entry.id;
        if (!callId) {
            missing.push({ path: `tool_outputs[${index}].tool_call_id`, reason: 'missing_tool_call_id' });
            return;
        }
        let content = '';
        if (typeof entry.output === 'string') {
            content = entry.output;
        }
        else if (entry.output != null) {
            try {
                content = JSON.stringify(entry.output);
            }
            catch {
                content = String(entry.output);
            }
        }
        const nameValue = typeof entry.name === 'string' ? entry.name : undefined;
        const augmented = maybeAugmentApplyPatchErrorContent(content, nameValue);
        outputs.push({
            tool_call_id: String(callId),
            content: augmented,
            name: nameValue
        });
    });
    return outputs.length ? outputs : undefined;
}
function deriveResumeToolOutputsFromResume(resume) {
    if (!resume || typeof resume !== 'object') {
        return undefined;
    }
    const mapped = mapReqInboundResumeToolOutputsDetailedWithNative(resume);
    if (!Array.isArray(mapped) || mapped.length === 0) {
        return undefined;
    }
    const outputs = mapped.map((entry) => ({
        tool_call_id: entry.tool_call_id,
        content: entry.content
    }));
    return outputs.length ? outputs : undefined;
}
function collectParameters(payload, streamHint) {
    const params = {};
    for (const key of RESPONSES_PARAMETER_KEYS) {
        if (payload[key] !== undefined) {
            params[key] = payload[key];
        }
    }
    if (streamHint !== undefined) {
        params.stream = streamHint;
    }
    return Object.keys(params).length ? params : undefined;
}
function normalizeTools(rawTools, missing) {
    if (!rawTools || rawTools.length === 0) {
        return undefined;
    }
    const tools = mapReqInboundBridgeToolsToChatWithNative(rawTools);
    if (tools.length === 0) {
        rawTools.forEach((tool, index) => {
            missing.push({ path: `tools[${index}]`, reason: 'invalid_entry', originalValue: jsonClone(tool) });
        });
    }
    return tools.length ? tools : undefined;
}
function normalizeMessages(value, missing) {
    if (!Array.isArray(value)) {
        if (value !== undefined) {
            missing.push({ path: 'messages', reason: 'invalid_type', originalValue: jsonClone(value) });
        }
        else {
            missing.push({ path: 'messages', reason: 'absent' });
        }
        return [];
    }
    const messages = [];
    value.forEach((item, index) => {
        if (!item || typeof item !== 'object' || Array.isArray(item)) {
            missing.push({ path: `messages[${index}]`, reason: 'invalid_entry', originalValue: jsonClone(item) });
            return;
        }
        messages.push(item);
    });
    return messages;
}
function serializeSystemContent(message) {
    if (!message)
        return undefined;
    const content = message.content;
    if (typeof content === 'string')
        return content;
    if (Array.isArray(content)) {
        const parts = [];
        content.forEach(part => {
            if (typeof part === 'string') {
                parts.push(part);
            }
            else if (part && typeof part === 'object') {
                const text = part.text;
                if (typeof text === 'string') {
                    parts.push(text);
                }
            }
        });
        return parts.join('');
    }
    if (content != null) {
        try {
            return JSON.stringify(content);
        }
        catch {
            return String(content);
        }
    }
    return undefined;
}
function mergeMetadata(a, b) {
    if (!a && !b) {
        return undefined;
    }
    if (!a && b) {
        return jsonClone(b);
    }
    if (a && !b) {
        return jsonClone(a);
    }
    const left = jsonClone(a);
    const right = jsonClone(b);
    return { ...left, ...right };
}
function isSubmitToolOutputsEndpoint(ctx) {
    if (!ctx || typeof ctx !== 'object') {
        return false;
    }
    const entry = typeof ctx.entryEndpoint === 'string' ? ctx.entryEndpoint.trim().toLowerCase() : '';
    return entry === RESPONSES_SUBMIT_ENDPOINT;
}
function attachResponsesSemantics(existing, context, resume) {
    if (!context && !resume) {
        return existing;
    }
    const next = existing ? { ...existing } : {};
    const currentNode = next.responses && isJsonObject(next.responses) ? { ...next.responses } : {};
    if (context) {
        currentNode.context = jsonClone(context);
    }
    if (resume) {
        currentNode.resume = jsonClone(resume);
    }
    next.responses = currentNode;
    return next;
}
function extractResponsesSemanticsNode(chat) {
    if (!chat?.semantics || typeof chat.semantics !== 'object') {
        return undefined;
    }
    const node = chat.semantics.responses;
    return node && isJsonObject(node) ? node : undefined;
}
function readResponsesContextFromSemantics(chat) {
    const node = extractResponsesSemanticsNode(chat);
    if (!node) {
        return undefined;
    }
    const contextNode = node.context;
    if (!contextNode || !isJsonObject(contextNode)) {
        return undefined;
    }
    return jsonClone(contextNode);
}
function readResponsesResumeFromSemantics(chat) {
    const node = extractResponsesSemanticsNode(chat);
    if (!node) {
        return undefined;
    }
    const resumeNode = node.resume;
    if (!resumeNode || !isJsonObject(resumeNode)) {
        return undefined;
    }
    return jsonClone(resumeNode);
}
function selectResponsesContextSnapshot(chat, envelopeMetadata) {
    const semanticsContext = readResponsesContextFromSemantics(chat);
    const context = semanticsContext ??
        {
            metadata: envelopeMetadata
        };
    const mergedMetadata = mergeMetadata(context.metadata ?? undefined, envelopeMetadata);
    if (mergedMetadata) {
        context.metadata = mergedMetadata;
    }
    return context;
}
function resolveSubmitResponseId(resumeMeta, responsesContext) {
    const resumeId = typeof resumeMeta?.restoredFromResponseId === 'string'
        ? resumeMeta.restoredFromResponseId.trim()
        : undefined;
    if (resumeId) {
        return resumeId;
    }
    const contextRecord = responsesContext && typeof responsesContext === 'object'
        ? responsesContext
        : undefined;
    const previousId = typeof contextRecord?.previous_response_id === 'string'
        ? contextRecord.previous_response_id.trim()
        : undefined;
    if (previousId) {
        return previousId;
    }
    return undefined;
}
function extractSubmitMetadata(responsesContext, resumeMeta) {
    if (responsesContext && responsesContext.metadata && isJsonObject(responsesContext.metadata)) {
        return jsonClone(responsesContext.metadata);
    }
    if (resumeMeta && resumeMeta.metadata && isJsonObject(resumeMeta.metadata)) {
        return jsonClone(resumeMeta.metadata);
    }
    return undefined;
}
function coerceOutputText(value) {
    if (typeof value === 'string') {
        return value;
    }
    if (value === undefined || value === null) {
        return '';
    }
    try {
        return JSON.stringify(value);
    }
    catch {
        return String(value);
    }
}
function extractCapturedToolOutputs(responsesContext) {
    if (!responsesContext || typeof responsesContext !== 'object') {
        return [];
    }
    const snapshot = responsesContext.__captured_tool_results;
    if (!Array.isArray(snapshot) || !snapshot.length) {
        return [];
    }
    const entries = [];
    snapshot.forEach((entry) => {
        if (!entry || typeof entry !== 'object') {
            return;
        }
        const record = entry;
        const toolId = typeof record.tool_call_id === 'string' && record.tool_call_id.trim().length
            ? record.tool_call_id.trim()
            : typeof record.call_id === 'string' && record.call_id.trim().length
                ? record.call_id.trim()
                : undefined;
        if (!toolId) {
            return;
        }
        entries.push({
            tool_call_id: toolId,
            id: toolId,
            output: typeof record.output === 'string' ? record.output : coerceOutputText(record.output),
            name: typeof record.name === 'string' ? record.name : undefined
        });
    });
    return entries;
}
function collectSubmitToolOutputs(chat, responsesContext) {
    const outputs = [];
    const seen = new Set();
    const append = (idSeed, outputSeed, name) => {
        const trimmed = typeof idSeed === 'string' && idSeed.trim().length ? idSeed.trim() : '';
        const fallbackId = trimmed || `submit_tool_${outputs.length + 1}`;
        if (seen.has(fallbackId)) {
            return;
        }
        seen.add(fallbackId);
        outputs.push({
            tool_call_id: fallbackId,
            id: fallbackId,
            output: coerceOutputText(outputSeed),
            name
        });
    };
    if (Array.isArray(chat.toolOutputs) && chat.toolOutputs.length) {
        chat.toolOutputs.forEach((entry) => {
            append(entry.tool_call_id ?? entry.call_id, entry.content, entry.name);
        });
    }
    if (!outputs.length) {
        const captured = extractCapturedToolOutputs(responsesContext);
        captured.forEach((entry) => append(entry.tool_call_id ?? entry.id, entry.output, entry.name));
    }
    if (!outputs.length) {
        const resume = readResponsesResumeFromSemantics(chat);
        if (resume) {
            const resumeOutputs = deriveResumeToolOutputsFromResume(resume);
            if (resumeOutputs?.length) {
                resumeOutputs.forEach((entry) => append(entry.tool_call_id, entry.content, entry.name));
            }
        }
    }
    return outputs;
}
function resolveSubmitStreamFlag(chat, _ctx, responsesContext) {
    if (chat.parameters && typeof chat.parameters.stream === 'boolean') {
        return chat.parameters.stream;
    }
    if (responsesContext && typeof responsesContext.stream === 'boolean') {
        return responsesContext.stream;
    }
    return undefined;
}
function resolveSubmitModel(chat, responsesContext) {
    const direct = chat.parameters && typeof chat.parameters.model === 'string'
        ? chat.parameters.model.trim()
        : undefined;
    if (direct) {
        return direct;
    }
    if (responsesContext && typeof responsesContext.parameters === 'object') {
        const params = responsesContext.parameters;
        const model = typeof params.model === 'string' ? params.model.trim() : undefined;
        if (model) {
            return model;
        }
    }
    return undefined;
}
function buildSubmitToolOutputsPayload(chat, ctx, responsesContext) {
    const resumeMeta = (() => {
        try {
            const resume = readResponsesResumeFromSemantics(chat);
            return resume && typeof resume === 'object' && !Array.isArray(resume) ? resume : undefined;
        }
        catch {
            return undefined;
        }
    })();
    const responseId = resolveSubmitResponseId(resumeMeta, responsesContext);
    if (!responseId) {
        throw new Error('Submit tool outputs requires response_id from Responses resume context');
    }
    const toolOutputs = collectSubmitToolOutputs(chat, responsesContext);
    if (!toolOutputs.length) {
        throw new Error('Submit tool outputs requires at least one tool output entry');
    }
    const payload = {
        response_id: responseId,
        tool_outputs: toolOutputs
    };
    const modelValue = resolveSubmitModel(chat, responsesContext);
    if (modelValue) {
        payload.model = modelValue;
    }
    const streamValue = resolveSubmitStreamFlag(chat, ctx, responsesContext);
    if (typeof streamValue === 'boolean') {
        payload.stream = streamValue;
    }
    const metadata = extractSubmitMetadata(responsesContext, resumeMeta);
    if (metadata) {
        payload.metadata = metadata;
    }
    return payload;
}
export class ResponsesSemanticMapper {
    async toChat(format, ctx) {
        const payload = format.payload || {};
        const responsesContext = captureResponsesContext(payload, { route: { requestId: ctx.requestId } });
        const { request, toolsNormalized } = buildChatRequestFromResponses(payload, responsesContext);
        const missingFields = [];
        const messages = normalizeMessages(request.messages, missingFields);
        let toolOutputs = mapToolOutputs(payload.tool_outputs, missingFields);
        const parameters = collectParameters(payload, responsesContext.stream);
        const metadata = { context: ctx };
        if (missingFields.length) {
            metadata.missingFields = missingFields;
        }
        // Keep responses protocol semantics in chat.semantics (not metadata).
        const semantics = attachResponsesSemantics(undefined, responsesContext, undefined);
        return {
            messages,
            tools: normalizeTools(toolsNormalized, missingFields),
            toolOutputs,
            parameters,
            semantics,
            metadata
        };
    }
    async fromChat(chat, ctx) {
        const envelopeMetadata = chat.metadata && isJsonObject(chat.metadata) ? chat.metadata : undefined;
        const responsesContext = selectResponsesContextSnapshot(chat, envelopeMetadata);
        if (isSubmitToolOutputsEndpoint(ctx)) {
            const submitPayload = buildSubmitToolOutputsPayload(chat, ctx, responsesContext);
            return {
                protocol: 'openai-responses',
                direction: 'response',
                payload: submitPayload,
                meta: {
                    context: ctx,
                    submitToolOutputs: true
                }
            };
        }
        const modelValue = chat.parameters?.model;
        if (typeof modelValue !== 'string' || !modelValue.trim()) {
            throw new Error('ChatEnvelope.parameters.model is required for openai-responses outbound conversion');
        }
        const requestShape = {
            ...(chat.parameters || {}),
            model: modelValue,
            messages: chat.messages,
            tools: chat.tools
        };
        const originalSystemMessages = (chat.messages || [])
            .filter((message) => Boolean(message && typeof message === 'object' && message.role === 'system'))
            .map(message => serializeSystemContent(message))
            .filter((content) => typeof content === 'string' && content.length > 0);
        responsesContext.originalSystemMessages = originalSystemMessages;
        const responsesResult = buildResponsesRequestFromChat(requestShape, responsesContext);
        const responses = responsesResult.request;
        if (chat.parameters && chat.parameters.stream !== undefined) {
            responses.stream = chat.parameters.stream;
        }
        // Do not forward ChatEnvelope.toolOutputs to OpenAI Responses create requests.
        // Upstream expects historical tool results to remain inside input[] as
        // tool role messages; sending the legacy top-level `tool_outputs` field
        // causes providers like FAI to reject the request (HTTP 400). Any actual
        // submit_tool_outputs call should be issued via the dedicated endpoint
        // upstream, not through this mapper.
        const result = {
            protocol: 'openai-responses',
            direction: 'response',
            payload: responses,
            meta: {
                context: ctx
            }
        };
        // Sampling knobs should remain intact for OpenAI Responses protocol.
        // Provider-specific removal belongs in compatibility profiles, not here.
        return result;
    }
}
