import { ProtocolConversionPipeline } from '../../index.js';
import { chatEnvelopeToStandardized } from '../../../hub/standardized-bridge.js';
import { AnthropicFormatAdapter } from '../../../hub/format-adapters/anthropic-format-adapter.js';
import { AnthropicSemanticMapper } from '../../../hub/semantic-mappers/anthropic-mapper.js';
import { buildAnthropicFromOpenAIChat } from '../../../codecs/anthropic-openai-codec.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_ANTHROPIC_ENDPOINT = '/v1/messages';
const ANTHROPIC_PROTOCOL = 'anthropic-messages';
function assertJsonObject(value, stage) {
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
        throw new Error(`Anthropic pipeline codec requires JSON object payload at ${stage}`);
    }
    return value;
}
function coerceAliasMap(candidate) {
    if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
        return undefined;
    }
    let populated = false;
    const result = {};
    for (const [key, value] of Object.entries(candidate)) {
        if (typeof key !== 'string' || typeof value !== 'string')
            continue;
        const trimmedKey = key.trim();
        if (!trimmedKey.length)
            continue;
        result[trimmedKey] = value;
        populated = true;
    }
    return populated ? result : undefined;
}
function readAliasMapFromSemantics(semantics) {
    if (!semantics || typeof semantics !== 'object' || Array.isArray(semantics)) {
        return undefined;
    }
    const toolsNode = semantics.tools;
    if (!toolsNode || typeof toolsNode !== 'object' || Array.isArray(toolsNode)) {
        return undefined;
    }
    const candidate = toolsNode.toolNameAliasMap ??
        toolsNode.toolAliasMap;
    return coerceAliasMap(candidate);
}
function ensureAliasMapInSemantics(chatEnvelope, aliasMap) {
    if (!chatEnvelope || typeof chatEnvelope !== 'object') {
        return;
    }
    const semantics = chatEnvelope.semantics && typeof chatEnvelope.semantics === 'object' && !Array.isArray(chatEnvelope.semantics)
        ? chatEnvelope.semantics
        : (() => {
            chatEnvelope.semantics = {};
            return chatEnvelope.semantics;
        })();
    const toolsNode = semantics.tools && typeof semantics.tools === 'object' && !Array.isArray(semantics.tools)
        ? semantics.tools
        : (() => {
            semantics.tools = {};
            return semantics.tools;
        })();
    if (!toolsNode.toolNameAliasMap && !toolsNode.toolAliasMap) {
        toolsNode.toolNameAliasMap = { ...aliasMap };
    }
}
function createAnthropicHooks() {
    const formatAdapter = new AnthropicFormatAdapter();
    const semanticMapper = new AnthropicSemanticMapper();
    return {
        id: 'anthropic-openai-v2',
        protocol: ANTHROPIC_PROTOCOL,
        inbound: {
            parse: async ({ wire, context }) => {
                const adapterContext = buildAdapterContextFromPipeline(context, {
                    defaultEntryEndpoint: DEFAULT_ANTHROPIC_ENDPOINT,
                    overrideProtocol: ANTHROPIC_PROTOCOL
                });
                const formatEnvelope = await formatAdapter.parseRequest(wire, adapterContext);
                const chatEnvelope = await semanticMapper.toChat(formatEnvelope, adapterContext);
                const aliasMap = readAliasMapFromSemantics(chatEnvelope.semantics);
                if (aliasMap) {
                    // A1: tool alias map is mappable semantics; keep it in chat.semantics (never metadata).
                    ensureAliasMapInSemantics(chatEnvelope, aliasMap);
                }
                const canonical = chatEnvelopeToStandardized(chatEnvelope, {
                    adapterContext,
                    endpoint: context.entryEndpoint ?? DEFAULT_ANTHROPIC_ENDPOINT,
                    requestId: context.requestId
                });
                return { canonical };
            }
        },
        outbound: {
            serialize: async ({ canonical, context }) => {
                const aliasMap = readAliasMapFromSemantics(canonical.semantics);
                const anthropicPayload = buildAnthropicFromOpenAIChat(canonical, {
                    toolNameMap: aliasMap,
                    requestId: context.requestId
                });
                return { payload: assertJsonObject(anthropicPayload, 'anthropic_outbound_serialize') };
            }
        }
    };
}
function buildPipelineContext(profile, context) {
    return {
        requestId: context.requestId,
        entryEndpoint: context.entryEndpoint ?? context.endpoint ?? DEFAULT_ANTHROPIC_ENDPOINT,
        providerProtocol: context.targetProtocol ?? profile.incomingProtocol ?? ANTHROPIC_PROTOCOL,
        targetProtocol: profile.outgoingProtocol ?? context.targetProtocol ?? OPENAI_PROTOCOL,
        profileId: profile.id,
        stream: context.stream,
        metadata: context.metadata
    };
}
export class AnthropicOpenAIPipelineCodec {
    id = 'anthropic-openai-v2';
    pipeline;
    initialized = false;
    constructor() {
        this.pipeline = new ProtocolConversionPipeline(createAnthropicHooks());
    }
    async initialize() {
        this.initialized = true;
    }
    ensureInitialized() {
        if (!this.initialized) {
            throw new Error('AnthropicOpenAIPipelineCodec must be initialized before use');
        }
    }
    async convertRequest(payload, profile, context) {
        this.ensureInitialized();
        const inboundContext = buildPipelineContext(profile, context);
        const inboundOptions = {
            payload: assertJsonObject(payload, 'anthropic_inbound_request'),
            context: inboundContext
        };
        const inbound = await this.pipeline.convertInbound(inboundOptions);
        const openaiPayload = await convertCanonicalToOpenAIChat(inbound.canonical, inbound.context);
        const filterContext = {
            ...context,
            requestId: context.requestId ?? inbound.context.requestId ?? `req_${Date.now()}`,
            entryEndpoint: context.entryEndpoint ?? context.endpoint ?? DEFAULT_ANTHROPIC_ENDPOINT,
            endpoint: context.endpoint ?? context.entryEndpoint ?? DEFAULT_ANTHROPIC_ENDPOINT
        };
        return runStandardChatRequestFilters(openaiPayload, profile, filterContext);
    }
    async convertResponse(payload, profile, context) {
        this.ensureInitialized();
        const sanitized = await canonicalizeOpenAIChatResponse(assertJsonObject(payload, 'openai_chat_response'), context);
        const outboundOptions = {
            canonical: sanitized,
            context: buildPipelineContext(profile, context)
        };
        const outbound = await this.pipeline.convertOutbound(outboundOptions);
        return outbound.payload;
    }
}
