/**
 * Recoge un corpus de articulos publicos de dev.to para medir que funciona.
 *
 * Medi 19 etiquetas ayer y el resultado decidio donde publico: la mediana de
 * reacciones es 0 en todas, y lo que cambia entre unas y otras es el techo. Eso
 * fue con 30 articulos por etiqueta. Esto lo lleva a escala para poder mirar
 * otras cosas —portada, longitud del titulo, hora, serie, tiempo de lectura—
 * sobre miles de articulos en vez de sobre una corazonada.
 *
 * La API publica de articulos no necesita clave. Aun asi se va despacio: ayer me
 * gane un "Retry later" por ir rapido, y un corpus no vale una sancion.
 *
 *   node tools/devto-corpus.js data/devto-corpus.csv
 *
 * Reanudable: no repite paginas ya guardadas.
 */
const fs = require('fs');
const path = require('path');

const OUT = process.argv[2] || path.join(__dirname, '..', 'data', 'devto-corpus.csv');
const PER_PAGE = 100;
const PAGES = 12;               // por etiqueta
const DELAY_MS = 1500;          // entre peticiones
const TAGS = ['webdev', 'javascript', 'programming', 'beginners', 'python', 'ai',
  'react', 'discuss', 'career', 'opensource', 'tutorial', 'productivity',
  'devops', 'node', 'typescript', 'css', 'database', 'security', 'testing',
  'machinelearning'];

const HEAD = 'tag,id,published_at,title,title_len,has_cover,reading_minutes,' +
  'tags,tag_count,reactions,comments,series,author,author_followers\n';
const q = (v) => '"' + String(v == null ? '' : v).replace(/"/g, '""') + '"';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function getJson(url, tries = 4) {
  for (let i = 0; i < tries; i++) {
    const r = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
    if (r.ok) {
      const text = await r.text();
      // El limitador responde texto plano "Retry later", no JSON.
      try { return JSON.parse(text); } catch { await sleep(30000 * (i + 1)); continue; }
    }
    if (r.status === 429) { await sleep(60000 * (i + 1)); continue; }
    return null;
  }
  return null;
}

(async () => {
  fs.mkdirSync(path.dirname(OUT), { recursive: true });
  if (!fs.existsSync(OUT)) fs.writeFileSync(OUT, HEAD);

  const seen = new Set(fs.readFileSync(OUT, 'utf8').split('\n').slice(1)
    .map((l) => (l.match(/^"[^"]*","(\d+)"/) || [])[1]).filter(Boolean));
  const donePages = new Set(fs.existsSync(OUT + '.pages')
    ? fs.readFileSync(OUT + '.pages', 'utf8').split('\n').filter(Boolean) : []);

  let added = 0;
  for (const tag of TAGS) {
    for (let page = 1; page <= PAGES; page++) {
      const key = `${tag}:${page}`;
      if (donePages.has(key)) continue;

      const arts = await getJson(
        `https://dev.to/api/articles?tag=${tag}&per_page=${PER_PAGE}&page=${page}`);
      await sleep(DELAY_MS);
      if (!Array.isArray(arts)) { console.log(`${key}: sin datos, se salta`); continue; }
      if (!arts.length) { fs.appendFileSync(OUT + '.pages', key + '\n'); break; }

      const rows = [];
      for (const a of arts) {
        if (seen.has(String(a.id))) continue;
        seen.add(String(a.id));
        rows.push([tag, a.id, a.published_at, a.title, (a.title || '').length,
          a.cover_image ? 1 : 0, a.reading_time_minutes,
          (a.tag_list || []).join(' '), (a.tag_list || []).length,
          a.public_reactions_count, a.comments_count, a.series || '',
          a.user && a.user.username, '',
        ].map(q).join(','));
      }
      if (rows.length) { fs.appendFileSync(OUT, rows.join('\n') + '\n'); added += rows.length; }
      fs.appendFileSync(OUT + '.pages', key + '\n');
      console.log(`${key}: +${rows.length} (total nuevos ${added})`);
    }
  }
  console.log('listo ->', OUT);
})();
