import type { JeitoDeFalar, PalavraFalada, VozDaNarracao } from "./narracao";
import { JEITOS_DE_FALAR } from "./narracao";

/*
 * A voz e a transcrição do editor, no servidor: OpenAI primeiro (a voz
 * gpt-4o-mini-tts e o whisper-1, que dá o tempo exato de cada palavra) e, se
 * ela falhar (sem chave, sem crédito, fora do ar), o Gemini (a voz do Gemini
 * em WAV e a transcrição com o tempo aproximado de cada palavra — o editor
 * acerta os começos no próprio som). As chaves ficam no servidor.
 */

export class FalaIndisponivel extends Error {}

/* As vozes do Gemini parecidas com as da tela. */
const VOZ_DO_GEMINI: Record<VozDaNarracao, string> = { nova: "Zephyr", shimmer: "Aoede", coral: "Leda", onyx: "Charon", echo: "Puck", ash: "Orus" };

/* WAV de 16 bits mono a partir do PCM cru do Gemini. */
function wav(pcm: Buffer, taxa: number): Buffer {
  const b = Buffer.alloc(44 + pcm.length);
  b.write("RIFF", 0);
  b.writeUInt32LE(36 + pcm.length, 4);
  b.write("WAVE", 8);
  b.write("fmt ", 12);
  b.writeUInt32LE(16, 16);
  b.writeUInt16LE(1, 20);
  b.writeUInt16LE(1, 22);
  b.writeUInt32LE(taxa, 24);
  b.writeUInt32LE(taxa * 2, 28);
  b.writeUInt16LE(2, 32);
  b.writeUInt16LE(16, 34);
  b.write("data", 36);
  b.writeUInt32LE(pcm.length, 40);
  pcm.copy(b, 44);
  return b;
}

async function vozDaOpenAi(texto: string, voz: VozDaNarracao, jeito: JeitoDeFalar) {
  const chave = process.env.OPENAI_API_KEY;
  if (!chave) throw new FalaIndisponivel("sem chave da OpenAI");
  const instrucao = JEITOS_DE_FALAR.find((j) => j.chave === jeito)?.instrucao ?? "";
  const r = await fetch("https://api.openai.com/v1/audio/speech", {
    method: "POST",
    headers: { Authorization: `Bearer ${chave}`, "Content-Type": "application/json" },
    body: JSON.stringify({ model: "gpt-4o-mini-tts", voice: voz, input: texto, instructions: instrucao, response_format: "mp3" }),
    signal: AbortSignal.timeout(90_000),
  });
  if (!r.ok) throw new FalaIndisponivel(`OpenAI ${r.status}: ${(await r.text().catch(() => "")).slice(0, 160)}`);
  return { audio: Buffer.from(await r.arrayBuffer()), tipo: "audio/mpeg" as const, provedor: "openai" };
}

async function vozDoGemini(texto: string, voz: VozDaNarracao, jeito: JeitoDeFalar) {
  const chave = process.env.GEMINI_API_KEY;
  if (!chave) throw new FalaIndisponivel("sem chave do Gemini");
  const como = { anuncio: "com energia de anúncio de redes sociais, animado e confiante", conversa: "natural e próximo, como numa conversa", calmo: "calmo e acolhedor, devagar" }[jeito];
  const r = await fetch("https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-tts:generateContent", {
    method: "POST",
    headers: { "content-type": "application/json", "x-goog-api-key": chave },
    body: JSON.stringify({ contents: [{ parts: [{ text: `Leia em português do Brasil, ${como}: ${texto}` }] }], generationConfig: { responseModalities: ["AUDIO"], speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: VOZ_DO_GEMINI[voz] } } } } }),
    signal: AbortSignal.timeout(120_000),
  });
  const j = (await r.json().catch(() => ({}))) as { candidates?: Array<{ content?: { parts?: Array<{ inlineData?: { data: string; mimeType: string } }> } }> };
  const parte = j.candidates?.[0]?.content?.parts?.find((p) => p.inlineData)?.inlineData;
  if (!r.ok || !parte) throw new FalaIndisponivel(`Gemini ${r.status}`);
  const taxa = Number(/rate=(\d+)/.exec(parte.mimeType)?.[1]) || 24000;
  return { audio: wav(Buffer.from(parte.data, "base64"), taxa), tipo: "audio/wav" as const, provedor: "gemini" };
}

/** A voz do texto (MP3 da OpenAI ou WAV do Gemini). */
export async function gerarVoz(texto: string, voz: VozDaNarracao, jeito: JeitoDeFalar) {
  try {
    return await vozDaOpenAi(texto, voz, jeito);
  } catch (primeira) {
    console.warn("[artes] voz pela OpenAI falhou, indo pelo Gemini:", primeira instanceof Error ? primeira.message : primeira);
    return vozDoGemini(texto, voz, jeito);
  }
}

async function transcreverPelaOpenAi(dados: ArrayBuffer, tipo: string, nome: string): Promise<PalavraFalada[]> {
  const chave = process.env.OPENAI_API_KEY;
  if (!chave) throw new FalaIndisponivel("sem chave da OpenAI");
  const f = new FormData();
  f.append("file", new Blob([dados], { type: tipo }), nome);
  f.append("model", "whisper-1");
  f.append("response_format", "verbose_json");
  f.append("timestamp_granularities[]", "word");
  f.append("language", "pt");
  const r = await fetch("https://api.openai.com/v1/audio/transcriptions", { method: "POST", headers: { Authorization: `Bearer ${chave}` }, body: f, signal: AbortSignal.timeout(150_000) });
  if (!r.ok) throw new FalaIndisponivel(`OpenAI ${r.status}: ${(await r.text().catch(() => "")).slice(0, 160)}`);
  const lido = (await r.json()) as { words?: Array<{ word: string; start: number; end: number }> };
  return (lido.words ?? []).map((w) => ({ texto: String(w.word || "").trim(), inicio: Number(w.start) || 0, fim: Number(w.end) || 0 })).filter((w) => w.texto);
}

async function transcreverPeloGemini(dados: ArrayBuffer, tipo: string): Promise<PalavraFalada[]> {
  const chave = process.env.GEMINI_API_KEY;
  if (!chave) throw new FalaIndisponivel("sem chave do Gemini");
  const corpo = {
    contents: [{ parts: [{ inlineData: { mimeType: tipo.startsWith("video/") ? tipo : tipo || "audio/mpeg", data: Buffer.from(dados).toString("base64") } }, { text: "Transcreva a fala em português do Brasil, palavra por palavra, com o segundo exato em que cada palavra começa e termina no áudio. Mantenha a pontuação grudada na palavra. Sem fala, devolva uma lista vazia." }] }],
    generationConfig: { responseMimeType: "application/json", responseSchema: { type: "ARRAY", items: { type: "OBJECT", properties: { texto: { type: "STRING" }, inicio: { type: "NUMBER" }, fim: { type: "NUMBER" } }, required: ["texto", "inicio", "fim"] } } },
  };
  for (const modelo of [process.env.IA_GEMINI_MODEL || "gemini-2.5-flash-lite", "gemini-3.8-flash"]) {
    const r = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${modelo}:generateContent`, { method: "POST", headers: { "content-type": "application/json", "x-goog-api-key": chave }, body: JSON.stringify(corpo), signal: AbortSignal.timeout(150_000) });
    if (!r.ok) continue;
    const j = (await r.json().catch(() => ({}))) as { candidates?: Array<{ content?: { parts?: Array<{ text?: string }> } }> };
    try {
      const lista = JSON.parse(j.candidates?.[0]?.content?.parts?.[0]?.text || "[]") as Array<{ texto: string; inicio: number; fim: number }>;
      return lista.map((w) => ({ texto: String(w.texto || "").trim(), inicio: Math.max(0, Number(w.inicio) || 0), fim: Math.max(0, Number(w.fim) || 0) })).filter((w) => w.texto);
    } catch {
      continue;
    }
  }
  throw new FalaIndisponivel("Gemini sem resposta");
}

/** As palavras faladas, com o tempo de cada uma (`aproximado`: o editor acerta os começos no som). */
export async function transcrever(dados: ArrayBuffer, tipo: string, nome: string): Promise<{ palavras: PalavraFalada[]; aproximado: boolean; provedor: string }> {
  try {
    return { palavras: await transcreverPelaOpenAi(dados, tipo, nome), aproximado: false, provedor: "openai" };
  } catch (primeira) {
    console.warn("[artes] transcrição pela OpenAI falhou, indo pelo Gemini:", primeira instanceof Error ? primeira.message : primeira);
    return { palavras: await transcreverPeloGemini(dados, tipo), aproximado: true, provedor: "gemini" };
  }
}
