/**
 * #3089 — Convert a complete OpenAI-style chat-completion JSON body into an
 * equivalent OpenAI SSE (`chat.completion.chunk`) stream.
 *
 * Some "reasoning" openai-compatible upstreams ignore a `stream: true` request
 * and reply with a single `application/json` chat-completion body instead of an
 * SSE stream. OmniRoute's streaming readiness check only recognizes SSE `data:`
 * frames, so such a body produced a spurious `STREAM_EARLY_EOF` / HTTP 502 even
 * though it carried valid `content` / `reasoning_content`. Synthesizing an SSE
 * stream from that JSON lets the normal streaming pipeline (and the client) get
 * a valid stream that preserves both `content` and `reasoning_content`.
 *
 * Returns "" when the text is not a parseable chat-completion object with at
 * least one choice — callers then fall back to the original (error) handling.
 */

type JsonRecord = Record<string, unknown>;

function isRecord(value: unknown): value is JsonRecord {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

function sseEvent(payload: JsonRecord): string {
  return `data: ${JSON.stringify(payload)}\n\n`;
}

export function synthesizeOpenAiSseFromJson(jsonText: string): string {
  let parsed: unknown;
  try {
    parsed = JSON.parse(jsonText);
  } catch {
    return "";
  }
  if (!isRecord(parsed)) return "";

  const choices = parsed.choices;
  if (!Array.isArray(choices) || choices.length === 0) return "";

  const id = typeof parsed.id === "string" && parsed.id ? parsed.id : "chatcmpl-omniroute-sse";
  const created = typeof parsed.created === "number" ? parsed.created : 0;
  const model = typeof parsed.model === "string" ? parsed.model : "";
  const base = { id, object: "chat.completion.chunk", created, model };

  let out = "";
  let emittedAny = false;

  choices.forEach((choice, fallbackIndex) => {
    if (!isRecord(choice)) return;
    const index = typeof choice.index === "number" ? choice.index : fallbackIndex;
    const message = isRecord(choice.message) ? choice.message : {};

    // Emit role, reasoning_content, content and tool_calls as SEPARATE sequential
    // deltas — the same shape a real reasoning model streams (reasoning first,
    // then content). Combining them in one delta caused the openai→openai
    // translator to re-split and DUPLICATE reasoning_content across chunks
    // (#3089 follow-up); separate deltas pass through cleanly with no duplication.
    const role = typeof message.role === "string" ? message.role : "assistant";
    const emitDelta = (delta: JsonRecord) => {
      out += sseEvent({ ...base, choices: [{ index, delta, finish_reason: null }] });
    };

    emitDelta({ role });
    if (typeof message.reasoning_content === "string" && message.reasoning_content.length > 0) {
      emitDelta({ reasoning_content: message.reasoning_content });
    }
    if (typeof message.content === "string" && message.content.length > 0) {
      emitDelta({ content: message.content });
    }
    if (Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
      emitDelta({ tool_calls: message.tool_calls });
    }

    const finishReason =
      typeof choice.finish_reason === "string" && choice.finish_reason ? choice.finish_reason : "stop";
    const finalChoice: JsonRecord = { index, delta: {}, finish_reason: finishReason };
    const finalChunk: JsonRecord = { ...base, choices: [finalChoice] };
    if (isRecord(parsed.usage)) finalChunk.usage = parsed.usage;
    out += sseEvent(finalChunk);
    emittedAny = true;
  });

  if (!emittedAny) return "";
  out += "data: [DONE]\n\n";
  return out;
}
