import { completeSimple } from "@earendil-works/pi-ai/compat";
import { normalizeGoogleUsage, normalizeTokenUsage } from "../usage.js";
import { resolveGoogleModel } from "./models.js";
import { bytesToBase64, resolveBaseUrlOverride } from "./shared.js";
function parseGoogleErrorJson(value) {
    const trimmed = value.trim();
    if (!trimmed)
        return null;
    for (const candidate of [
        trimmed,
        trimmed.slice(trimmed.indexOf("{"), trimmed.lastIndexOf("}") + 1),
    ]) {
        if (!candidate.startsWith("{") || !candidate.endsWith("}"))
            continue;
        try {
            return JSON.parse(candidate);
        }
        catch { }
    }
    return null;
}
function extractGoogleErrorMessage(value) {
    if (typeof value === "string") {
        const trimmed = value.trim();
        if (!trimmed)
            return null;
        const parsed = parseGoogleErrorJson(trimmed);
        if (parsed) {
            const nested = extractGoogleErrorMessage(parsed);
            if (nested)
                return nested;
        }
        return trimmed;
    }
    if (!value || typeof value !== "object")
        return null;
    const record = value;
    return (extractGoogleErrorMessage(record.error) ??
        extractGoogleErrorMessage(record.message) ??
        extractGoogleErrorMessage(record.details));
}
function extractGoogleErrorStatusCode(value) {
    if (typeof value === "string") {
        const trimmed = value.trim();
        if (!trimmed)
            return null;
        const parsed = parseGoogleErrorJson(trimmed);
        return parsed ? extractGoogleErrorStatusCode(parsed) : null;
    }
    if (!value || typeof value !== "object")
        return null;
    const record = value;
    const code = typeof record.code === "number"
        ? record.code
        : typeof record.code === "string" && /^\d{3}$/.test(record.code.trim())
            ? Number(record.code)
            : null;
    if (code !== null && Number.isInteger(code) && code >= 100 && code <= 599)
        return code;
    return (extractGoogleErrorStatusCode(record.error) ??
        extractGoogleErrorStatusCode(record.message) ??
        extractGoogleErrorStatusCode(record.details));
}
export function normalizeGoogleAssistantError(response, modelId) {
    const raw = typeof response?.errorMessage === "string" ? response.errorMessage : "";
    if (response?.stopReason === "aborted") {
        return new DOMException(raw.trim() || `Google request aborted for model "${modelId}".`, "AbortError");
    }
    if (!raw.trim() && response?.stopReason !== "error")
        return null;
    const message = extractGoogleErrorMessage(raw) ?? `Google request failed for model "${modelId}".`;
    const statusCode = extractGoogleErrorStatusCode(raw);
    const error = /not found|not supported|Call ListModels/i.test(message)
        ? new Error(`Google API rejected model "${modelId}": ${message}`)
        : new Error(`Google request failed for model "${modelId}": ${message}`);
    if (statusCode !== null)
        error.statusCode = statusCode;
    return error;
}
function extractGoogleResponseText(content) {
    return content
        .filter((block) => block.type === "text")
        .map((block) => block.text ?? "")
        .join("")
        .trim();
}
export async function completeGoogleText({ modelId, apiKey, context, temperature, maxOutputTokens, signal, googleBaseUrlOverride, }) {
    const model = resolveGoogleModel({ modelId, context, googleBaseUrlOverride });
    const result = await completeSimple(model, context, {
        ...(typeof temperature === "number" ? { temperature } : {}),
        ...(typeof maxOutputTokens === "number" ? { maxTokens: maxOutputTokens } : {}),
        apiKey,
        signal,
    });
    const normalizedError = normalizeGoogleAssistantError(result, modelId);
    if (normalizedError)
        throw normalizedError;
    const text = extractGoogleResponseText(result.content);
    if (!text)
        throw new Error(`LLM returned an empty summary (model google/${modelId}).`);
    return { text, usage: normalizeTokenUsage(result.usage) };
}
export async function completeGoogleDocument({ modelId, apiKey, promptText, document, maxOutputTokens, temperature, timeoutMs, fetchImpl, googleBaseUrlOverride, }) {
    if (document.kind !== "document") {
        throw new Error("Internal error: expected a document attachment for Google.");
    }
    const baseUrl = resolveBaseUrlOverride(googleBaseUrlOverride) ??
        "https://generativelanguage.googleapis.com/v1beta";
    const url = new URL(`${baseUrl.replace(/\/$/, "")}/models/${modelId}:generateContent`);
    url.searchParams.set("key", apiKey);
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), timeoutMs);
    const generationConfig = {};
    if (typeof maxOutputTokens === "number")
        generationConfig.maxOutputTokens = maxOutputTokens;
    if (typeof temperature === "number")
        generationConfig.temperature = temperature;
    const payload = {
        contents: [
            {
                parts: [
                    {
                        inline_data: {
                            mime_type: document.mediaType,
                            data: bytesToBase64(document.bytes),
                        },
                    },
                    { text: promptText },
                ],
            },
        ],
        ...(Object.keys(generationConfig).length > 0 ? { generationConfig } : {}),
    };
    try {
        const response = await fetchImpl(url, {
            method: "POST",
            headers: { "content-type": "application/json" },
            body: JSON.stringify(payload),
            signal: controller.signal,
        });
        const bodyText = await response.text();
        if (!response.ok) {
            const detail = extractGoogleErrorMessage(bodyText);
            const hint = detail ? `: ${detail}` : "";
            const error = new Error(`Google API error (${response.status})${hint}.`);
            error.statusCode = response.status;
            error.responseBody = bodyText;
            throw error;
        }
        const data = JSON.parse(bodyText);
        const text = (data.candidates ?? [])
            .flatMap((candidate) => candidate.content?.parts ?? [])
            .map((part) => (typeof part.text === "string" ? part.text : ""))
            .join("")
            .trim();
        if (!text) {
            throw new Error(`LLM returned an empty summary (model google/${modelId}).`);
        }
        return { text, usage: normalizeGoogleUsage(data.usageMetadata) };
    }
    finally {
        clearTimeout(timeout);
    }
}
//# sourceMappingURL=google.js.map