import { REASONING_BUDGET_MAP } from './interface.js';
import { ProviderError } from '../types/errors.js';
import { mapAnthropicStopReason } from './stop-reason-maps.js';
import { withRetry } from '../utils/retry.js';
import { logger } from '../utils/logger.js';
import { instrumentedLlmCall } from '../runtime/llm-observability.js';
import { toAnthropicTools, toAnthropicMessages, fromAnthropicContent, parseToolCallArguments, } from './tool-formats.js';
import { toProviderError } from '../utils/error-display.js';
import { instrumentedFetch } from '../utils/fetch-with-timeout.js';
const ANTHROPIC_API_VERSION = '2023-06-01';
/**
 * AnthropicCompatProvider — generic provider for endpoints that speak the
 * Anthropic Messages API (SSE streaming variant). Useful for self-hosted
 * proxies, Claude-compatible services, and any backend that follows the
 * Anthropic Messages API spec.
 *
 * Configured via a custom provider JSON file with `"type": "anthropic-compat"`
 * in the configured providers directory.
 */
export class AnthropicCompatProvider {
    name;
    models;
    baseURL;
    apiKey;
    defaultModel;
    defaultHeaders;
    authEnvVars;
    serviceNames;
    aliases;
    subscriptionProviderId;
    streamProtocol;
    authHeaderMode;
    allowAnonymous;
    anonymousConfigured;
    anonymousDetail;
    constructor(opts) {
        this.name = opts.name;
        this.models = opts.models;
        this.baseURL = opts.baseURL.replace(/\/$/, ''); // strip trailing slash
        this.apiKey = opts.apiKey;
        this.defaultModel = opts.defaultModel;
        this.defaultHeaders = opts.defaultHeaders ?? {};
        this.authEnvVars = opts.authEnvVars ?? [];
        this.serviceNames = opts.serviceNames ?? [];
        this.aliases = opts.aliases ?? [];
        this.subscriptionProviderId = opts.subscriptionProviderId;
        this.streamProtocol = opts.streamProtocol;
        this.authHeaderMode = opts.authHeaderMode ?? 'x-api-key';
        this.allowAnonymous = opts.allowAnonymous ?? false;
        this.anonymousConfigured = opts.anonymousConfigured ?? false;
        this.anonymousDetail = opts.anonymousDetail;
    }
    isConfigured() {
        return Boolean(this.apiKey) || this.anonymousConfigured;
    }
    async chat(params) {
        const { messages, tools, model, maxTokens, signal, systemPrompt, onDelta, reasoningEffort } = params;
        return (await instrumentedLlmCall(() => withRetry(async () => {
            const resolvedModel = model ?? this.defaultModel;
            const body = {
                model: resolvedModel,
                max_tokens: maxTokens ?? 8192,
                messages: toAnthropicMessages(messages),
                stream: true,
            };
            if (systemPrompt) {
                body['system'] = systemPrompt;
            }
            if (tools && tools.length > 0) {
                body['tools'] = toAnthropicTools(tools);
            }
            if (reasoningEffort && reasoningEffort !== 'instant') {
                const budget = REASONING_BUDGET_MAP[reasoningEffort];
                if (budget !== undefined && budget > 0) {
                    body['thinking'] = { type: 'enabled', budget_tokens: budget };
                    const currentMax = body['max_tokens'] ?? 8192;
                    if (currentMax <= budget) {
                        body['max_tokens'] = budget + 4096;
                    }
                }
            }
            const headers = {
                'Content-Type': 'application/json',
                'anthropic-version': ANTHROPIC_API_VERSION,
                ...this.defaultHeaders,
            };
            if (this.apiKey) {
                if (this.authHeaderMode === 'bearer') {
                    headers['Authorization'] = `Bearer ${this.apiKey}`;
                }
                else {
                    headers['x-api-key'] = this.apiKey;
                }
            }
            if (body['thinking']) {
                headers['anthropic-beta'] = 'interleaved-thinking-2025-05-14';
            }
            let res;
            try {
                res = await instrumentedFetch(`${this.baseURL}/messages`, {
                    method: 'POST',
                    headers,
                    body: JSON.stringify(body),
                    ...(signal !== undefined ? { signal } : {}),
                });
            }
            catch (err) {
                throw toProviderError(err, {
                    provider: this.name,
                    operation: 'chat',
                    phase: 'request',
                });
            }
            if (!res.ok) {
                const text = await res.text().catch(() => 'unknown error');
                throw new ProviderError(`AnthropicCompat(${this.name}) API error ${res.status}: ${text}`, {
                    statusCode: res.status,
                    provider: this.name,
                    operation: 'chat',
                    phase: 'request',
                });
            }
            // Parse SSE stream
            let responseText = '';
            let inputTokens = 0;
            let outputTokens = 0;
            let cacheReadTokens = 0;
            let cacheWriteTokens = 0;
            let rawStopReason;
            let stopReason = 'unknown';
            // Accumulate tool use blocks by index
            const toolBlocks = new Map();
            const reader = res.body?.getReader();
            if (!reader) {
                throw new ProviderError(`AnthropicCompat(${this.name}) returned no response body.`, {
                    statusCode: 502,
                    provider: this.name,
                    operation: 'chat',
                    phase: 'response',
                });
            }
            const decoder = new TextDecoder();
            let buffer = '';
            try {
                while (true) {
                    const { done, value } = await reader.read();
                    if (done)
                        break;
                    buffer += decoder.decode(value, { stream: true });
                    const lines = buffer.split('\n');
                    buffer = lines.pop() ?? '';
                    for (const line of lines) {
                        if (!line.startsWith('data: '))
                            continue;
                        const data = line.slice(6).trim();
                        if (!data || data === '[DONE]')
                            continue;
                        let event;
                        try {
                            event = JSON.parse(data);
                        }
                        catch {
                            logger.warn('AnthropicCompat SSE: failed to parse JSON chunk', {
                                chunkPreview: data.slice(0, 200),
                                chunkLength: data.length,
                            });
                            continue;
                        }
                        if (event.type === 'content_block_start' && event.content_block?.type === 'tool_use') {
                            const idx = event.index ?? 0;
                            toolBlocks.set(idx, {
                                id: event.content_block.id ?? '',
                                name: event.content_block.name ?? '',
                                args: '',
                            });
                            if (onDelta) {
                                onDelta({ toolCalls: [{ index: idx, id: event.content_block.id, name: event.content_block.name }] });
                            }
                        }
                        else if (event.type === 'content_block_delta') {
                            const idx = event.index ?? 0;
                            if (event.delta?.type === 'text_delta' && event.delta.text) {
                                responseText += event.delta.text;
                                if (onDelta)
                                    onDelta({ content: event.delta.text });
                            }
                            else if (event.delta?.type === 'thinking_delta' && event.delta.thinking) {
                                if (onDelta)
                                    onDelta({ reasoning: event.delta.thinking });
                            }
                            else if (event.delta?.type === 'input_json_delta' && event.delta.partial_json) {
                                const block = toolBlocks.get(idx);
                                if (block)
                                    block.args += event.delta.partial_json;
                                if (onDelta) {
                                    onDelta({ toolCalls: [{ index: idx, arguments: event.delta.partial_json }] });
                                }
                            }
                        }
                        else if (event.type === 'message_delta') {
                            if (event.delta?.stop_reason) {
                                rawStopReason = event.delta.stop_reason;
                                stopReason = mapAnthropicStopReason(rawStopReason);
                            }
                            if (event.usage?.output_tokens)
                                outputTokens = event.usage.output_tokens;
                            if (event.usage?.cache_read_input_tokens != null)
                                cacheReadTokens = event.usage.cache_read_input_tokens;
                            if (event.usage?.cache_creation_input_tokens != null)
                                cacheWriteTokens = event.usage.cache_creation_input_tokens;
                        }
                        else if (event.type === 'message_start') {
                            if (event.message?.usage) {
                                inputTokens = event.message.usage.input_tokens;
                                outputTokens = event.message.usage.output_tokens;
                                cacheReadTokens = event.message.usage.cache_read_input_tokens ?? 0;
                                cacheWriteTokens = event.message.usage.cache_creation_input_tokens ?? 0;
                            }
                        }
                    }
                }
            }
            finally {
                reader.releaseLock();
            }
            // Assemble content blocks
            const contentBlocks = [];
            if (responseText) {
                contentBlocks.push({ type: 'text', text: responseText });
            }
            for (const [, block] of [...toolBlocks.entries()].sort(([a], [b]) => a - b)) {
                const parsedInput = parseToolCallArguments(block.args, {
                    provider: this.name,
                    toolName: block.name,
                    callId: block.id,
                });
                if (parsedInput === undefined)
                    continue;
                contentBlocks.push({
                    type: 'tool_use',
                    id: block.id,
                    name: block.name,
                    input: parsedInput,
                });
            }
            const { text, toolCalls } = fromAnthropicContent(contentBlocks);
            return {
                content: text,
                toolCalls,
                usage: { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens },
                stopReason: stopReason === 'unknown' && text ? 'completed' : stopReason,
                ...(rawStopReason !== undefined ? { providerStopReason: rawStopReason } : {}),
            };
        }), { provider: this.name, model: model ?? this.defaultModel })).result;
    }
    async describeRuntime(deps) {
        const { buildStandardProviderAuthRoutes } = await import('./runtime-metadata.js');
        const authRoutes = await buildStandardProviderAuthRoutes({
            providerId: this.name,
            apiKeyEnvVars: this.authEnvVars,
            secretKeys: this.authEnvVars,
            serviceNames: this.serviceNames,
            ...(this.subscriptionProviderId ? { subscriptionProviderId: this.subscriptionProviderId } : {}),
            allowAnonymous: this.allowAnonymous,
            anonymousConfigured: this.anonymousConfigured,
            anonymousDetail: this.anonymousDetail,
        }, deps);
        return {
            auth: {
                mode: this.allowAnonymous && !this.apiKey ? 'anonymous' : 'api-key',
                configured: Boolean(this.apiKey) || this.anonymousConfigured,
                detail: this.apiKey
                    ? `API key for ${this.name} is available`
                    : this.allowAnonymous
                        ? (this.anonymousDetail ?? `${this.name} can be used without a stored API key`)
                        : `API key for ${this.name} is not configured`,
                ...(this.authEnvVars.length > 0 ? { envVars: this.authEnvVars } : {}),
                routes: authRoutes,
            },
            models: {
                defaultModel: this.defaultModel,
                models: this.models,
                ...(this.aliases.length > 0 ? { aliases: this.aliases } : {}),
            },
            usage: {
                streaming: true,
                toolCalling: true,
                parallelTools: true,
                promptCaching: true,
                notes: ['Anthropic prompt caching and thinking controls are surfaced through the compat wrapper.'],
            },
            policy: {
                local: false,
                streamProtocol: this.streamProtocol ?? 'anthropic-sse',
                reasoningMode: 'thinking_budget',
                supportedReasoningEfforts: ['instant', 'low', 'medium', 'high'],
                cacheStrategy: 'anthropic-prompt-cache',
            },
        };
    }
}
