/* audit_chem8.js — аудит KaTeX и оформления учебника «Химия 8».
 * Загружает каждую страницу в jsdom (renderMathInElement застаблен → $…$ остаются
 * литералами с уже раскрытыми JS-эскейпами), строит все §, извлекает формулы и
 * проверяет: баланс $, баланс {}, отсутствие управляющих символов (следы \t/\n),
 * пустые формулы, «сырые» $…$ вне рендера. Запуск: node backend/scripts/audit_chem8.js
 */
'use strict';
const fs = require('fs');
const path = require('path');
const { JSDOM, VirtualConsole } = require('jsdom');

const ROOT = path.join(__dirname, '..', '..');
const readF = p => fs.readFileSync(path.join(ROOT, p), 'utf8');
const wait = ms => new Promise(r => setTimeout(r, ms));

const PAGES = [
  ['chemistry_8_intro.html', 'chem8_intro_widgets'],
  ['chemistry_8_ch1.html', 'chem8_ch1_widgets'],
  ['chemistry_8_ch2.html', 'chem8_ch2_widgets'],
  ['chemistry_8_ch3.html', 'chem8_ch3_widgets'],
  ['chemistry_8_ch4.html', 'chem8_ch4_widgets'],
  ['chemistry_8_ch5.html', 'chem8_ch5_widgets'],
  ['chemistry_8_ch6.html', 'chem8_ch6_widgets']
];

function buildPage(file, widgets) {
  let html = readF('frontend/textbooks/' + file);
  const inl = {
    '/js/biochem-core.js': readF('frontend/js/biochem-core.js'),
    '/js/chem8_svg.js': readF('frontend/js/chem8_svg.js'),
    '/js/chem8_mol.js': readF('frontend/js/chem8_mol.js'),
    ['/js/' + widgets + '.js']: readF('frontend/js/' + widgets + '.js'),
    '/js/chem8_engine.js': readF('frontend/js/chem8_engine.js')
  };
  html = html
    .replace(/<script defer src="https:\/\/cdn[^"]*"[^>]*><\/script>/g, '')
    .replace(/<script src="\/js\/api\.js" defer><\/script>/, '<script>window.renderMathInElement=function(){};</script>')
    .replace(/<script src="\/js\/xp\.js" defer><\/script>/, '');
  Object.keys(inl).forEach(src => {
    html = html.replace(new RegExp('<script src="' + src + '" defer><\\/script>'), () => '<script>\n' + inl[src] + '\n</script>');
  });
  return html;
}

function extractMath(s) {
  const out = [];
  // $$...$$ затем $...$
  let re = /\$\$([\s\S]+?)\$\$/g, m;
  let masked = s;
  while ((m = re.exec(s)) !== null) out.push({ disp: true, body: m[1] });
  masked = s.replace(/\$\$[\s\S]+?\$\$/g, '');
  re = /\$([^$]*)\$/g;
  while ((m = re.exec(masked)) !== null) out.push({ disp: false, body: m[1] });
  return out;
}

function checkBraces(b) { let d = 0; for (const c of b) { if (c === '{') d++; else if (c === '}') d--; if (d < 0) return false; } return d === 0; }
function hasCtrl(b) { return /[\t\n\r\f\v\b]/.test(b); }

async function auditPage(file, widgets) {
  const issues = [];
  const vc = new VirtualConsole(); const errs = [];
  vc.on('jsdomError', e => errs.push(e.message));
  const dom = new JSDOM(buildPage(file, widgets), {
    runScripts: 'dangerously', pretendToBeVisual: true, virtualConsole: vc, url: 'http://localhost/',
    beforeParse(w) { w.scrollTo = function () {}; }
  });
  await wait(120);
  const doc = dom.window.document;
  const paras = (dom.window.PARAS || []).map(p => p.id);
  for (const id of paras) { try { dom.window.goTo(id); } catch (e) {} }
  await wait(120);

  if (errs.length) issues.push('script errors: ' + errs.join(' | '));

  // собрать все § тела + sidebar
  let html = '';
  doc.querySelectorAll('[id$="-body"]').forEach(el => { html += el.innerHTML + '\n'; });
  const sidebar = doc.getElementById('sidebar-content'); if (sidebar) html += sidebar.innerHTML;

  // баланс $ (нечётное число одиночных $ вне $$)
  const noDisp = html.replace(/\$\$[\s\S]+?\$\$/g, '');
  const singles = (noDisp.match(/\$/g) || []).length;
  if (singles % 2 !== 0) issues.push('нечётное число одиночных $ (' + singles + ')');

  const maths = extractMath(html);
  let bad = 0;
  for (const m of maths) {
    const b = m.body;
    if (!b.trim()) { issues.push('пустая формула $' + (m.disp ? '$' : '') + '$'); bad++; continue; }
    if (!checkBraces(b)) { issues.push('несбалансированные {} в: ' + b.slice(0, 50)); bad++; }
    if (hasCtrl(b)) { issues.push('управляющий символ (след \\t/\\n?) в: ' + JSON.stringify(b.slice(0, 50))); bad++; }
    // одиночный backslash перед буквой, не часть известной команды? — грубая эвристика: \ в конце
    if (/\\$/.test(b)) { issues.push('формула заканчивается на \\: ' + b.slice(-20)); bad++; }
  }
  return { file, mathCount: maths.length, badCount: bad, issues };
}

(async () => {
  let total = 0, totalBad = 0;
  for (const [file, w] of PAGES) {
    const r = await auditPage(file, w);
    total += r.mathCount; totalBad += r.badCount;
    console.log('\n=== ' + file + ' — формул: ' + r.mathCount + ', проблем: ' + r.issues.length + ' ===');
    if (r.issues.length) r.issues.slice(0, 25).forEach(i => console.log('  ! ' + i));
    else console.log('  OK');
  }
  console.log('\nИТОГО формул: ' + total + ', проблемных: ' + totalBad);
  process.exit(0);
})();
