import fs from "fs";

const HOST = "http://85.31.224.193:4000/";
const TEMP_DIR = "./temp-tts";

// Pastikan folder temp ada
if (!fs.existsSync(TEMP_DIR)) {
  fs.mkdirSync(TEMP_DIR, { recursive: true });
}

/* ============================================================
   CLEAN TEXT (AMAN UNTUK UNICODE)
============================================================ */

function convertDateToText(text: string): string {
  const months = [
    "Januari", "Februari", "Maret", "April", "Mei", "Juni",
    "Juli", "Agustus", "September", "Oktober", "November", "Desember"
  ];

  return text.replace(/\(?(\d{1,2})\/(\d{1,2})\/(\d{4})\)?/g, (_, dd, mm, yyyy) => {
    const monthIndex = parseInt(mm, 10) - 1;
    if (monthIndex < 0 || monthIndex > 11) return _;
    return `${parseInt(dd, 10)} ${months[monthIndex]} ${yyyy}`;
  });
}

function cleanText(text: string): string {
  // Ubah tanggal dulu biar lebih mudah dibaca TTS
  text = convertDateToText(text);

  return text
    // izinkan: huruf, angka, spasi, dan . , ! ? ;
    .replace(/[^\p{L}\p{N} .,!?;]/gu, "")
    // normalkan banyak spasi menjadi 1 spasi
    .replace(/\s+/g, " ")
    // trim spasi kiri/kanan
    .trim();
}




/* ============================================================
   SPLIT TEXT (MAKSIMAL 200 CHAR + CARI TITIK/KOMA/!?)
============================================================ */

function splitText(text: string, max = 200): string[] {
  const chunks: string[] = [];
  text = text.trim();

  const isDecimal = (str: string, idx: number) => {
    // kasus angka.desimal → contoh: 1000.25   atau   1,234.56
    const before = str[idx - 1];
    const after = str[idx + 1];
    return /\d/.test(before || '') && /\d/.test(after || '');
  };

  const isThousandsSeparator = (str: string, idx: number) => {
    // angka ribuan 1.234 atau 2,500 dll
    const before = str[idx - 1];
    const after = str[idx + 1];
    return /\d/.test(before || '') && /\d/.test(after || '');
  };

  while (text.length > 0) {
    if (text.length <= max) {
      chunks.push(text);
      break;
    }

    const slice = text.slice(0, max);

    let cutIndex = -1;

    // Cari tanda baca yang aman untuk split
    for (let i = slice.length - 1; i >= 0; i--) {
      const ch = slice[i];

      if (".,!?;".includes(ch || '')) {
        // Abaikan jika itu bagian dari angka
        if (isDecimal(slice, i)) continue;
        if (isThousandsSeparator(slice, i)) continue;

        cutIndex = i + 1;
        break;
      }
    }

    // Jika tidak ada tanda baca yang valid
    if (cutIndex === -1) cutIndex = max;

    const part = text.slice(0, cutIndex).trim();
    if (part) chunks.push(part);

    text = text.slice(cutIndex).trim();
  }

  return chunks;
}


/* ============================================================
   FETCH WITH RETRY
============================================================ */

async function fetchRetry(url: string, options: any = {}, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      return await fetch(url, options);
    } catch (err) {
      console.warn(`Fetch attempt ${i + 1} failed:`, err);
      if (i === retries - 1) throw err;
      await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
    }
  }
}

/* ============================================================
   TTS ASYNC REQUEST
============================================================ */

async function generate(text: string) {
  const res = await fetchRetry(`${HOST}/tts-async`, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({ text, prompt: "dayu" }),
  });

  if (!res) {
    throw new Error("Failed to fetch result");
  }

  let data;
  try {
    data = await res.json();
  } catch {
    throw new Error("Invalid JSON response from server");
  }

  if (!data.job_id) {
    console.error("❌ Response tidak mengandung job_id:", data);
    throw new Error("job_id missing in generate response");
  }

  return data;
}


/* ============================================================
   MAIN
============================================================ */

(async () => {
  console.log("🎙️  TTS Async Client Starting...\n");

  let text = `
Kalau soal **minimal durasi contoh suara untuk clone suara (voice cloning)** di model seperti Chatterbox TTS atau umumnya model voice cloning, biasanya durasi minimalnya tergantung dari kualitas dan metode cloning-nya.

Berikut gambaran umumnya:

* **Minimal durasi ideal untuk voice cloning berkualitas cukup baik:**
  **sekitar 30 detik – 1 menit** rekaman suara bersih, jernih, tanpa noise, dan dengan intonasi alami.

* **Jika kurang dari 30 detik:**
  Model bisa saja clone, tapi hasilnya biasanya kurang natural dan suaranya bisa terdengar “robotik” atau kurang variatif.

* **Durasi lebih dari 1 menit:**
  Biasanya semakin bagus hasil cloning karena model punya banyak data untuk belajar karakter suara, intonasi, ekspresi, dan variasi.

---

### Catatan:

* Kualitas rekaman sangat penting: noise rendah, microphone bagus, format lossless (WAV) lebih disarankan.
* Banyak model TTS/voice cloning modern bisa pakai data pendek, tapi kualitas output terbatas.
* Kalau kamu pakai Chatterbox TTS khusus, coba cek dokumentasi mereka apakah ada rekomendasi durasi input.

---

Kalau kamu butuh, aku bisa bantu cek dokumentasi spesifik Chatterbox TTS atau rekomendasi untuk voice cloning dari model lain juga. Mau?

`;

  text = cleanText(text);

  const parts = splitText(text, 360);

  console.log(`📝 Total chunks: ${parts.length}\n`);

  const jobs: string[] = [];

  // SEND JOBS
  console.log("📤 Sending jobs to server...");
  for (let i = 0; i < parts.length; i++) {
    try {
      const res = await generate(parts[i] as string);
      jobs.push(res.job_id);
      console.log(`   ✓ Job ${i + 1}/${parts.length}: ${res.job_id}`);
    } catch (error) {
      console.error(`❌ Failed to generate job for chunk ${i + 1}:`, error);
      process.exit(1);
    }
  }

  console.log(`\n✅ All ${jobs.length} jobs submitted\n`);
  fs.writeFileSync(`${TEMP_DIR}/jobs.json`, JSON.stringify(jobs));

})();
