/**
 * ARIA-patched openai-to-cli adapter.
 *
 * Erweitert die npm-Version von claude-max-api-proxy:
 *  - Multimodal-Content (Array von text-Parts) wird zu String reduziert.
 *  - Wenn die Anfrage ein `tools`-Feld enthaelt: die Tool-Definitionen
 *    werden in den Prompt als <system>-Block injiziert, mit klarer
 *    Anweisung das <tool_call name="...">{...}</tool_call> Format
 *    zu verwenden statt freiem Text.
 *  - Wenn Messages role=tool enthalten: deren Inhalt wird als
 *    <tool_result tool_call_id="...">…</tool_result> ins Prompt-Fragment
 *    eingewoben damit Claude den Loop-Step bekommt.
 *
 * Wird zur Container-Startzeit ueber die npm-Version geschrieben
 * (siehe docker-compose.yml proxy-Block).
 */

const MODEL_MAP = {
    "claude-opus-4": "opus",
    "claude-sonnet-4": "sonnet",
    "claude-haiku-4": "haiku",
    "claude-code-cli/claude-opus-4": "opus",
    "claude-code-cli/claude-sonnet-4": "sonnet",
    "claude-code-cli/claude-haiku-4": "haiku",
    "opus": "opus",
    "sonnet": "sonnet",
    "haiku": "haiku",
};

export function extractModel(model) {
    if (MODEL_MAP[model]) return MODEL_MAP[model];
    const stripped = (model || "").replace(/^claude-code-cli\//, "");
    if (MODEL_MAP[stripped]) return MODEL_MAP[stripped];
    return "opus";
}

/** Multimodal: content kann String oder Array von Parts sein. */
function _text(c) {
    if (typeof c === "string") return c;
    if (Array.isArray(c)) {
        return c
            .filter((b) => b && b.type === "text")
            .map((b) => b.text || "")
            .join("");
    }
    return String(c == null ? "" : c);
}

/**
 * Baut den Tool-Use-Block fuer den System-Prompt.
 * Anweisung: Claude soll <tool_call name="X">{json args}</tool_call>
 * ausgeben statt das Tool intern via Bash zu simulieren.
 */
function _toolsBlock(tools) {
    if (!Array.isArray(tools) || tools.length === 0) return "";
    const lines = [];
    lines.push("# Verfuegbare Tools");
    lines.push("");
    lines.push(
        "Du hast neben deinen eigenen internen Tools (Bash, Read, etc.) auch " +
        "diese externen Tools, die im Backend-System angesiedelt sind. " +
        "Sie sind die EINZIGE Moeglichkeit Aktionen auszuloesen wie Trigger anlegen, " +
        "Skills aufrufen, oder Konfiguration aendern. Simuliere sie NICHT mit Bash/sleep — " +
        "rufe sie sauber auf:"
    );
    lines.push("");
    for (const t of tools) {
        if (!t || t.type !== "function" || !t.function) continue;
        const fn = t.function;
        const name = fn.name || "";
        const desc = fn.description || "";
        const params = fn.parameters || {};
        lines.push(`## ${name}`);
        if (desc) lines.push(desc);
        try {
            lines.push("Schema: " + JSON.stringify(params));
        } catch (_) {
            lines.push("Schema: (nicht serialisierbar)");
        }
        lines.push("");
    }
    lines.push("# Tool-Call-Format");
    lines.push("");
    lines.push(
        "Wenn du eines der OBIGEN externen Tools aufrufen willst, antworte " +
        "**ausschliesslich** mit einem oder mehreren Bloecken in genau dieser Form, " +
        "JEDER fuer sich auf einer eigenen Zeile:"
    );
    lines.push("");
    lines.push('<tool_call name="TOOL_NAME">{"arg1":"value","arg2":123}</tool_call>');
    lines.push("");
    lines.push(
        "Regeln: (1) Innerhalb des Blocks steht NUR gueltiges JSON mit den Argumenten. " +
        "(2) Kein Text drumherum. (3) Keine Code-Fences, kein Markdown. " +
        "(4) Mehrere Tool-Calls = mehrere Bloecke untereinander. " +
        "(5) Nach den Bloecken aufhoeren — der Server fuehrt die Tools aus und " +
        "schickt dir die Ergebnisse fuer den naechsten Turn. " +
        "(6) Wenn KEIN externes Tool noetig ist, antworte normal als Text fuer den User. " +
        "(7) Nutze Bash/sleep NICHT als Ersatz fuer trigger_timer — das ist genau " +
        "der Bug den wir damit fixen."
    );
    return lines.join("\n");
}

/**
 * Wandelt OpenAI-messages in einen Single-String-Prompt um.
 * - system/user/assistant wie bisher
 * - tool-role: als <tool_result tool_call_id="..." name="..."> eingewoben
 */
export function messagesToPrompt(messages, tools) {
    const parts = [];
    const toolsBlock = _toolsBlock(tools);
    if (toolsBlock) {
        parts.push(`<system>\n${toolsBlock}\n</system>\n`);
    }
    for (const msg of messages) {
        if (!msg) continue;
        switch (msg.role) {
            case "system":
                parts.push(`<system>\n${_text(msg.content)}\n</system>\n`);
                break;
            case "user":
                parts.push(_text(msg.content));
                break;
            case "assistant": {
                const txt = _text(msg.content);
                const tcs = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
                const tcParts = tcs.map((tc) => {
                    const name = tc?.function?.name || tc?.name || "";
                    let args = tc?.function?.arguments ?? tc?.arguments ?? "{}";
                    if (typeof args !== "string") {
                        try { args = JSON.stringify(args); } catch (_) { args = "{}"; }
                    }
                    return `<tool_call name="${name}">${args}</tool_call>`;
                }).join("\n");
                const combined = [txt, tcParts].filter(Boolean).join("\n").trim();
                if (combined) parts.push(`<previous_response>\n${combined}\n</previous_response>\n`);
                break;
            }
            case "tool": {
                const name = msg.name || "";
                const id = msg.tool_call_id || "";
                parts.push(
                    `<tool_result tool_call_id="${id}" name="${name}">\n${_text(msg.content)}\n</tool_result>\n`
                );
                break;
            }
        }
    }
    return parts.join("\n").trim();
}

/**
 * Extrahiert NUR den System-Anteil (System-Messages + Tool-Use-Block) als
 * rohen Text — OHNE <system>-Tags. Fuer den ECHTEN System-Prompt-Kanal der
 * Claude-CLI (--system-prompt, VOLLER Replace — nicht --append). Damit ist
 * die ARIA-Persona DIE Identitaet des Modells und nicht ein Anhaengsel hinter
 * Claude Codes eigener "You are Claude Code"-Identitaet (die bei duennem
 * Kontext sonst gewinnt und die Persona als Injection abwehrt). Der Output
 * muss deshalb SELBSTTRAGEND sein — er ersetzt Claude Codes System-Prompt
 * komplett inkl. dynamischer Sektionen (cwd, git, platform).
 * Reihenfolge: erst der Tool-Use-Block (Format-Anweisung), dann die
 * System-Messages in Original-Reihenfolge.
 */
export function extractSystemPrompt(messages, tools) {
    const chunks = [];
    const toolsBlock = _toolsBlock(tools);
    if (toolsBlock) chunks.push(toolsBlock);
    for (const msg of messages || []) {
        if (msg && msg.role === "system") {
            const t = _text(msg.content).trim();
            if (t) chunks.push(t);
        }
    }
    return chunks.join("\n\n").trim();
}

/**
 * Wie messagesToPrompt, aber OHNE System-Messages und OHNE Tool-Block — nur der
 * eigentliche Verlauf (user/assistant/tool). Fuer den Modus, in dem der
 * System-Prompt ueber --append-system-prompt separat zugestellt wird.
 */
export function conversationToPrompt(messages) {
    const parts = [];
    for (const msg of messages || []) {
        if (!msg) continue;
        switch (msg.role) {
            case "system":
                break; // geht ueber --append-system-prompt
            case "user":
                parts.push(_text(msg.content));
                break;
            case "assistant": {
                const txt = _text(msg.content);
                const tcs = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
                const tcParts = tcs.map((tc) => {
                    const name = tc?.function?.name || tc?.name || "";
                    let args = tc?.function?.arguments ?? tc?.arguments ?? "{}";
                    if (typeof args !== "string") {
                        try { args = JSON.stringify(args); } catch (_) { args = "{}"; }
                    }
                    return `<tool_call name="${name}">${args}</tool_call>`;
                }).join("\n");
                const combined = [txt, tcParts].filter(Boolean).join("\n").trim();
                if (combined) parts.push(`<previous_response>\n${combined}\n</previous_response>\n`);
                break;
            }
            case "tool": {
                const name = msg.name || "";
                const id = msg.tool_call_id || "";
                parts.push(
                    `<tool_result tool_call_id="${id}" name="${name}">\n${_text(msg.content)}\n</tool_result>\n`
                );
                break;
            }
        }
    }
    return parts.join("\n").trim();
}

export function openaiToCli(request) {
    // Persona/System + Tool-Block gehen ueber den ECHTEN System-Prompt-Kanal
    // (--system-prompt = VOLLER Replace, siehe manager.js buildArgs-Patch in
    // docker-compose.yml). Der Prompt enthaelt nur noch den Gespraechsverlauf.
    // Voller Replace statt --append, weil Anhaengen Claude Codes eingebaute
    // "You are Claude Code"-Identitaet stehen laesst — die bei duennem Kontext
    // (Hauptchat) gewinnt und die ARIA-Persona als Injection abwehrt.
    // systemPrompt ist immer ein String (extractSystemPrompt liefert "" statt
    // undefined). ACHTUNG: bei --system-prompt darf er NIE leer sein, sonst
    // laeuft das Modell ganz ohne System-Prompt — der Brain schickt aber immer
    // eine System-Message + Tool-Block, also ist er real nie leer.
    return {
        prompt: conversationToPrompt(request.messages),
        systemPrompt: extractSystemPrompt(request.messages, request.tools),
        model: extractModel(request.model),
        sessionId: request.user,
    };
}
