import { config as defaultConfig } from "../config.js";
import { errInfo } from "./errInfo.js";
import { parseJson, parseStream, safeDiagnosticLabel } from "./responsesParse.js";
import { GENERATE_DEVELOPER_PROMPT, GENERATE_NO_SEARCH_DEVELOPER_PROMPT, buildUserTextPrompt, waitForOAuthReady, } from "./oauthProxy.js";
const BUILTIN_PROMPT_ID = "builtin_cat";
const BUILTIN_CAT_PROMPT = "고양이";
const TEXT_OK_PROMPT = "Reply with exactly OK.";
function imageTool(size, quality, moderation) {
    return { type: "image_generation", size, quality, moderation, action: "generate" };
}
function createProbeSpecs({ model, size, quality, moderation, prompt, matrix, }) {
    const imageOnlyTools = [imageTool(size, quality, moderation)];
    const webSearchImageTools = [{ type: "web_search" }, imageTool(size, quality, moderation)];
    const specs = [
        {
            id: "text_sanity",
            expectation: "text",
            promptId: "builtin_text_ok",
            promptChars: TEXT_OK_PROMPT.length,
            stream: false,
            toolTypes: [],
            toolChoiceKind: "none",
            payload: {
                model,
                input: [{ role: "user", content: TEXT_OK_PROMPT }],
                stream: false,
            },
        },
        {
            id: "minimal_image_non_stream",
            expectation: "image",
            promptId: BUILTIN_PROMPT_ID,
            promptChars: prompt.length,
            stream: false,
            toolTypes: ["image_generation"],
            toolChoiceKind: "image_generation",
            payload: {
                model,
                input: [{ role: "user", content: prompt }],
                tools: imageOnlyTools,
                tool_choice: { type: "image_generation" },
                stream: false,
            },
        },
        {
            id: "minimal_image_stream",
            expectation: "image",
            promptId: BUILTIN_PROMPT_ID,
            promptChars: prompt.length,
            stream: true,
            toolTypes: ["image_generation"],
            toolChoiceKind: "image_generation",
            payload: {
                model,
                input: [{ role: "user", content: prompt }],
                tools: imageOnlyTools,
                tool_choice: { type: "image_generation" },
                stream: true,
            },
        },
    ];
    if (!matrix)
        return specs;
    return [
        ...specs,
        {
            id: "current_payload_no_search_required",
            expectation: "image",
            promptId: BUILTIN_PROMPT_ID,
            promptChars: prompt.length,
            stream: true,
            toolTypes: ["image_generation"],
            toolChoiceKind: "required",
            payload: {
                model,
                input: [
                    { role: "developer", content: GENERATE_NO_SEARCH_DEVELOPER_PROMPT },
                    { role: "user", content: buildUserTextPrompt(prompt, "auto", { webSearchEnabled: false }) },
                ],
                tools: imageOnlyTools,
                tool_choice: "required",
                reasoning: { effort: "low" },
                stream: true,
            },
        },
        {
            id: "current_payload_web_search_required",
            expectation: "image",
            promptId: BUILTIN_PROMPT_ID,
            promptChars: prompt.length,
            stream: true,
            toolTypes: ["web_search", "image_generation"],
            toolChoiceKind: "required",
            payload: {
                model,
                input: [
                    { role: "developer", content: GENERATE_DEVELOPER_PROMPT },
                    { role: "user", content: buildUserTextPrompt(prompt, "auto", { webSearchEnabled: true }) },
                ],
                tools: webSearchImageTools,
                tool_choice: "required",
                reasoning: { effort: "low" },
                stream: true,
            },
        },
        {
            id: "current_payload_web_search_forced_image",
            expectation: "image",
            promptId: BUILTIN_PROMPT_ID,
            promptChars: prompt.length,
            stream: true,
            toolTypes: ["web_search", "image_generation"],
            toolChoiceKind: "image_generation",
            payload: {
                model,
                input: [
                    { role: "developer", content: GENERATE_DEVELOPER_PROMPT },
                    { role: "user", content: buildUserTextPrompt(prompt, "auto", { webSearchEnabled: true }) },
                ],
                tools: webSearchImageTools,
                tool_choice: { type: "image_generation" },
                reasoning: { effort: "low" },
                stream: true,
            },
        },
    ];
}
async function endpointFor(provider, options) {
    const ctx = options.ctx || { config: defaultConfig };
    if (provider === "api") {
        const headers = {
            "Content-Type": "application/json",
            Accept: "text/event-stream, application/json",
            Authorization: apiAuthorizationHeader(options.apiKey),
        };
        return {
            url: "https://api.openai.com/v1/responses",
            headers,
        };
    }
    await waitForOAuthReady(ctx);
    const port = ctx.config?.oauth?.proxyPort || defaultConfig.oauth.proxyPort;
    const baseUrl = safeOAuthBaseUrl(options.oauthUrl || `http://127.0.0.1:${port}`);
    const headers = {
        "Content-Type": "application/json",
        Accept: "text/event-stream, application/json",
    };
    return {
        url: `${baseUrl}/v1/responses`,
        headers,
    };
}
function apiAuthorizationHeader(apiKey) {
    const key = typeof apiKey === "string" ? apiKey.trim() : "";
    if (!key) {
        throw Object.assign(new Error("API key is required for API provider image probe"), {
            code: "API_KEY_REQUIRED",
        });
    }
    if (/[\u0000-\u001f\u007f]/.test(key)) {
        throw Object.assign(new Error("API key contains invalid characters."), {
            code: "AUTH_API_KEY_INVALID",
        });
    }
    return `Bearer ${key}`;
}
function safeOAuthBaseUrl(value) {
    try {
        const parsed = new URL(value);
        parsed.username = "";
        parsed.password = "";
        return parsed.toString().replace(/\/$/, "");
    }
    catch {
        return value.replace(/\/$/, "");
    }
}
function sanitizeProbeErrorMessage(value) {
    return value
        .replace(/([a-z][a-z0-9+.-]*:\/\/)([^/\s@]+)@/gi, "$1[redacted]@")
        .replace(/([?&](?:access_token|api_key|key|secret|token)=)[^&\s]+/gi, "$1[redacted]")
        .replace(/Bearer\s+[^"'\s]+/gi, "Bearer [redacted]")
        .replace(/sk-[A-Za-z0-9_-]+/g, "sk-[redacted]");
}
function safeUpstreamError(text) {
    try {
        const parsed = JSON.parse(text);
        const error = parsed?.error || {};
        return {
            code: safeDiagnosticLabel(error.code),
            type: safeDiagnosticLabel(error.type),
            param: safeDiagnosticLabel(error.param),
        };
    }
    catch {
        return { code: null, type: null, param: null };
    }
}
function reasonFrom(result) {
    const diagnostics = result.response.diagnostics;
    if (!result.error && result.ok)
        return null;
    if (result.error?.code)
        return result.error.code;
    if (!diagnostics)
        return "probe_failed";
    if (diagnostics.streamStats.bytesRead > 0 && result.response.eventCount === 0)
        return "stream_parse_failed";
    if (diagnostics.imageCallFailed)
        return "image_tool_failed";
    if (diagnostics.imageCallCompleted && diagnostics.imageResultCount === 0)
        return "image_tool_completed_without_result";
    if (!diagnostics.imageCallSeen && result.response.webSearchCalls > 0)
        return "web_search_only_response";
    if (!diagnostics.imageCallSeen && diagnostics.messageOutputSeen)
        return "image_tool_not_called";
    if (result.expectation === "text" && result.response.textOutputChars === 0)
        return "text_output_missing";
    if (result.expectation === "image" && result.response.imageResultCount === 0)
        return "image_output_missing";
    return "probe_failed";
}
async function runSingleProbe(provider, options, spec) {
    const start = Date.now();
    let httpStatus = null;
    let contentType = null;
    let upstreamRequestId = null;
    try {
        const endpoint = await endpointFor(provider, options);
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), options.timeoutMs || defaultConfig.oauth.generationTimeoutMs);
        try {
            const res = await fetch(endpoint.url, {
                method: "POST",
                headers: endpoint.headers,
                signal: controller.signal,
                body: JSON.stringify(spec.payload),
            });
            httpStatus = res.status;
            contentType = res.headers.get("content-type");
            upstreamRequestId = res.headers.get("x-request-id") || res.headers.get("openai-request-id");
            if (!res.ok) {
                const body = await res.text();
                const upstream = safeUpstreamError(body);
                const partial = {
                    id: spec.id,
                    ok: false,
                    expectation: spec.expectation,
                    request: {
                        stream: spec.stream,
                        toolTypes: spec.toolTypes,
                        toolChoiceKind: spec.toolChoiceKind,
                        promptId: spec.promptId,
                        promptChars: spec.promptChars,
                    },
                    response: {
                        httpStatus,
                        contentType,
                        upstreamRequestId,
                        durationMs: Date.now() - start,
                        eventCount: 0,
                        eventTypes: {},
                        webSearchCalls: 0,
                        textOutputChars: 0,
                        imageResultCount: 0,
                        firstImageChars: 0,
                        diagnostics: null,
                    },
                    error: {
                        ...upstream,
                        message: "Upstream returned a non-2xx response",
                        upstreamBodyChars: body.length,
                    },
                };
                return { ...partial, diagnosticReason: reasonFrom(partial) };
            }
            const parsed = contentType?.includes("text/event-stream")
                ? await parseStream(res, { scope: "doctor-image-probe", maxImages: 1 })
                : await parseJson(res, 1);
            const ok = spec.expectation === "image"
                ? Boolean(parsed.images[0]?.b64)
                : Boolean(parsed.text && parsed.text.trim());
            const partial = {
                id: spec.id,
                ok,
                expectation: spec.expectation,
                request: {
                    stream: spec.stream,
                    toolTypes: spec.toolTypes,
                    toolChoiceKind: spec.toolChoiceKind,
                    promptId: spec.promptId,
                    promptChars: spec.promptChars,
                },
                response: {
                    httpStatus,
                    contentType,
                    upstreamRequestId,
                    durationMs: Date.now() - start,
                    eventCount: parsed.eventCount,
                    eventTypes: parsed.eventTypes,
                    webSearchCalls: parsed.webSearchCalls,
                    textOutputChars: parsed.text?.length || 0,
                    imageResultCount: parsed.images.length,
                    firstImageChars: parsed.images[0]?.b64.length || 0,
                    diagnostics: parsed.diagnostics,
                },
                error: null,
            };
            return { ...partial, diagnosticReason: reasonFrom(partial) };
        }
        finally {
            clearTimeout(timer);
        }
    }
    catch (e) {
        const err = errInfo(e);
        const partial = {
            id: spec.id,
            ok: false,
            expectation: spec.expectation,
            request: {
                stream: spec.stream,
                toolTypes: spec.toolTypes,
                toolChoiceKind: spec.toolChoiceKind,
                promptId: spec.promptId,
                promptChars: spec.promptChars,
            },
            response: {
                httpStatus,
                contentType,
                upstreamRequestId,
                durationMs: Date.now() - start,
                eventCount: Number(err.raw?.eventCount) || 0,
                eventTypes: {},
                webSearchCalls: 0,
                textOutputChars: 0,
                imageResultCount: 0,
                firstImageChars: 0,
                diagnostics: null,
            },
            error: {
                code: safeDiagnosticLabel(err.code),
                type: null,
                param: null,
                message: err.name === "AbortError" ? "Probe timed out" : sanitizeProbeErrorMessage(err.message),
            },
        };
        return { ...partial, diagnosticReason: reasonFrom(partial) };
    }
}
export async function runImageDoctorProbe(options = {}) {
    const provider = options.provider || "oauth";
    const model = options.model || defaultConfig.imageModels?.default || "gpt-5.4-mini";
    const size = options.size || "1024x1024";
    const quality = options.quality || "low";
    const moderation = options.moderation || "low";
    const prompt = options.prompt || BUILTIN_CAT_PROMPT;
    const specs = createProbeSpecs({
        model,
        size,
        quality,
        moderation,
        prompt,
        matrix: options.matrix === true,
    });
    const probes = [];
    for (const spec of specs)
        probes.push(await runSingleProbe(provider, options, spec));
    return {
        provider,
        endpointKind: provider === "api" ? "api" : "oauth",
        model,
        size,
        quality,
        moderation,
        promptId: options.prompt ? "custom" : BUILTIN_PROMPT_ID,
        promptChars: prompt.length,
        matrix: options.matrix === true,
        probes,
        summary: {
            ok: probes.every((probe) => probe.ok),
            passed: probes.filter((probe) => probe.ok).length,
            failed: probes.filter((probe) => !probe.ok).length,
        },
    };
}
