/**
 * Google Gen AI SDK 原生流式 + thinking 解析（@google/genai generateContentStream）。
 */

export type GeminiStreamEmitters = {
  onThinkingDelta: (delta: string) => void;
  onAssistantDelta: (delta: string) => void;
};

export type GeminiStreamAggregateResult = {
  replyText: string;
  thinkingText: string;
  modelParts: unknown[];
  functionCalls: unknown[];
  usageMetadata: unknown;
};

export function geminiIncludeThoughtsEnabled(): boolean {
  const raw = (process.env.GEMINI_INCLUDE_THOUGHTS ?? "1").trim().toLowerCase();
  return raw !== "0" && raw !== "false" && raw !== "off";
}

export function geminiThinkingBudget(): number {
  const raw = process.env.GEMINI_THINKING_BUDGET?.trim();
  if (!raw) return -1;
  const n = Number.parseInt(raw, 10);
  return Number.isFinite(n) ? n : -1;
}

export function buildGeminiGenerateConfig(opts: {
  systemInstruction?: string;
  googleTools: readonly unknown[];
}): Record<string, unknown> {
  const tools: unknown[] = [];
  if (opts.googleTools.length > 0) {
    tools.push({ functionDeclarations: opts.googleTools });
  }
  if (process.env.GEMINI_ENABLE_GOOGLE_SEARCH === "1") {
    tools.push({ googleSearch: {} });
  }
  if (process.env.GEMINI_ENABLE_CODE_EXECUTION === "1") {
    tools.push({ codeExecution: {} });
  }

  const config: Record<string, unknown> = {};
  if (tools.length > 0) config.tools = tools;
  if (opts.systemInstruction) {
    config.systemInstruction = { parts: [{ text: opts.systemInstruction }] };
  }
  if (geminiIncludeThoughtsEnabled()) {
    config.thinkingConfig = {
      includeThoughts: true,
      thinkingBudget: geminiThinkingBudget(),
    };
  }
  return config;
}

function partsFromChunk(chunk: unknown): unknown[] {
  const c = chunk as {
    candidates?: Array<{ content?: { parts?: unknown[] } }>;
  };
  const parts = c?.candidates?.[0]?.content?.parts;
  return Array.isArray(parts) ? parts : [];
}

function appendParts(acc: unknown[], incoming: unknown[]): unknown[] {
  if (incoming.length === 0) return acc;
  return acc.concat(incoming);
}

export function cumulativeTextFromParts(allParts: readonly unknown[]): {
  thought: string;
  text: string;
} {
  let thought = "";
  let text = "";
  for (const part of allParts) {
    const p = part as {
      text?: string;
      thought?: boolean;
      functionCall?: unknown;
      functionResponse?: unknown;
    };
    if (typeof p.text !== "string") continue;
    if (p.thought === true) {
      thought += p.text;
    } else if (!p.functionCall && !p.functionResponse) {
      text += p.text;
    }
  }
  return { thought, text };
}

export type GeminiFunctionCall = { name?: string; id?: string; args?: unknown };

export function extractFunctionCallsFromParts(partList: readonly unknown[]): GeminiFunctionCall[] {
  const calls: GeminiFunctionCall[] = [];
  for (const part of partList) {
    const fc = (part as { functionCall?: unknown })?.functionCall;
    if (fc && typeof fc === "object") calls.push(fc as GeminiFunctionCall);
  }
  return calls;
}

export async function consumeGeminiContentStream(
  stream: AsyncIterable<unknown>,
  emitters: GeminiStreamEmitters,
): Promise<GeminiStreamAggregateResult> {
  let allParts: unknown[] = [];
  let usageMetadata: unknown;
  let lastChunk: unknown;
  let emittedThoughtLen = 0;
  let emittedTextLen = 0;

  for await (const chunk of stream) {
    lastChunk = chunk;
    const chunkMeta = chunk as { usageMetadata?: unknown };
    if (chunkMeta.usageMetadata) usageMetadata = chunkMeta.usageMetadata;

    allParts = appendParts(allParts, partsFromChunk(chunk));
    const { thought, text } = cumulativeTextFromParts(allParts);

    if (thought.length > emittedThoughtLen) {
      emitters.onThinkingDelta(thought.slice(emittedThoughtLen));
      emittedThoughtLen = thought.length;
    }
    if (text.length > emittedTextLen) {
      emitters.onAssistantDelta(text.slice(emittedTextLen));
      emittedTextLen = text.length;
    }
  }

  const { thought: thinkingText, text: replyText } = cumulativeTextFromParts(allParts);

  const last = lastChunk as { functionCalls?: unknown[] } | undefined;
  let functionCalls = Array.isArray(last?.functionCalls) ? last!.functionCalls! : [];
  if (functionCalls.length === 0) {
    functionCalls = extractFunctionCallsFromParts(allParts);
  }

  return {
    replyText,
    thinkingText,
    modelParts: allParts,
    functionCalls,
    usageMetadata,
  };
}
