import { errInfo } from "./errInfo.js";
import { logEvent } from "./logger.js";
import { waitForOAuthReady } from "./oauthProxy/runtime.js";
import { requireRuntimeContext } from "./runtimeContext.js";
const AGENT_QUESTION_DEVELOPER_PROMPT = [
    "You are the text-only question mode for ima2-gen Agent.",
    "Answer the user's question directly and naturally in the user's language.",
    "Do not generate images, do not call image tools, and do not turn the question into an image prompt.",
    "Do not simply repeat the user's question unless the user explicitly asks you to repeat text.",
    "Keep the answer concise and useful for an image-generation workflow when relevant.",
].join(" ");
export async function requestAgentQuestionAnswer(ctxRaw, question, options = {}) {
    try {
        const ctx = requireRuntimeContext(ctxRaw);
        const endpoint = await resolveQuestionEndpoint(ctx, options.provider);
        const timeoutMs = ctx.config.oauth?.generationTimeoutMs ?? 120_000;
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), timeoutMs);
        const signal = options.signal ? combineSignals([controller.signal, options.signal]) : controller.signal;
        try {
            const res = await fetch(endpoint.url, {
                method: "POST",
                headers: endpoint.headers,
                signal,
                body: JSON.stringify({
                    model: options.model || ctx.config.imageModels?.default || "gpt-5.4-mini",
                    input: [
                        { role: "developer", content: AGENT_QUESTION_DEVELOPER_PROMPT },
                        { role: "user", content: question },
                    ],
                    reasoning: { effort: options.reasoningEffort || "low" },
                    stream: true,
                }),
            });
            logEvent("agent_question", "response", {
                requestId: options.requestId,
                provider: options.provider,
                status: res.status,
                contentType: res.headers.get("content-type"),
            });
            if (!res.ok)
                throw await questionHttpError(res, options.provider);
            const parsed = res.headers.get("content-type")?.includes("text/event-stream")
                ? await readTextStream(res)
                : readTextBody(await res.json());
            if (!parsed.text.trim())
                throw questionError("Agent question returned an empty response", "AGENT_QUESTION_EMPTY", 502);
            return { text: parsed.text.trim().slice(0, 4_000), usage: parsed.usage };
        }
        finally {
            clearTimeout(timer);
        }
    }
    catch (error) {
        const err = errInfo(error);
        if (err.name === "AbortError") {
            throw questionError("Agent question timed out", "AGENT_QUESTION_TIMEOUT", 504, err.raw);
        }
        throw err.raw;
    }
}
async function resolveQuestionEndpoint(ctx, provider) {
    try {
        if (provider === "api") {
            if (!ctx.apiKey)
                throw questionError("API key is required for Agent question mode", "API_KEY_REQUIRED", 401);
            return {
                url: "https://api.openai.com/v1/responses",
                headers: {
                    "Content-Type": "application/json",
                    Accept: "text/event-stream",
                    Authorization: `Bearer ${ctx.apiKey}`,
                },
            };
        }
        await waitForOAuthReady(ctx);
        return {
            url: `${ctx.oauthUrl}/v1/responses`,
            headers: { "Content-Type": "application/json", Accept: "text/event-stream" },
        };
    }
    catch (error) {
        throw errInfo(error).raw;
    }
}
async function questionHttpError(res, provider) {
    try {
        const text = await res.text();
        const err = questionError(`${provider === "api" ? "OpenAI API" : "OAuth proxy"} rejected Agent question mode`, "AGENT_QUESTION_UPSTREAM_FAILED", res.status >= 400 && res.status < 600 ? res.status : 502);
        err.upstreamBodyChars = text.length;
        return err;
    }
    catch (error) {
        throw errInfo(error).raw;
    }
}
async function readTextStream(res) {
    try {
        const reader = res.body?.getReader();
        if (!reader)
            return { text: "", usage: null };
        const decoder = new TextDecoder();
        const parts = [];
        let usage = null;
        let buffer = "";
        while (true) {
            const { done, value } = await reader.read();
            if (done)
                break;
            buffer += decoder.decode(value, { stream: true });
            let boundary = buffer.indexOf("\n\n");
            while (boundary !== -1) {
                const block = buffer.slice(0, boundary);
                buffer = buffer.slice(boundary + 2);
                const event = parseSseEvent(block);
                if (event)
                    collectTextEvent(event, parts, (nextUsage) => {
                        usage = nextUsage;
                    });
                boundary = buffer.indexOf("\n\n");
            }
        }
        const finalEvent = parseSseEvent(buffer);
        if (finalEvent)
            collectTextEvent(finalEvent, parts, (nextUsage) => {
                usage = nextUsage;
            });
        return { text: parts.join("").trim(), usage };
    }
    catch (error) {
        throw errInfo(error).raw;
    }
}
function readTextBody(body) {
    if (typeof body.output_text === "string" && body.output_text.trim()) {
        return { text: body.output_text, usage: body.usage ?? null };
    }
    const parts = [];
    for (const item of body.output ?? []) {
        appendContentText(item.content, parts);
    }
    return { text: parts.join("\n\n").trim(), usage: body.usage ?? null };
}
function parseSseEvent(block) {
    const data = block
        .split("\n")
        .filter((line) => line.startsWith("data:"))
        .map((line) => line.slice(5).trimStart())
        .join("\n");
    if (!data || data === "[DONE]")
        return null;
    try {
        const parsed = JSON.parse(data);
        return parsed && typeof parsed === "object" ? parsed : null;
    }
    catch {
        return null;
    }
}
function collectTextEvent(event, parts, setUsage) {
    if (event.type === "response.output_text.delta" && typeof event.delta === "string")
        parts.push(event.delta);
    if (event.type === "response.output_text.done" && typeof event.text === "string" && parts.length === 0)
        parts.push(event.text);
    if (event.type === "response.output_item.done" && event.item?.type === "message" && parts.length === 0) {
        appendContentText(event.item.content, parts);
    }
    if ((event.type === "response.completed" || event.type === "response.incomplete") && event.response?.usage) {
        setUsage(event.response.usage);
    }
    if (event.type === "error") {
        throw questionError(event.error?.message || "Agent question stream failed", event.error?.code || "AGENT_QUESTION_STREAM_ERROR", 502);
    }
}
function appendContentText(content, parts) {
    for (const part of content ?? []) {
        if (typeof part.text === "string" && part.text.trim())
            parts.push(part.text);
        else if (part.text && typeof part.text === "object" && typeof part.text.value === "string")
            parts.push(part.text.value);
        else if (typeof part.value === "string" && part.value.trim())
            parts.push(part.value);
        else if (typeof part.refusal === "string" && part.refusal.trim())
            parts.push(part.refusal);
    }
}
function combineSignals(signals) {
    if (signals.length === 1)
        return signals[0];
    const controller = new AbortController();
    for (const signal of signals) {
        if (signal.aborted) {
            controller.abort();
            break;
        }
        signal.addEventListener("abort", () => controller.abort(), { once: true });
    }
    return controller.signal;
}
function questionError(message, code, status, cause) {
    const err = new Error(message);
    err.code = code;
    err.status = status;
    if (cause)
        err.cause = cause;
    return err;
}
