/**
 * ARIA-patched cli-to-openai adapter.
 *
 * Erweitert die npm-Version von claude-max-api-proxy:
 *  - normalizeModelName ist null-safe (Original-Patch der vorher per sed lief).
 *  - Parser fuer <tool_call name="X">{json}</tool_call>-Bloecke im Result-Text:
 *    Wenn welche gefunden werden, wandert das in `message.tool_calls`
 *    (OpenAI-Format) und finish_reason=tool_calls. Der restliche Text
 *    (alles ausserhalb der Bloecke) wird verworfen, weil das interner
 *    Tool-Use-Schritt war, nicht User-facing.
 *
 *  Wird zur Container-Startzeit ueber die npm-Version geschrieben
 *  (siehe docker-compose.yml proxy-Block).
 */

import { randomUUID } from "crypto";

export function extractTextContent(message) {
    return message.message.content
        .filter((c) => c.type === "text")
        .map((c) => c.text)
        .join("");
}

export function cliToOpenaiChunk(message, requestId, isFirst = false) {
    const text = extractTextContent(message);
    return {
        id: `chatcmpl-${requestId}`,
        object: "chat.completion.chunk",
        created: Math.floor(Date.now() / 1000),
        model: normalizeModelName(message.message.model),
        choices: [
            {
                index: 0,
                delta: {
                    role: isFirst ? "assistant" : undefined,
                    content: text,
                },
                finish_reason: message.message.stop_reason ? "stop" : null,
            },
        ],
    };
}

export function createDoneChunk(requestId, model) {
    return {
        id: `chatcmpl-${requestId}`,
        object: "chat.completion.chunk",
        created: Math.floor(Date.now() / 1000),
        model: normalizeModelName(model),
        choices: [
            {
                index: 0,
                delta: {},
                finish_reason: "stop",
            },
        ],
    };
}

/**
 * Sucht im Result-Text alle <tool_call name="...">{json}</tool_call>
 * Bloecke. Gibt [{id, name, arguments(json-string)}, restText] zurueck.
 *
 * Defensiv:
 *  - "name"-Attribut sowohl in Doppel- als auch Einzelhochkommata
 *  - Whitespace beim JSON tolerant
 *  - Bei JSON-Parse-Fehler: das Argument wird als _raw weitergereicht
 *    (unser Brain-Side-Parser kennt das)
 */
function _parseToolCalls(text) {
    if (!text || typeof text !== "string") return { tool_calls: [], rest: text || "" };
    const re = /<tool_call\s+name=["']([^"']+)["']\s*>([\s\S]*?)<\/tool_call>/gi;
    const tcs = [];
    let lastIndex = 0;
    const restParts = [];
    let m;
    while ((m = re.exec(text)) !== null) {
        restParts.push(text.slice(lastIndex, m.index));
        const name = m[1];
        let argsBody = (m[2] || "").trim();
        // Fences entfernen falls Claude welche eingebaut hat
        argsBody = argsBody.replace(/^```(?:json)?\s*/i, "").replace(/```\s*$/, "").trim();
        if (!argsBody) argsBody = "{}";
        // Validieren — aber in OpenAI-Format ist arguments immer ein STRING
        try {
            JSON.parse(argsBody);
        } catch (_) {
            // Behalten als Roh-String — Brain-Side toleriert das via {_raw:...}
        }
        tcs.push({
            id: `call_${randomUUID().replace(/-/g, "").slice(0, 24)}`,
            type: "function",
            function: { name, arguments: argsBody },
        });
        lastIndex = re.lastIndex;
    }
    restParts.push(text.slice(lastIndex));
    return { tool_calls: tcs, rest: restParts.join("").trim() };
}

export function cliResultToOpenai(result, requestId) {
    const modelName = result.modelUsage
        ? Object.keys(result.modelUsage)[0]
        : "claude-sonnet-4";

    const rawText = result.result || "";
    const { tool_calls, rest } = _parseToolCalls(rawText);

    const message = { role: "assistant" };
    let finishReason = "stop";
    if (tool_calls.length > 0) {
        message.tool_calls = tool_calls;
        // Wenn Claude neben den Tool-Calls noch Text geschrieben hat, behalten
        // wir den im content — Brain-Seite kann ihn als Pre-Tool-Plaintext sehen.
        // Wenn nur Tool-Calls da waren (rest leer), content explizit null.
        message.content = rest || null;
        finishReason = "tool_calls";
    } else {
        message.content = rawText;
    }

    return {
        id: `chatcmpl-${requestId}`,
        object: "chat.completion",
        created: Math.floor(Date.now() / 1000),
        model: normalizeModelName(modelName),
        choices: [
            { index: 0, message, finish_reason: finishReason },
        ],
        usage: {
            prompt_tokens: result.usage?.input_tokens || 0,
            completion_tokens: result.usage?.output_tokens || 0,
            total_tokens:
                (result.usage?.input_tokens || 0) + (result.usage?.output_tokens || 0),
        },
    };
}

function normalizeModelName(model) {
    const m = model || "claude-sonnet-4";
    if (m.includes("opus")) return "claude-opus-4";
    if (m.includes("sonnet")) return "claude-sonnet-4";
    if (m.includes("haiku")) return "claude-haiku-4";
    return m;
}
