#!/usr/bin/env node
/**
 * tag-exam-tasks.js — heuristic topic/subtopic/difficulty classifier
 * for exam_tasks (F6 of the exam-prep module).
 *
 * Strategy: ordered rules, first match wins. Each rule has:
 *   - test  : regex applied to the plain-text body (HTML + LaTeX stripped)
 *   - sub   : subtopic slug to assign
 * Rules ordered most-specific → most-general. The text is normalized
 * to plain Russian/Latin before matching.
 *
 * Usage:
 *   node backend/scripts/tag-exam-tasks.js math9            # tag math9
 *   node backend/scripts/tag-exam-tasks.js math9 --dry      # report only
 *   node backend/scripts/tag-exam-tasks.js math9 --reset    # null out all tags first
 *
 * Idempotent (UPDATE not INSERT). Re-runnable.
 */
'use strict';
const db = require('../src/db/db');

const args  = process.argv.slice(2).filter(a => !a.startsWith('--'));
const flags = new Set(process.argv.slice(2).filter(a => a.startsWith('--')));
const DRY   = flags.has('--dry');
const RESET = flags.has('--reset');
const VERBOSE = flags.has('--verbose') || flags.has('-v');
const examKey = args[0] || 'math9';

/* ── Text normalization ───────────────────────────────────────── */
function stripText(html) {
  return String(html || '')
    .replace(/<svg[\s\S]*?<\/svg>/gi, ' ')
    .replace(/<[^>]+>/g, ' ')
    // common LaTeX cleanup
    .replace(/\\dfrac\{([^}]+)\}\{([^}]+)\}/g, '($1)/($2)')
    .replace(/\\frac\{([^}]+)\}\{([^}]+)\}/g,  '($1)/($2)')
    .replace(/\\sqrt\{([^}]+)\}/g, 'sqrt($1)')
    .replace(/\\cdot/g, '*').replace(/\\leq/g, '<=').replace(/\\geq/g, '>=')
    .replace(/\\lt/g, '<').replace(/\\gt/g, '>')
    .replace(/\\[a-zA-Z]+/g, ' ')           // remove leftover LaTeX commands
    .replace(/[{}$]/g, ' ')
    .replace(/&[a-z]+;/gi, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}

/* ── Subtopic rules (ORDER MATTERS) ───────────────────────────── */
/* Strategy:
   1) Theory "Какое утверждение НЕ верно" — catch first (these are spread across topics)
   2) Specific geometry shapes
   3) Specific algebra topics (progressions, inequalities, equations)
   4) Functions/graphs
   5) Word problems with units/people/money
   6) Fractions, polynomials, expressions
   7) Powers/roots, numbers, arithmetic
*/
/* IMPORTANT: JS regex \w / \b are ASCII-only. Use [а-яё]* for Russian word
   tails and avoid \b after Cyrillic. The `u` flag with \p{L} would also work
   but stays here as plain regex for readability. */
const RULES = [
  // ── Theory ("какое из утверждений НЕ верно" / "какое из равенств верно")
  { sub: 'theory-statements',
    test: /Какое из следующих утверждений\s*НЕ\s*верно|Какое из следующих равенств\s+верно|Определите, какое из следующих равенств\s+верно|какое из следующих утверждений верно|Определите\s+верное\s+равенство|какое\s+из\s+данных\s+равенств\s+является\s+верным|выберите\s+тождество/i },

  // ── Word-problems (concrete subjects, percents, two-digit numbers, applied)
  { sub: 'alg-word-problems',
    test: /процент|%\s|\\%|на\s+\d{1,3}\s*%|от\s+числа\s+\d|зарплат|туристы|бассейн|маляр|бригад|пешеход|автомобил|населен|кубометр|мешк|пакет|корм|план[аеоуы]?\s|доход|поезд|вкладчик|акци[а-я]*|стоит\s+\d|купил|продал|посадил|урожа|удо[йя]|двузначн[а-я]*\s+числ|трёхзначн[а-я]*\s+числ|сумм[а-я]*\s+(?:его\s+|её\s+|своих\s+)?цифр|разделить\s+на\s+сумму|таня|маша|петя|ваня|саша|катя|оля|вова|учащемуся|школьник|урок|домашн[а-я]+\s+задани|самостоятельн[а-я]+\s+работ|часов\s+сна|возраст[а-я]*\s+\d|сколько\s+раз|задуманн[а-я]+\s+числ|задумал[а-я]*\s+числ|лампоч|украшени|поместь|деда\s+мороза|девоч|подруг|обменя[а-я]+\s+фотограф|расстояни[а-я]+\s+между\s+городами|расстояни[а-я]+\s+на\s+карте|масштаб\s+карт|израсходова[а-я]+|покраск|спортивн[а-я]+\s+зал|пол\s+в|купил[а-я]*\s+краск|банк\s+заданий|задач\s+по\s+(?:алгебре|геометрии|физике)|мотоциклист|велосипедист|сотрудник|комбинат|лаборатори|обрабатыва|испек[а-я]+|муки\s+получается|свежих\s+яблок|из\s+\d[\d., ]*\s*кг|сколько\s+надо|сколько\s+(?:килограмм|часов|дней|минут|метров|литров|штук)|ботанич[а-я]+\s+сад|ландшафтн|кусты\s+роз|кусты\s+пион|плиточник|подрядчик|облицовочн|кирпич|укладк|напольн[а-я]+\s+плитк|компани[а-я]+\s+поступил|брат\s+и\s+сестр|сестр\w*\s+вышл|тренажёрн[а-я]+\s+зал|зрительн[а-я]+\s+зал|определите\s+масштаб|местност[а-я]*\s+изображено|изображ[а-я]+\s+на\s+карте|умен[а-я]+\s+на\s+\d|увелич[а-я]+\s+на\s+\d|ящик[а-я]*\s+для\s+упаковк|упаковк[а-я]+\s+фрукт|ячеек|в\s+каждом\s+ряду/i },

  // ── Geometry: shapes (specific)
  { sub: 'geom-quadrilaterals',
    test: /параллелограмм|ромб[а-я]*|трапеци|квадрат[а-я]*|прямоугольник|четырёхугольник|четырехугольник|правильн[а-я]+\s+многоугольник|многоугольник|шестиугольник|пятиугольник/i },
  { sub: 'geom-triangles',
    test: /треугольник|гипотенуз|катет|медиан|биссектрис|пифагор|синусов|косинусов|остроугольн|тупоугольн|равнобедренн|равносторонн|угла\s+[A-ZА-Я]|внутри\s+угла/i },
  { sub: 'geom-circle',
    test: /окружност|круг[а-я]*|радиус|диаметр|вписан|описан|дуг[а-я]*|сектор|хорд|касательн/i },
  { sub: 'geom-coordinates',
    test: /координат|\s+вектор|уравнение прямой|уравнение окружности|на плоскости\s+xOy|точк[а-я]*\s*\(\s*-?\d|парабол[а-я]*\s+y\s*=|вершин[а-я]*\s+парабол/i },

  // ── Algebra: structured topics
  { sub: 'alg-progressions',
    test: /прогресси|арифметическ.{0,30}последовательн|геометрическ.{0,30}последовательн|n[\s\-]?[ыйего]\s+член|первы[йх]?\s+член|разност[а-я]*\s+прогресс|знаменател[а-я]*\s+прогресс|(?:первый|второй|третий|четвёртый|пятый|шестой|седьмой|восьмой|девятый|десятый|сотый)\s+член\s+(?:последовательности|прогрессии)|член\s+последовательности|заданн\w+\s+формулой\s+a_?n|a_?n\s*=\s*[a-zA-Z\d]/i },
  { sub: 'alg-inequalities',
    test: /неравенств|систем[а-я]*\s+неравенств|совокупност[а-я]*\s+неравенств|[xy]\s*[<>≤≥]|<=|>=|меньше нуля|больше нуля/i },
  { sub: 'alg-equations',
    test: /уравнен|систем[а-я]*\s+уравнен|корн[а-я]+\s+уравнения|решит[ье].{0,30}систем|квадратное уравнение|найдите\s+(?:все\s+)?значения?\s+переменной|при\s+как[а-я]+\s+значени[а-я]+\s+переменной/i },
  { sub: 'alg-functions',
    test: /функци|график|область значен|область определен|чётн[а-я]*\s+функци|нечётн[а-я]*\s+функци|возрастан|убыван|y\s*=\s*f|f\(\s*x\s*\)|парабол/i },

  // ── Algebra: general (rank lower)
  { sub: 'alg-fractions',
    test: /сократите\s+дробь|сокращени[а-я]*\s+дроб|алгебраическ[а-я]*\s+дроб|(?:числител|знаменател)[а-я]*\s+дроб|сумм[а-я]*\s+дроб|разност[а-я]*\s+дроб|произведени[а-я]*\s+дроб|частн[а-я]*\s+дроб|обыкновенн[а-я]*\s+дроб|дробей|в\s+виде\s+дроби|запись\s+выражения.{0,40}в\s+виде\s+дроби|выражени[а-я]*\s+НЕ\s+имеет\s+смысла|выражени[а-я]*\s+не\s+имеет\s+смысла/i },
  { sub: 'alg-polynomials',
    test: /многочлен|одночлен|разложите\s+на\s+множител|вынесите.{0,30}скобк|формул[а-я]*\s+сокращённ[а-я]*/i },
  { sub: 'alg-expressions',
    test: /упростите\s+выражени|тождественно\s+равн|значение\s+выражения|преобразуйте\s+выражени|результат\s+упрощения\s+выражения|равенство\s+выполняется|при\s+каком\s+значении\s+a|найдите\s+значение\s+выражения|найдите\s+(?:частное|произведение)\s+[a-z]\s+и\s+[a-z]|приведите\s+подобные\s+слагаемые|подобные\s+слагаемые|раскройте\s+скобки|раскрой[а-я]*\s+скобк/i },
  { sub: 'alg-powers',
    test: /степен[ьия]|показател[а-я]*\s+степен|корен[ья]|sqrt\(|иррациональн|[a-zа-я0-9]\s*\^\s*[\-{}\d]|\^\s*\{\s*-?\d/i },
  { sub: 'alg-arithmetic',
    test: /сумма\s+(?:двух\s+)?чисел|разность\s+(?:двух\s+)?чисел|произведение\s+чисел|вычислит|сумм[а-я]*\s+как[а-я]+\s+двух\s+чисел|отношение\s+чисел|к\s+разности\s+чисел|к\s+сумме\s+чисел|если\s+к\s+\w+\s+чисел|если\s+к\s+(?:разности|сумме|произведению)|(?:сумма|разность|произведение|частное)\s+каких\s+двух\s+чисел|определите\s+(?:наибольшее|наименьшее)\s+из\s+значений|значений\s+числовых\s+выражений/i },
  { sub: 'alg-numbers',
    test: /натуральн|целое\s+число|рациональн|действительн|какое.{0,40}число|НОД\s*\(|НОК\s*\(|наибольший\s+общий\s+делитель|наименьшее\s+общее\s+кратное|простое\s+число|составное\s+число|множеств[а-я]*\s+(?:составн|прост|натуральн|цел)|определите\s+промежуток|промежутк[а-я]*.{0,30}принадлежит\s+число|является\s+простым/i },
];

/* ── Difficulty heuristic ─────────────────────────────────────── */
/* 1..5; based on task_idx position within the 10-question variant. */
function difficultyFor(taskIdx, type) {
  // Tasks 1-3 (easy), 4-6 (medium), 7-10 (hard)
  let base;
  if (taskIdx <= 3) base = taskIdx === 1 ? 1 : 2;
  else if (taskIdx <= 6) base = 3;
  else if (taskIdx <= 8) base = 4;
  else base = 5;
  if (type === 'long') base = Math.min(5, base + 1);
  return base;
}

/* ── Classifier ───────────────────────────────────────────────── */
function classify(row) {
  const txt = stripText(row.text_html);
  for (const rule of RULES) {
    if (rule.test.test(txt)) {
      const parent = rule.sub.startsWith('alg-')   ? 'algebra'
                   : rule.sub.startsWith('geom-')  ? 'geometry'
                   : 'theory';
      return { topic: parent, subtopic: rule.sub };
    }
  }
  return { topic: null, subtopic: null };
}

/* ── Main ─────────────────────────────────────────────────────── */
function main() {
  if (RESET && !DRY) {
    const r = db.prepare(`UPDATE exam_tasks SET topic = NULL, subtopic = NULL, difficulty = NULL WHERE exam_key = ?`).run(examKey);
    console.log(`[reset] cleared tags on ${r.changes} rows`);
  }

  const tasks = db.prepare(`
    SELECT id, variant, task_idx, task_type, text_html
      FROM exam_tasks
     WHERE exam_key = ?
     ORDER BY variant, task_idx
  `).all(examKey);

  const upd = db.prepare(`UPDATE exam_tasks SET topic = ?, subtopic = ?, difficulty = ? WHERE id = ?`);

  const stats = {
    total:    tasks.length,
    tagged:   0,
    unknown:  0,
    bySub:    new Map(),
    byParent: new Map(),
    unknownSamples: [],
  };

  const run = DRY ? (..._args) => {} : (a, b, c, d) => upd.run(a, b, c, d);

  db.transaction(() => {
    for (const t of tasks) {
      const { topic, subtopic } = classify(t);
      const diff = difficultyFor(t.task_idx, t.task_type);

      if (subtopic) {
        stats.tagged++;
        stats.bySub.set(subtopic, (stats.bySub.get(subtopic) || 0) + 1);
        stats.byParent.set(topic, (stats.byParent.get(topic) || 0) + 1);
      } else {
        stats.unknown++;
        if (stats.unknownSamples.length < 12) {
          stats.unknownSamples.push({
            v: t.variant, i: t.task_idx,
            text: stripText(t.text_html).slice(0, 100),
          });
        }
      }
      run(topic, subtopic, diff, t.id);
    }
  })();

  /* ── Report ── */
  const pct = n => ((n / stats.total) * 100).toFixed(1) + '%';
  console.log(`\n═══ ${examKey} tagging ═══${DRY ? '  (DRY RUN)' : ''}`);
  console.log(`Total tasks: ${stats.total}`);
  console.log(`Tagged:      ${stats.tagged}  (${pct(stats.tagged)})`);
  console.log(`Unknown:     ${stats.unknown}  (${pct(stats.unknown)})`);

  console.log(`\nBy section:`);
  for (const [parent, n] of [...stats.byParent.entries()].sort((a, b) => b[1] - a[1])) {
    console.log(`  ${parent.padEnd(10)} ${String(n).padStart(4)}  (${pct(n)})`);
  }

  console.log(`\nBy subtopic:`);
  for (const [sub, n] of [...stats.bySub.entries()].sort((a, b) => b[1] - a[1])) {
    console.log(`  ${sub.padEnd(22)} ${String(n).padStart(4)}  (${pct(n)})`);
  }

  if (stats.unknownSamples.length) {
    console.log(`\nUntagged samples (first ${stats.unknownSamples.length}):`);
    for (const s of stats.unknownSamples) {
      console.log(`  v${String(s.v).padStart(2,'0')} t${s.i}: ${s.text}`);
    }
  }
  if (DRY) console.log(`\n[DRY RUN] no changes written.`);
}

main();
