import { isJsonObject, jsonClone } from '../../types/json.js';
import { buildOpenAIChatFromAnthropic, buildAnthropicRequestFromOpenAIChat } from '../../../codecs/anthropic-openai-codec.js';
import { encodeMetadataPassthrough, extractMetadataPassthrough } from '../../../metadata-passthrough.js';
import { buildAnthropicToolAliasMapWithNative } from '../../../../router/virtual-router/engine-selection/native-chat-process-governance-semantics.js';
import { ChatSemanticMapper } from './chat-mapper.js';
const ANTHROPIC_PARAMETER_KEYS = [
    'model',
    'temperature',
    'top_p',
    'top_k',
    'max_tokens',
    'max_output_tokens',
    'metadata',
    'stream',
    'tool_choice',
    'thinking'
];
const ANTHROPIC_TOP_LEVEL_FIELDS = new Set([
    'model',
    'messages',
    'tools',
    'system',
    'stop_sequences',
    'temperature',
    'top_p',
    'top_k',
    'max_tokens',
    'max_output_tokens',
    'metadata',
    'stream',
    'tool_choice',
    'thinking'
]);
const PASSTHROUGH_METADATA_PREFIX = 'rcc_passthrough_';
const PASSTHROUGH_PARAMETERS = ['tool_choice'];
const RESPONSES_DROPPED_PARAMETER_KEYS = [
    'prompt_cache_key',
    'response_format',
    'parallel_tool_calls',
    'service_tier',
    'truncation',
    'include',
    'store'
];
function ensureSemantics(chat) {
    if (!chat.semantics || typeof chat.semantics !== 'object') {
        chat.semantics = {};
    }
    return chat.semantics;
}
function ensureToolsSemanticsNode(chat) {
    const semantics = ensureSemantics(chat);
    if (!semantics.tools || !isJsonObject(semantics.tools)) {
        semantics.tools = {};
    }
    return semantics.tools;
}
function markExplicitEmptyTools(chat) {
    const semantics = ensureSemantics(chat);
    if (!semantics.tools || !isJsonObject(semantics.tools)) {
        semantics.tools = {};
    }
    semantics.tools.explicitEmpty = true;
}
function readToolsSemantics(chat) {
    if (!chat.semantics || typeof chat.semantics !== 'object') {
        return undefined;
    }
    const node = chat.semantics.tools;
    return node && isJsonObject(node) ? node : undefined;
}
function hasExplicitEmptyToolsSemantics(chat) {
    if (!chat.semantics || typeof chat.semantics !== 'object') {
        return false;
    }
    const toolsNode = chat.semantics.tools;
    if (!toolsNode || !isJsonObject(toolsNode)) {
        return false;
    }
    return Boolean(toolsNode.explicitEmpty);
}
function sanitizeAnthropicPayload(payload) {
    for (const key of Object.keys(payload)) {
        if (!ANTHROPIC_TOP_LEVEL_FIELDS.has(key)) {
            delete payload[key];
        }
    }
    return payload;
}
function collectParameters(payload) {
    const params = {};
    for (const key of ANTHROPIC_PARAMETER_KEYS) {
        if (payload[key] !== undefined) {
            params[key] = payload[key];
        }
    }
    if (Array.isArray(payload.stop_sequences)) {
        params.stop = payload.stop_sequences;
    }
    return Object.keys(params).length ? params : undefined;
}
function mapReasoningEffortToAnthropicBudget(effort) {
    const normalized = effort.trim().toLowerCase();
    if (normalized === 'minimal' || normalized === 'low')
        return 1024;
    if (normalized === 'medium')
        return 4096;
    if (normalized === 'high')
        return 8192;
    return 4096;
}
function buildAnthropicThinkingFromReasoning(reasoning) {
    if (reasoning === undefined || reasoning === null) {
        return undefined;
    }
    if (typeof reasoning === 'boolean') {
        if (!reasoning) {
            return { type: 'disabled' };
        }
        return { type: 'enabled', budget_tokens: 4096 };
    }
    if (typeof reasoning === 'string') {
        const normalized = reasoning.trim().toLowerCase();
        if (!normalized.length) {
            return undefined;
        }
        if (normalized === 'off' || normalized === 'none' || normalized === 'disabled' || normalized === 'false') {
            return { type: 'disabled' };
        }
        return {
            type: 'enabled',
            budget_tokens: mapReasoningEffortToAnthropicBudget(normalized)
        };
    }
    if (typeof reasoning === 'number' && Number.isFinite(reasoning)) {
        const budget = Math.max(0, Math.floor(reasoning));
        return budget <= 0
            ? { type: 'disabled' }
            : { type: 'enabled', budget_tokens: budget };
    }
    if (!isJsonObject(reasoning)) {
        return undefined;
    }
    const node = reasoning;
    const enabledRaw = node.enabled;
    if (enabledRaw === false) {
        return { type: 'disabled' };
    }
    const effortRaw = typeof node.effort === 'string'
        ? node.effort
        : typeof node.level === 'string'
            ? node.level
            : undefined;
    const budgetRaw = typeof node.budget_tokens === 'number'
        ? node.budget_tokens
        : typeof node.budget === 'number'
            ? node.budget
            : typeof node.max_tokens === 'number'
                ? node.max_tokens
                : undefined;
    if (typeof budgetRaw === 'number' && Number.isFinite(budgetRaw)) {
        const budget = Math.max(0, Math.floor(budgetRaw));
        return budget <= 0
            ? { type: 'disabled' }
            : { type: 'enabled', budget_tokens: budget };
    }
    if (typeof effortRaw === 'string' && effortRaw.trim().length) {
        const normalized = effortRaw.trim().toLowerCase();
        if (normalized === 'off' || normalized === 'none' || normalized === 'disabled') {
            return { type: 'disabled' };
        }
        return {
            type: 'enabled',
            budget_tokens: mapReasoningEffortToAnthropicBudget(normalized)
        };
    }
    if (enabledRaw === true) {
        return { type: 'enabled', budget_tokens: 4096 };
    }
    return { type: 'enabled', budget_tokens: 4096 };
}
function normalizeContextToken(value) {
    return typeof value === 'string' ? value.trim().toLowerCase() : '';
}
function isArkCodingPlanContext(ctx) {
    if (!ctx || typeof ctx !== 'object') {
        return false;
    }
    const candidates = [
        normalizeContextToken(ctx.providerId),
        normalizeContextToken(ctx.providerKey),
        normalizeContextToken(ctx.runtimeKey)
    ];
    return candidates.some((candidate) => candidate === 'ark-coding-plan' || candidate.startsWith('ark-coding-plan.'));
}
function cloneAnthropicSystemBlocks(value) {
    if (value === undefined || value === null) {
        return undefined;
    }
    const blocks = Array.isArray(value) ? value : [value];
    if (!blocks.length) {
        return undefined;
    }
    return blocks.map((entry) => jsonClone(entry));
}
function isResponsesOrigin(chat) {
    const semantics = chat?.semantics;
    if (semantics && semantics.responses && isJsonObject(semantics.responses)) {
        return true;
    }
    const ctx = chat?.metadata && typeof chat.metadata === 'object'
        ? chat.metadata.context
        : undefined;
    const protocol = typeof ctx?.providerProtocol === 'string' ? ctx.providerProtocol.trim().toLowerCase() : '';
    if (protocol === 'openai-responses') {
        return true;
    }
    const endpoint = typeof ctx?.entryEndpoint === 'string' ? ctx.entryEndpoint.trim().toLowerCase() : '';
    return endpoint === '/v1/responses';
}
function appendMappingAudit(chat, options) {
    const metadata = chat.metadata && typeof chat.metadata === 'object'
        ? chat.metadata
        : (chat.metadata = { context: chat.metadata?.context ?? {} });
    const root = metadata.mappingAudit && typeof metadata.mappingAudit === 'object' && !Array.isArray(metadata.mappingAudit)
        ? metadata.mappingAudit
        : (metadata.mappingAudit = {});
    const current = Array.isArray(root[options.bucket]) ? root[options.bucket] : [];
    const duplicate = current.find((entry) => entry &&
        entry.field === options.field &&
        entry.targetProtocol === options.targetProtocol &&
        entry.reason === options.reason);
    if (!duplicate) {
        current.push({
            field: options.field,
            source: options.source ?? 'chat.parameters',
            targetProtocol: options.targetProtocol,
            reason: options.reason
        });
    }
    root[options.bucket] = current;
}
function appendDroppedFieldAudit(chat, options) {
    appendMappingAudit(chat, {
        bucket: 'dropped',
        ...options
    });
}
function appendLossyFieldAudit(chat, options) {
    appendMappingAudit(chat, {
        bucket: 'lossy',
        ...options
    });
}
export class AnthropicSemanticMapper {
    chatMapper = new ChatSemanticMapper();
    async toChat(format, ctx) {
        const payload = (format.payload ?? {});
        const missing = [];
        if (!Array.isArray(payload.messages))
            missing.push({ path: 'messages', reason: 'absent' });
        if (typeof payload.model !== 'string')
            missing.push({ path: 'model', reason: 'absent' });
        const passthrough = extractMetadataPassthrough(payload.metadata, {
            prefix: PASSTHROUGH_METADATA_PREFIX,
            keys: PASSTHROUGH_PARAMETERS
        });
        const openaiPayload = buildOpenAIChatFromAnthropic(payload);
        const canonicalContext = {
            ...ctx,
            providerProtocol: 'openai-chat',
            entryEndpoint: ctx.entryEndpoint || '/v1/chat/completions'
        };
        const chatEnvelope = await this.chatMapper.toChat({
            protocol: 'openai-chat',
            direction: 'request',
            payload: openaiPayload
        }, canonicalContext);
        const metadata = chatEnvelope.metadata ?? { context: canonicalContext };
        chatEnvelope.metadata = metadata;
        metadata.context = canonicalContext;
        const semantics = ensureSemantics(chatEnvelope);
        if (!semantics.system || !isJsonObject(semantics.system)) {
            semantics.system = {};
        }
        if (!semantics.providerExtras || !isJsonObject(semantics.providerExtras)) {
            semantics.providerExtras = {};
        }
        const systemBlocks = cloneAnthropicSystemBlocks(payload.system);
        if (systemBlocks) {
            semantics.system.blocks = jsonClone(systemBlocks);
        }
        if (payload.tools && Array.isArray(payload.tools) && payload.tools.length === 0) {
            markExplicitEmptyTools(chatEnvelope);
        }
        const aliasMap = buildAnthropicToolAliasMapWithNative(payload.tools);
        if (aliasMap) {
            const toolsNode = ensureToolsSemanticsNode(chatEnvelope);
            toolsNode.toolNameAliasMap = jsonClone(aliasMap);
        }
        if (Array.isArray(payload.messages) && payload.messages.length) {
            const shapes = payload.messages.map((entry) => {
                if (!entry || typeof entry !== 'object') {
                    return 'unknown';
                }
                const rawContent = entry.content;
                if (typeof rawContent === 'string') {
                    return 'string';
                }
                if (Array.isArray(rawContent)) {
                    return 'array';
                }
                if (rawContent === null || rawContent === undefined) {
                    return 'null';
                }
                return typeof rawContent;
            });
            const mirrorNode = { messageContentShape: shapes };
            semantics.providerExtras.anthropicMirror = jsonClone(mirrorNode);
        }
        if (missing.length) {
            metadata.missingFields = Array.isArray(metadata.missingFields)
                ? [...metadata.missingFields, ...missing]
                : missing;
        }
        const providerMetadata = passthrough.metadata ??
            (payload.metadata && isJsonObject(payload.metadata) ? jsonClone(payload.metadata) : undefined);
        if (providerMetadata) {
            semantics.providerExtras.providerMetadata = jsonClone(providerMetadata);
        }
        const mergedParameters = { ...(chatEnvelope.parameters ?? {}) };
        const mergeParameters = (source) => {
            if (!source) {
                return;
            }
            for (const [key, value] of Object.entries(source)) {
                if (mergedParameters[key] !== undefined) {
                    continue;
                }
                mergedParameters[key] = jsonClone(value);
            }
        };
        mergeParameters(collectParameters(payload));
        if (providerMetadata) {
            mergedParameters.metadata = jsonClone(providerMetadata);
        }
        if (passthrough.passthrough) {
            for (const [key, value] of Object.entries(passthrough.passthrough)) {
                mergedParameters[key] = jsonClone(value);
            }
        }
        if (Object.keys(mergedParameters).length) {
            chatEnvelope.parameters = mergedParameters;
        }
        else {
            delete chatEnvelope.parameters;
        }
        return chatEnvelope;
    }
    async fromChat(chat, ctx) {
        const model = chat.parameters?.model;
        if (typeof model !== 'string' || !model.trim()) {
            throw new Error('ChatEnvelope.parameters.model is required for anthropic-messages outbound conversion');
        }
        const baseRequest = {
            model,
            messages: chat.messages,
            tools: chat.tools
        };
        const explicitEmptyTools = hasExplicitEmptyToolsSemantics(chat);
        const trimmedParameters = chat.parameters && typeof chat.parameters === 'object' ? chat.parameters : undefined;
        const responsesOrigin = isResponsesOrigin(chat);
        if (trimmedParameters && responsesOrigin) {
            for (const field of RESPONSES_DROPPED_PARAMETER_KEYS) {
                if (!Object.prototype.hasOwnProperty.call(trimmedParameters, field)) {
                    continue;
                }
                appendDroppedFieldAudit(chat, {
                    field,
                    targetProtocol: 'anthropic-messages',
                    reason: 'unsupported_semantics_no_equivalent'
                });
            }
        }
        if (trimmedParameters) {
            for (const [key, value] of Object.entries(trimmedParameters)) {
                if (ANTHROPIC_TOP_LEVEL_FIELDS.has(key) || key === 'stop') {
                    if (key === 'messages' || key === 'tools') {
                        continue;
                    }
                    baseRequest[key] = value;
                }
            }
        }
        const passthroughMetadata = encodeMetadataPassthrough(chat.parameters, {
            prefix: PASSTHROUGH_METADATA_PREFIX,
            keys: PASSTHROUGH_PARAMETERS
        });
        if (passthroughMetadata) {
            const rawMetadata = baseRequest.metadata;
            const existingMetadata = isJsonObject(rawMetadata)
                ? jsonClone(rawMetadata)
                : {};
            for (const [key, value] of Object.entries(passthroughMetadata)) {
                existingMetadata[key] = value;
            }
            baseRequest.metadata = existingMetadata;
        }
        if (baseRequest.max_output_tokens && !baseRequest.max_tokens) {
            baseRequest.max_tokens = baseRequest.max_output_tokens;
        }
        const rawReasoning = trimmedParameters?.reasoning;
        const mappedThinking = buildAnthropicThinkingFromReasoning(rawReasoning);
        if (mappedThinking && baseRequest.thinking === undefined) {
            baseRequest.thinking = mappedThinking;
        }
        if (baseRequest.thinking === undefined &&
            rawReasoning === undefined &&
            isArkCodingPlanContext(ctx)) {
            baseRequest.thinking = {
                type: 'enabled',
                budget_tokens: mapReasoningEffortToAnthropicBudget('high')
            };
        }
        if (responsesOrigin && trimmedParameters && Object.prototype.hasOwnProperty.call(trimmedParameters, 'reasoning')) {
            appendLossyFieldAudit(chat, {
                field: 'reasoning',
                targetProtocol: 'anthropic-messages',
                reason: 'normalized_to_anthropic_thinking_budget'
            });
        }
        // 出站阶段不再直接透传其它协议的 providerMetadata，避免跨协议打洞；
        // Anthropic 自身入口的 metadata 已在入站阶段通过 collectParameters/encodeMetadataPassthrough
        // 按白名单收集，这里仅依赖这些显式映射结果。
        if (explicitEmptyTools && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
            baseRequest.tools = [];
        }
        try {
            const sysNode = chat.semantics && typeof chat.semantics === 'object' ? chat.semantics.system : undefined;
            if (sysNode && typeof sysNode === 'object' && !Array.isArray(sysNode) && sysNode.blocks !== undefined) {
                baseRequest.system = jsonClone(sysNode.blocks);
            }
        }
        catch {
            // ignore
        }
        try {
            const extras = chat.semantics && typeof chat.semantics === 'object' ? chat.semantics.providerExtras : undefined;
            const mirror = extras && typeof extras === 'object' && !Array.isArray(extras) ? extras.anthropicMirror : undefined;
            if (mirror && typeof mirror === 'object' && !Array.isArray(mirror)) {
                baseRequest.__anthropicMirror = jsonClone(mirror);
            }
            const providerMetadata = extras && typeof extras === 'object' && !Array.isArray(extras) ? extras.providerMetadata : undefined;
            if (providerMetadata && typeof providerMetadata === 'object' && !Array.isArray(providerMetadata)) {
                // Only for anthropic-native endpoint: allow restoring anthropic metadata back into outbound.
                const existing = baseRequest.metadata && isJsonObject(baseRequest.metadata)
                    ? jsonClone(baseRequest.metadata)
                    : {};
                const merged = {
                    ...existing,
                    ...jsonClone(providerMetadata)
                };
                baseRequest.metadata = merged;
            }
        }
        catch {
            // ignore
        }
        const payloadSource = buildAnthropicRequestFromOpenAIChat(baseRequest);
        const payload = sanitizeAnthropicPayload(JSON.parse(JSON.stringify(payloadSource)));
        sanitizeAnthropicPayload(payload);
        return {
            protocol: 'anthropic-messages',
            direction: 'response',
            payload,
            meta: {
                context: ctx
            }
        };
    }
}
