/**
 * ============================================================
 *  Social Media Sentiment Analyzer — Node.js + Playwright
 *  File   : sentiment_scraper.js
 *  Output : hasil_sentimen.xlsx  +  ringkasan di terminal
 * ============================================================
 *
 *  INSTALASI (jalankan sekali saja):
 *    npm install playwright sentiment exceljs
 *    npx playwright install chromium
 *
 *  MENJALANKAN:
 *    node sentiment_scraper.js
 *
 *  CARA AMBIL auth_token X.com:
 *    1. Login di browser Chrome/Firefox ke x.com
 *    2. F12 → Application → Cookies → https://x.com
 *    3. Cari cookie "auth_token" → salin nilainya
 *    4. Tempel di variabel X_AUTH_TOKEN di bawah
 * ============================================================
 */

const { chromium } = require('playwright');
const Sentiment = require('sentiment');
const ExcelJS = require('exceljs');
const path = require('path');
const { POSITIVE_ID, NEGATIVE_ID } = require('./words_id');

// ══════════════════════════════════════════════════════════════
//  ① KONFIGURASI — Sesuaikan sebelum menjalankan
// ══════════════════════════════════════════════════════════════
const KEYWORD = 'Robby S Putra';                 // Topik yang dicari
const PLATFORM = 'x';                       // 'x' = Twitter/X  (facebook belum support otomatis)
const MAX_TWEET = 100;  // ← Jumlah tweet yang ingin dikumpulkan
const OUTPUT_FILE = path.join(__dirname, 'hasil_sentimen.xlsx');

// Cookie login X.com (WAJIB untuk menghindari login wall)
const X_AUTH_TOKEN = '5e114136ed52971adddd375366103dafa7044864';
// ══════════════════════════════════════════════════════════════

// ── Inisialisasi Sentiment dengan kosakata Indonesia ───────────
const analyzer = new Sentiment();
analyzer.registerLanguage('id', {
  labels: { ...POSITIVE_ID, ...NEGATIVE_ID },
});

/**
 * Analisis sentimen satu teks — gabungkan bahasa Inggris + Indonesia.
 * Return: { score, label, comparative }
 */
function analyzeSentiment(text) {
  // Coba analisis bahasa Indonesia dulu
  const idResult = analyzer.analyze(text, { language: 'id' });
  // Fallback ke English jika tidak ada kata yang dikenali
  const enResult = analyzer.analyze(text);

  // Gabungkan skor (bobot: 60% ID + 40% EN)
  const combinedScore = (idResult.score * 0.6) + (enResult.score * 0.4);
  const comparative = combinedScore / Math.max(text.split(/\s+/).length, 1);

  let label;
  if (combinedScore > 0.5) label = 'POSITIF';
  else if (combinedScore < -0.5) label = 'NEGATIF';
  else label = 'NETRAL';

  return {
    score: parseFloat(combinedScore.toFixed(2)),
    label,
    comparative: parseFloat(comparative.toFixed(4)),
    words_pos: idResult.positive.concat(enResult.positive).join(' | '),
    words_neg: idResult.negative.concat(enResult.negative).join(' | '),
  };
}

/**
 * Simpan array-of-objects ke file XLSX yang rapi.
 * Fitur: header bold + berwarna, lebar kolom otomatis,
 *        warna sel sentimen, baris header frozen, auto-filter.
 */
async function saveXLSX(records, filePath) {
  if (!records.length) return console.log('\n⚠  Tidak ada data untuk disimpan.');

  const workbook = new ExcelJS.Workbook();
  workbook.creator = 'botScraping — Sentiment Analyzer';
  workbook.created = new Date();

  const sheet = workbook.addWorksheet('Hasil Sentimen', {
    views: [{ state: 'frozen', ySplit: 1 }], // Freeze baris header
  });

  // ── Definisi kolom ──────────────────────────────────────────
  const colDefs = [
    { key: 'user', header: 'User', width: 22 },
    { key: 'teks', header: 'Teks Tweet', width: 60 },
    { key: 'tautan', header: 'Tautan', width: 45 },
    { key: 'like', header: 'Like', width: 8 },
    { key: 'retweet', header: 'Retweet', width: 10 },
    { key: 'reply', header: 'Reply', width: 8 },
    { key: 'sentimen', header: 'Sentimen', width: 12 },
    { key: 'skor_sentimen', header: 'Skor', width: 8 },
    { key: 'comparative', header: 'Comparative', width: 13 },
    { key: 'kata_positif', header: 'Kata Positif', width: 30 },
    { key: 'kata_negatif', header: 'Kata Negatif', width: 30 },
    { key: 'keyword', header: 'Keyword', width: 14 },
    { key: 'scraped_at', header: 'Scraped At', width: 22 },
  ];

  sheet.columns = colDefs;

  // ── Style baris header ───────────────────────────────────────
  const headerRow = sheet.getRow(1);
  headerRow.eachCell(cell => {
    cell.font = { bold: true, color: { argb: 'FFFFFFFF' }, size: 11 };
    cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF1F4E79' } };
    cell.alignment = { vertical: 'middle', horizontal: 'center', wrapText: false };
    cell.border = {
      bottom: { style: 'medium', color: { argb: 'FF2E75B6' } },
    };
  });
  headerRow.height = 22;

  // ── Warna sentimen ───────────────────────────────────────────
  const COLORS = {
    POSITIF: { bg: 'FFE2EFDA', fg: 'FF375623' }, // hijau lembut
    NEGATIF: { bg: 'FFFCE4D6', fg: 'FF833C00' }, // merah/oranye lembut
    NETRAL: { bg: 'FFF2F2F2', fg: 'FF595959' }, // abu-abu lembut
  };

  // ── Tambahkan data ───────────────────────────────────────────
  records.forEach((record, idx) => {
    const row = sheet.addRow(record);
    const sentVal = (record.sentimen || 'NETRAL').toUpperCase();
    const color = COLORS[sentVal] || COLORS.NETRAL;
    const isEven = idx % 2 === 1;

    row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
      // Warna khusus kolom Sentimen (kolom ke-7)
      if (colNumber === 7) {
        cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: color.bg } };
        cell.font = { bold: true, color: { argb: color.fg } };
        cell.alignment = { horizontal: 'center', vertical: 'top' };
      } else {
        // Warna zebra untuk baris genap
        if (isEven) {
          cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFF7FBFF' } };
        }
        cell.alignment = { vertical: 'top', wrapText: colNumber === 2 };
      }

      // Border tipis tiap sel
      cell.border = {
        top: { style: 'thin', color: { argb: 'FFD9D9D9' } },
        left: { style: 'thin', color: { argb: 'FFD9D9D9' } },
        bottom: { style: 'thin', color: { argb: 'FFD9D9D9' } },
        right: { style: 'thin', color: { argb: 'FFD9D9D9' } },
      };
    });

    // Tinggi baris — teks panjang butuh lebih tinggi
    const lines = Math.ceil((record.teks || '').length / 80);
    row.height = Math.min(Math.max(lines * 15, 18), 80);
  });

  // ── Auto-filter di baris header ─────────────────────────────
  sheet.autoFilter = {
    from: { row: 1, column: 1 },
    to: { row: 1, column: colDefs.length },
  };

  // ── Simpan file ──────────────────────────────────────────────
  await workbook.xlsx.writeFile(filePath);
  console.log(`\n✅ Excel disimpan ke: ${filePath}`);
}

/** Hitung statistik sentimen */
function buildStats(records) {
  const total = records.length;
  const pos = records.filter(r => r.sentimen === 'POSITIF');
  const neg = records.filter(r => r.sentimen === 'NEGATIF');
  const neu = records.filter(r => r.sentimen === 'NETRAL');

  const pct = (n) => ((n / total) * 100).toFixed(1) + '%';

  // Komentar terbaik (skor tertinggi) dan terburuk (skor terendah)
  const sorted = [...records].sort((a, b) => b.skor_sentimen - a.skor_sentimen);
  const best3 = sorted.slice(0, 3);
  const worst3 = sorted.slice(-3).reverse();

  return { total, pos, neg, neu, pct, best3, worst3 };
}

/** Cetak ringkasan ke terminal */
function printSummary(stats, keyword) {
  const bar = (n, total, len = 30) => {
    const filled = Math.round((n / total) * len);
    return '█'.repeat(filled) + '░'.repeat(len - filled);
  };

  console.log('\n');
  console.log('╔══════════════════════════════════════════════════════════╗');
  console.log(`║         HASIL ANALISIS SENTIMEN — "${keyword.toUpperCase()}"`);
  console.log('╠══════════════════════════════════════════════════════════╣');
  console.log(`║  Total data terkumpul : ${stats.total}`);
  console.log('║');
  console.log(`║  😊 POSITIF  ${bar(stats.pos.length, stats.total)}  ${stats.pos.length} (${stats.pct(stats.pos.length)})`);
  console.log(`║  😠 NEGATIF  ${bar(stats.neg.length, stats.total)}  ${stats.neg.length} (${stats.pct(stats.neg.length)})`);
  console.log(`║  😐 NETRAL   ${bar(stats.neu.length, stats.total)}  ${stats.neu.length} (${stats.pct(stats.neu.length)})`);
  console.log('║');
  console.log('╠══════════════════════════════════════════════════════════╣');
  console.log('║  🏆 TOP 3 KOMENTAR TERBAIK (paling positif):');
  stats.best3.forEach((r, i) => {
    const cuplikan = r.teks.length > 80 ? r.teks.slice(0, 80) + '…' : r.teks;
    console.log(`║  [${i + 1}] Skor: +${r.skor_sentimen} | ${cuplikan}`);
  });
  console.log('║');
  console.log('╠══════════════════════════════════════════════════════════╣');
  console.log('║  💀 TOP 3 KOMENTAR TERBURUK (paling negatif):');
  stats.worst3.forEach((r, i) => {
    const cuplikan = r.teks.length > 80 ? r.teks.slice(0, 80) + '…' : r.teks;
    console.log(`║  [${i + 1}] Skor: ${r.skor_sentimen} | ${cuplikan}`);
  });
  console.log('╚══════════════════════════════════════════════════════════╝');
}

// ══════════════════════════════════════════════════════════════
//  FUNGSI UTAMA
// ══════════════════════════════════════════════════════════════
async function runSentimentScraper() {
  console.log(`\n🚀 Memulai Sentiment Scraper`);
  console.log(`   Platform : ${PLATFORM.toUpperCase()}`);
  console.log(`   Keyword  : "${KEYWORD}"`);
  console.log(`   Target   : ${MAX_TWEET} tweet\n`);

  // ── Validasi token ────────────────────────────────────────
  if (!X_AUTH_TOKEN || X_AUTH_TOKEN === 'PASTE_AUTH_TOKEN_KAMU_DI_SINI') {
    console.error('❌ X_AUTH_TOKEN belum diisi! Baca komentar di bagian konfigurasi.');
    process.exit(1);
  }

  // ── Buka browser ─────────────────────────────────────────
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
    locale: 'id-ID',
    viewport: { width: 1280, height: 900 },
  });

  // Inject auth cookie
  await context.addCookies([
    { name: 'auth_token', value: X_AUTH_TOKEN, domain: '.x.com', path: '/' },
  ]);

  const page = await context.newPage();

  let rawTweets = [];

  try {
    // ── Buka halaman pencarian X.com ─────────────────────
    const searchUrl = `https://x.com/search?q=${encodeURIComponent(KEYWORD)}&src=typed_query&f=live`;
    console.log(`📄 Membuka: ${searchUrl}`);
    await page.goto(searchUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 });

    console.log('⏳ Menunggu hasil pencarian...');
    await page.waitForSelector('article[data-testid="tweet"]', { timeout: 20_000 });
    console.log('   Hasil ditemukan! Mulai mengumpulkan data...\n');

    // ── Scroll & kumpulkan tweet ─────────────────────────
    const seen = new Set();
    let previousCount = 0;
    let noNewTweetsCount = 0;
    let s = 0;
    // Maksimal percobaan scroll (agar tidak infinite loop jika nyangkut)
    const MAX_SCROLL_ATTEMPTS = Math.max(30, Math.ceil(MAX_TWEET / 2));

    while (rawTweets.length < MAX_TWEET && s < MAX_SCROLL_ATTEMPTS) {
      const batch = await page.evaluate(() => {
        const articles = document.querySelectorAll('article[data-testid="tweet"]');
        const result = [];

        articles.forEach(item => {
          const teksEl = item.querySelector('[data-testid="tweetText"]');
          const teks = teksEl ? teksEl.innerText.trim() : '';

          const timeEl = item.querySelector('time');
          const href = (timeEl && timeEl.parentElement) ? timeEl.parentElement.href : '';

          const likeEl = item.querySelector('[data-testid="like"] span');
          const like = likeEl ? likeEl.innerText.trim() : '0';

          const rtEl = item.querySelector('[data-testid="retweet"] span');
          const rt = rtEl ? rtEl.innerText.trim() : '0';

          const replyEl = item.querySelector('[data-testid="reply"] span');
          const reply = replyEl ? replyEl.innerText.trim() : '0';

          const userEl = item.querySelector('[data-testid="User-Name"]');
          const user = userEl ? userEl.innerText.split('\n')[0].trim() : 'Unknown';

          if (teks) result.push({ teks, tautan: href, like, retweet: rt, reply, user });
        });

        return result;
      });

      // Deduplikasi berdasarkan URL
      for (const t of batch) {
        if (!seen.has(t.tautan) && rawTweets.length < MAX_TWEET) {
          seen.add(t.tautan);
          rawTweets.push(t);
        }
      }

      s++;
      console.log(`   Scroll ${s} — Terkumpul: ${rawTweets.length} tweet`);

      // Cek apakah ada penambahan tweet baru di scroll ini
      if (rawTweets.length === previousCount) {
        noNewTweetsCount++;
        if (noNewTweetsCount >= 4) {
          console.log('\n   ⚠ Pencarian mentok. Tidak ada tweet baru yang ditemukan lagi.');
          break;
        }
      } else {
        noNewTweetsCount = 0; // Reset jika menemukan tweet baru
      }
      previousCount = rawTweets.length;

      // Scroll ke paling bawah halaman & tunggu loading API X.com
      await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
      await page.waitForTimeout(3000 + Math.random() * 1500); // 3-4.5 detik
    }

  } catch (err) {
    console.error('\n❌ Error saat scraping:', err.message);
  } finally {
    await browser.close();
    console.log('\n🔒 Browser ditutup.');
  }

  if (!rawTweets.length) {
    console.log('\n⚠  Tidak ada data yang berhasil dikumpulkan.');
    return;
  }

  // ── Analisis sentimen tiap tweet ─────────────────────────
  const now = new Date().toLocaleString('id-ID', {
    timeZone: 'Asia/Jakarta',
    year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit', second: '2-digit',
  });

  const records = rawTweets.map(t => {
    const s = analyzeSentiment(t.teks);
    return {
      user: t.user,
      teks: t.teks,
      tautan: t.tautan,
      like: t.like,
      retweet: t.retweet,
      reply: t.reply,
      sentimen: s.label,
      skor_sentimen: s.score,
      comparative: s.comparative,
      kata_positif: s.words_pos,
      kata_negatif: s.words_neg,
      keyword: KEYWORD,
      scraped_at: now,
    };
  });

  // ── Hitung statistik & tampilkan ringkasan ──────────────
  const stats = buildStats(records);
  printSummary(stats, KEYWORD);

  // ── Simpan ke XLSX ───────────────────────────────────
  await saveXLSX(records, OUTPUT_FILE);
  console.log(`\n📊 Detail distribusi sentimen:`);
  console.log(`   POSITIF : ${stats.pos.length} tweet (${stats.pct(stats.pos.length)})`);
  console.log(`   NEGATIF : ${stats.neg.length} tweet (${stats.pct(stats.neg.length)})`);
  console.log(`   NETRAL  : ${stats.neu.length} tweet (${stats.pct(stats.neu.length)})`);
}

runSentimentScraper().catch(err => {
  console.error('❌ Fatal:', err);
  process.exit(1);
});
