import { ProtocolConversionPipeline } from '../../index.js';
import { chatEnvelopeToStandardized } from '../../../hub/standardized-bridge.js';
import { ResponsesFormatAdapter } from '../../../hub/format-adapters/responses-format-adapter.js';
import { ResponsesSemanticMapper } from '../../../hub/semantic-mappers/responses-mapper.js';
import { captureResponsesContext, buildChatRequestFromResponses, buildResponsesPayloadFromChat } from '../../../responses/responses-openai-bridge.js';
import { buildAdapterContextFromPipeline } from '../../hooks/adapter-context.js';
import { runStandardChatRequestFilters } from '../../../index.js';
import { canonicalizeOpenAIChatResponse, convertStandardizedToOpenAIChat as convertCanonicalToOpenAIChat, OPENAI_PROTOCOL } from './shared/openai-chat-helpers.js';
const DEFAULT_RESPONSES_ENDPOINT = '/v1/responses';
const RESPONSES_PROTOCOL = 'openai-responses';
function assertJsonObject(value, stage) {
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
        throw new Error(`Responses pipeline codec requires JSON payload at ${stage}`);
    }
    return value;
}
function sanitizeResponsesMessages(payload) {
    const messages = Array.isArray(payload?.messages) ? payload.messages : [];
    if (!messages.length)
        return;
    const sanitized = [];
    let counter = 0;
    const normalizeId = (raw) => {
        if (typeof raw !== 'string')
            return undefined;
        const trimmed = raw.trim();
        if (!trimmed.length)
            return undefined;
        if (trimmed.startsWith('fc_'))
            return trimmed;
        const stripped = trimmed.replace(/^call[_-]?/i, '');
        const normalized = stripped.length ? stripped : trimmed;
        return normalized.startsWith('fc_') ? normalized : `fc_${normalized}`;
    };
    for (const message of messages) {
        if (!message || typeof message !== 'object')
            continue;
        const role = String(message.role || '').toLowerCase();
        if (role === 'assistant' && Array.isArray(message.tool_calls) && message.tool_calls.length) {
            message.tool_calls = message.tool_calls.map((call) => {
                if (!call || typeof call !== 'object')
                    return call;
                const existing = normalizeId(call.id) ?? normalizeId(call.call_id) ?? `fc_function_call_${counter++}`;
                if (existing) {
                    call.id = existing;
                }
                if (call.call_id !== undefined)
                    delete call.call_id;
                if (call.tool_call_id !== undefined)
                    delete call.tool_call_id;
                return call;
            });
            sanitized.push(message);
            continue;
        }
        if (role === 'tool') {
            const clone = { ...message };
            const normalizedToolId = normalizeId(clone.tool_call_id ?? clone.call_id);
            if (normalizedToolId) {
                clone.tool_call_id = normalizedToolId;
            }
            else if (typeof clone.tool_call_id === 'string') {
                const trimmed = clone.tool_call_id.trim();
                if (!trimmed.length) {
                    delete clone.tool_call_id;
                }
                else {
                    clone.tool_call_id = trimmed;
                }
            }
            if ('call_id' in clone)
                delete clone.call_id;
            if ('id' in clone)
                delete clone.id;
            sanitized.push(clone);
            continue;
        }
        sanitized.push(message);
    }
    payload.messages = sanitized;
}
function cloneResponsesContext(context) {
    try {
        return JSON.parse(JSON.stringify(context ?? {}));
    }
    catch {
        return {};
    }
}
function restoreResponsesContext(value) {
    if (!value || typeof value !== 'object') {
        return undefined;
    }
    return value;
}
function captureToolResults(payload) {
    const results = [];
    const inputArr = Array.isArray(payload?.input) ? payload.input : [];
    for (const it of inputArr) {
        if (!it || typeof it !== 'object')
            continue;
        const t = String(it.type || '').toLowerCase();
        if (t === 'tool_result' || t === 'tool_message' || t === 'function_call_output') {
            const tool_call_id = it.tool_call_id || it.call_id || it.tool_use_id;
            let output = undefined;
            const rawOut = it.output;
            if (typeof rawOut === 'string')
                output = rawOut;
            else if (rawOut && typeof rawOut === 'object') {
                try {
                    output = JSON.stringify(rawOut);
                }
                catch { /* ignore */ }
            }
            results.push({ tool_call_id, output });
        }
    }
    return results;
}
function createResponsesHooks() {
    const formatAdapter = new ResponsesFormatAdapter();
    const semanticMapper = new ResponsesSemanticMapper();
    return {
        id: 'responses-openai-v2',
        protocol: RESPONSES_PROTOCOL,
        inbound: {
            parse: async ({ wire, context, meta }) => {
                const adapterContext = buildAdapterContextFromPipeline(context, {
                    defaultEntryEndpoint: DEFAULT_RESPONSES_ENDPOINT,
                    overrideProtocol: RESPONSES_PROTOCOL
                });
                const formatEnvelope = await formatAdapter.parseRequest(wire, adapterContext);
                const chatEnvelope = await semanticMapper.toChat(formatEnvelope, adapterContext);
                const canonical = chatEnvelopeToStandardized(chatEnvelope, {
                    adapterContext,
                    endpoint: adapterContext.entryEndpoint,
                    requestId: adapterContext.requestId
                });
                // Capture Responses-specific context for outbound mapping parity
                const responsesContext = captureResponsesContext(wire, { route: { requestId: adapterContext.requestId } });
                const built = buildChatRequestFromResponses(wire, responsesContext);
                if (built.toolsNormalized) {
                    responsesContext.toolsNormalized = built.toolsNormalized;
                }
                const captured = captureToolResults(wire);
                if (captured.length) {
                    responsesContext.__captured_tool_results = captured;
                }
                meta.set('responsesContext', cloneResponsesContext(responsesContext));
                return { canonical };
            }
        },
        outbound: {
            serialize: async ({ canonical, meta }) => {
                const stored = meta.consume('responsesContext');
                const responsesContext = restoreResponsesContext(stored);
                const payload = buildResponsesPayloadFromChat(canonical, responsesContext);
                return { payload: assertJsonObject(payload, 'responses_outbound_serialize') };
            }
        }
    };
}
function buildPipelineContext(profile, context) {
    return {
        requestId: context.requestId,
        entryEndpoint: context.entryEndpoint ?? context.endpoint ?? DEFAULT_RESPONSES_ENDPOINT,
        providerProtocol: profile.incomingProtocol ?? RESPONSES_PROTOCOL,
        targetProtocol: profile.outgoingProtocol ?? context.targetProtocol ?? OPENAI_PROTOCOL,
        profileId: profile.id,
        stream: context.stream,
        metadata: context.metadata
    };
}
export class ResponsesOpenAIPipelineCodec {
    id = 'responses-openai-v2';
    pipeline;
    requestMetaStore = new Map();
    initialized = false;
    constructor() {
        this.pipeline = new ProtocolConversionPipeline(createResponsesHooks());
    }
    async initialize() {
        this.initialized = true;
    }
    ensureInitialized() {
        if (!this.initialized) {
            throw new Error('ResponsesOpenAIPipelineCodec must be initialized before use');
        }
    }
    stashMeta(requestId, bag) {
        this.requestMetaStore.set(requestId, bag.snapshot());
    }
    async convertRequest(payload, profile, context) {
        this.ensureInitialized();
        const inboundContext = buildPipelineContext(profile, context);
        const requestId = context.requestId ?? inboundContext.requestId ?? `req_${Date.now()}`;
        inboundContext.requestId = requestId;
        const inboundOptions = {
            payload: assertJsonObject(payload, 'responses_inbound_request'),
            context: inboundContext
        };
        const inbound = await this.pipeline.convertInbound(inboundOptions);
        this.stashMeta(requestId, inbound.meta);
        const openaiPayload = await convertCanonicalToOpenAIChat(inbound.canonical, inbound.context);
        const filterContext = {
            ...context,
            requestId,
            entryEndpoint: context.entryEndpoint ?? DEFAULT_RESPONSES_ENDPOINT,
            endpoint: context.endpoint ?? DEFAULT_RESPONSES_ENDPOINT
        };
        const filtered = await runStandardChatRequestFilters(openaiPayload, profile, filterContext);
        sanitizeResponsesMessages(filtered);
        return filtered;
    }
    async convertResponse(payload, profile, context) {
        this.ensureInitialized();
        const pipelineContext = buildPipelineContext(profile, context);
        const requestId = context.requestId ?? pipelineContext.requestId ?? `req_${Date.now()}`;
        pipelineContext.requestId = requestId;
        const storedMeta = this.requestMetaStore.get(requestId);
        if (storedMeta) {
            this.requestMetaStore.delete(requestId);
        }
        const sanitized = await canonicalizeOpenAIChatResponse(assertJsonObject(payload, 'responses_openai_response'), context);
        const outboundOptions = {
            canonical: sanitized,
            context: pipelineContext,
            meta: storedMeta
        };
        const outbound = await this.pipeline.convertOutbound(outboundOptions);
        return outbound.payload;
    }
}
