import OpenAI from 'openai';
import { Agent, hostedMcpTool, OpenAIProvider, setDefaultModelProvider, setDefaultOpenAIClient, setDefaultOpenAIKey, setOpenAIAPI, tool, addTraceProcessor, getGlobalTraceProvider, setTracingExportApiKey } from '@openai/agents';
import { ATTACHMENT_TOOLS_PREAMBLE } from '../../';
const TRACE_LOG_PREFIX = '[OpenAI Trace]';
const toSerializable = (value) => {
    if (typeof value.toJSON === 'function') {
        const json = value.toJSON();
        if (json) {
            return json;
        }
    }
    const fallback = {};
    for (const key of Object.keys(value)) {
        const item = value[key];
        if (typeof item === 'function') {
            continue;
        }
        fallback[key] = item;
    }
    return fallback;
};
const logTrace = (stage, trace) => {
    const payload = {
        traceId: trace.traceId,
        name: trace.name,
        groupId: trace.groupId,
        metadata: trace.metadata,
        raw: toSerializable(trace)
    };
    console.log(`${TRACE_LOG_PREFIX} ${stage}`, payload);
};
const logSpan = (stage, span) => {
    const payload = {
        traceId: span.traceId,
        spanId: span.spanId,
        parentId: span.parentId,
        startedAt: span.startedAt,
        endedAt: span.endedAt,
        data: span.spanData,
        error: span.error,
        raw: toSerializable(span)
    };
    console.log(`${TRACE_LOG_PREFIX} ${stage}`, payload);
};
const consoleLogTrace = {
    start() {
        console.log(`${TRACE_LOG_PREFIX} processor attached`);
    },
    async onTraceStart(trace) {
        logTrace('trace:start', trace);
    },
    async onTraceEnd(trace) {
        logTrace('trace:end', trace);
    },
    async onSpanStart(span) {
        logSpan('span:start', span);
    },
    async onSpanEnd(span) {
        logSpan('span:end', span);
    },
    async shutdown() {
        console.log(`${TRACE_LOG_PREFIX} processor shutdown`);
    },
    async forceFlush() {
        console.log(`${TRACE_LOG_PREFIX} force flush noop`);
    }
};
let tracingProcessorRegistered = false;
let tracingForcedFlushWarningLogged = false;
const isPlainObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
const coercePositiveInteger = (value) => {
    if (value === undefined || value === null) {
        return undefined;
    }
    const numeric = typeof value === 'string' ? Number(value.trim()) : Number(value);
    if (!Number.isFinite(numeric) || numeric <= 0) {
        return undefined;
    }
    return Math.round(numeric);
};
const normalizeModelSettings = (modelName, rawSettings) => {
    if (!rawSettings) {
        return undefined;
    }
    const normalized = { ...rawSettings };
    const modelId = typeof modelName === 'string' ? modelName.trim().toLowerCase() : '';
    const isGpt5 = modelId.startsWith('gpt-5');
    const applyReasoningEffort = () => {
        const effort = typeof normalized.reasoningEffort === 'string' ? normalized.reasoningEffort.trim() : undefined;
        if (!effort) {
            if (isPlainObject(normalized.reasoning)) {
                normalized.reasoning = { ...normalized.reasoning };
            }
            return;
        }
        const base = isPlainObject(normalized.reasoning) ? { ...normalized.reasoning } : {};
        base.effort = effort;
        normalized.reasoning = base;
    };
    const applyTextVerbosity = () => {
        const verbosity = typeof normalized.textVerbosity === 'string' ? normalized.textVerbosity.trim() : undefined;
        if (!verbosity) {
            if (isPlainObject(normalized.text)) {
                normalized.text = { ...normalized.text };
            }
            return;
        }
        const base = isPlainObject(normalized.text) ? { ...normalized.text } : {};
        base.verbosity = verbosity;
        normalized.text = base;
    };
    applyReasoningEffort();
    applyTextVerbosity();
    delete normalized.reasoningEffort;
    delete normalized.textVerbosity;
    const maxTokens = coercePositiveInteger(normalized.maxTokens);
    if (maxTokens !== undefined) {
        normalized.maxTokens = maxTokens;
    }
    else {
        delete normalized.maxTokens;
    }
    if (isGpt5) {
        delete normalized.temperature;
        delete normalized.topP;
    }
    return normalized;
};
const normalizeToolPermissions = (preferences) => {
    if (!preferences) {
        return [];
    }
    return Object.entries(preferences);
};
const isLikelyPrivateNetwork = (url) => {
    if (typeof url !== 'string') {
        return true;
    }
    const trimmed = url.trim();
    if (trimmed.length === 0) {
        return true;
    }
    try {
        const parsed = new URL(trimmed);
        const hostname = parsed.hostname.toLowerCase();
        if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {
            return true;
        }
        if (hostname.endsWith('.local')) {
            return true;
        }
        if (/^10\./.test(hostname) || /^192\.168\./.test(hostname) || /^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)) {
            return true;
        }
        return false;
    }
    catch {
        return true;
    }
};
const warnIfInaccessibleMcpServer = (serverId, serverUrl) => {
    const accessible = !isLikelyPrivateNetwork(serverUrl);
    if (!accessible) {
        console.warn(`MCP server "${serverId}" uses a URL that is likely inaccessible to OpenAI hosted models (${serverUrl ?? 'missing URL'}).` +
            ' GPT-5 will fail when it cannot reach the server dependency. Expose the server over a public HTTPS endpoint or disable it for this model.');
    }
    return accessible;
};
const normalizeServerLabel = (id) => {
    const raw = typeof id === 'string' ? id.trim() : '';
    if (raw.length === 0) {
        return 'mcp_default';
    }
    const withoutPrefix = raw.replace(/^(mcp[_-])/, '');
    return `mcp_${withoutPrefix}`;
};
let attachmentToolDefinitionsResolver = null;
let currentOpenAIClient = null;
let currentOpenAIProvider = null;
let currentOpenAIProviderOptions = null;
export const getCurrentOpenAIClient = () => currentOpenAIClient;
export const getCurrentOpenAIProvider = () => currentOpenAIProvider;
export const getCurrentOpenAIProviderOptions = () => {
    if (!currentOpenAIProviderOptions) {
        return null;
    }
    return { ...currentOpenAIProviderOptions };
};
export const setAttachmentToolDefinitionsResolver = (resolver) => {
    attachmentToolDefinitionsResolver = resolver;
};
export class OpenAIChatAgent extends Agent {
    constructor(props, resolveAttachmentTools) {
        const normalizedModelSettings = normalizeModelSettings(props.modelName, props.modelSettings);
        const resolvedOptions = {
            ...props,
            ...(normalizedModelSettings !== undefined ? { modelSettings: normalizedModelSettings } : {})
        };
        console.log('Creating ChatAgent with options:', resolvedOptions);
        const resolver = resolveAttachmentTools ?? attachmentToolDefinitionsResolver;
        if (!resolver) {
            throw new Error('Attachment tool definitions resolver is not configured. Ensure the OpenAI provider plugin was registered with the playground.');
        }
        const allServers = resolvedOptions.mcpServers ?? [];
        const modelId = typeof resolvedOptions.modelName === 'string' ? resolvedOptions.modelName.trim().toLowerCase() : '';
        const requiresPublicMcpServers = modelId.startsWith('gpt-5');
        const unreachableServers = [];
        const enabledServers = allServers.filter((server) => {
            const isEnabled = server.options.serverEnabled ?? true;
            if (!isEnabled) {
                return false;
            }
            const accessible = warnIfInaccessibleMcpServer(server.id, server.options.url);
            if (!accessible && requiresPublicMcpServers) {
                unreachableServers.push({
                    id: server.id,
                    name: server.options.serverName,
                    url: server.options.url
                });
                return false;
            }
            return true;
        });
        if (requiresPublicMcpServers && unreachableServers.length > 0) {
            const serverSummary = unreachableServers
                .map((entry) => {
                const label = entry.name && entry.name.trim().length > 0 ? entry.name : entry.id;
                const url = entry.url ?? 'missing URL';
                return `• ${label} (${url})`;
            })
                .join('\n');
            throw new Error('GPT-5 runs on OpenAI-hosted infrastructure and cannot reach MCP servers on private networks. ' +
                'Update these servers to use a publicly accessible HTTPS endpoint or disable them before invoking GPT-5:\n' +
                serverSummary);
        }
        const askFirstSummaries = [];
        const mcpTools = enabledServers.map((server) => {
            const permissions = normalizeToolPermissions(server.options.toolPreferences);
            const allowedTools = permissions
                .filter(([, permission]) => permission !== 'disabled')
                .map(([name]) => name);
            const askFirstTools = permissions
                .filter(([, permission]) => permission === 'ask_first')
                .map(([name]) => name);
            if (askFirstTools.length > 0) {
                askFirstSummaries.push({
                    serverLabel: server.options.serverName ?? server.id,
                    tools: askFirstTools
                });
            }
            console.log(`MCP Server "${server.options.serverName}" allowed tools:`, allowedTools);
            if (askFirstTools.length > 0) {
                console.log(`MCP Server "${server.options.serverName}" tools requiring approval:`, askFirstTools);
            }
            const serverLabel = normalizeServerLabel(server.id);
            if (serverLabel !== server.id) {
                console.log(`Using normalized MCP server label "${serverLabel}" for server id "${server.id}"`);
            }
            const baseOptions = {
                serverLabel,
                serverUrl: server.options.url,
                headers: typeof server.options.headers === 'function' ? undefined : server.options.headers,
                ...(allowedTools.length > 0 ? { allowedTools } : {})
            };
            if (askFirstTools.length > 0) {
                return hostedMcpTool({
                    ...baseOptions,
                    requireApproval: {
                        always: {
                            toolNames: askFirstTools
                        }
                    }
                });
            }
            return hostedMcpTool(baseOptions);
        });
        const attachmentToolDefinitions = resolver();
        const attachmentTools = attachmentToolDefinitions.map((definition) => {
            const baseOptions = {
                name: definition.name,
                description: definition.description,
                parameters: definition.parameters,
                strict: true,
                async execute(rawInput, runContext) {
                    let parsedInput;
                    if (typeof rawInput === 'string') {
                        try {
                            parsedInput = JSON.parse(rawInput);
                        }
                        catch (error) {
                            console.warn(`Failed to parse input for attachment tool "${definition.name}"`, error);
                            parsedInput = undefined;
                        }
                    }
                    else if (rawInput && typeof rawInput === 'object') {
                        parsedInput = rawInput;
                    }
                    const context = (runContext?.context ?? {});
                    const runtime = {
                        attachments: context.attachments,
                        additionalPrompts: context.additionalPrompts,
                        getAttachmentCache: resolvedOptions.getAttachmentCache
                    };
                    const result = await definition.handler(parsedInput, runtime);
                    return JSON.stringify(result);
                }
            };
            if (definition.needsApproval) {
                return tool({
                    ...baseOptions,
                    needsApproval: definition.needsApproval
                });
            }
            return tool(baseOptions);
        });
        const customTools = (resolvedOptions.additionalTools ?? []).map((definition) => tool(definition));
        const tools = [...attachmentTools, ...mcpTools, ...customTools];
        if (resolvedOptions.apiMode) {
            setOpenAIAPI(resolvedOptions.apiMode);
        }
        const combinedInstructions = [ATTACHMENT_TOOLS_PREAMBLE, resolvedOptions.instructions ?? '']
            .filter((segment) => typeof segment === 'string' && segment.trim().length > 0)
            .join('\n\n');
        const trimmedApiKey = typeof resolvedOptions.apiKey === 'string' ? resolvedOptions.apiKey.trim() : '';
        if (!trimmedApiKey) {
            throw new Error('OpenAI API key is required to initialize the OpenAI chat agent.');
        }
        setTracingExportApiKey(trimmedApiKey);
        const client = createOpenAIClient({
            ...resolvedOptions,
            apiKey: trimmedApiKey
        });
        console.log('Created OpenAI client:', client);
        setDefaultOpenAIKey(trimmedApiKey);
        setDefaultOpenAIClient(client);
        const providerOptions = {
            openAIClient: client
        };
        if (resolvedOptions.apiMode) {
            providerOptions.useResponses = resolvedOptions.apiMode !== 'chat_completions';
        }
        currentOpenAIClient = client;
        currentOpenAIProviderOptions = { ...providerOptions };
        currentOpenAIProvider = new OpenAIProvider(providerOptions);
        setDefaultModelProvider(currentOpenAIProvider);
        const traceProvider = getGlobalTraceProvider();
        traceProvider.setDisabled(false);
        if (!tracingProcessorRegistered) {
            addTraceProcessor(consoleLogTrace);
            tracingProcessorRegistered = true;
        }
        void traceProvider.forceFlush?.().catch((error) => {
            if (!tracingForcedFlushWarningLogged) {
                console.warn('[OpenAI Trace] forceFlush unavailable or failed:', error);
                tracingForcedFlushWarningLogged = true;
            }
        });
        super({
            name: 'Chat Agent',
            instructions: combinedInstructions,
            model: resolvedOptions.modelName,
            modelSettings: resolvedOptions.modelSettings,
            outputType: 'text',
            tools
        });
        Object.defineProperty(this, "options", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.options = { ...resolvedOptions };
    }
}
export const normalizeBaseUrl = (explicitBaseUrl, connection) => {
    const candidates = [explicitBaseUrl];
    if (connection) {
        candidates.push(connection['baseUrl'], connection['baseURL']);
    }
    for (const candidate of candidates) {
        if (typeof candidate === 'string' && candidate.trim().length > 0) {
            return candidate.trim();
        }
    }
    return undefined;
};
export const normalizeClientHeaders = (headers) => {
    if (!headers) {
        return undefined;
    }
    const entries = Object.entries(headers)
        .map(([key, value]) => [key.trim(), value])
        .filter(([key, value]) => key.length > 0 && typeof value === 'string');
    if (entries.length === 0) {
        return undefined;
    }
    return Object.fromEntries(entries);
};
export const createOpenAIClient = (options) => {
    const baseUrl = normalizeBaseUrl(options.baseUrl, options.connection);
    const clientHeaders = normalizeClientHeaders(options.clientHeaders);
    const opts = {
        apiKey: options.apiKey,
        dangerouslyAllowBrowser: true,
        ...(baseUrl ? { baseURL: baseUrl } : {}),
        ...(clientHeaders ? { defaultHeaders: clientHeaders } : {}),
        ...(options.logLevel ? { logLevel: options.logLevel } : {})
    };
    return new OpenAI(opts);
};
