// review_geom11.js - аудит Геометрии 11 (только репорт)
const fs = require('fs');
const path = require('path');

const FILES = [
  'frontend/textbooks/geometry_11_hub.html',
  'frontend/textbooks/geometry_11_ch1.html',
  'frontend/textbooks/geometry_11_ch2.html',
  'frontend/textbooks/geometry_11_ch3.html',
  'frontend/textbooks/geometry_11_ch4.html',
];
const ROOT = path.join(__dirname, '..', '..');

// Словарь KaTeX-команд (часто встречающиеся)
const KATEX_CMDS = new Set([
  'dfrac','frac','tfrac','sqrt','cdot','times','pm','mp','ne','le','ge','approx',
  'angle','triangle','square','vec','overline','overrightarrow','overrightarrow',
  'sin','cos','tan','cot','arcsin','arccos','arctan','log','ln','lg','exp',
  'pi','alpha','beta','gamma','delta','varepsilon','theta','lambda','mu','phi','omega','rho','sigma','tau',
  'Delta','Gamma','Omega','Phi','Sigma','Theta','Lambda',
  'boxed','underline','mathbf','mathrm','text','textbf','textit',
  'sum','prod','int','infty','lim','to','rightarrow','leftarrow','Rightarrow','Leftrightarrow',
  'left','right','big','Big','bigg','Bigg','quad','qquad',',','!',
  'cap','cup','in','notin','subset','supset','emptyset','varnothing',
  'parallel','perp','cong','sim','equiv','neq',
  'begin','end','array','matrix','pmatrix','bmatrix','cases','aligned','align',
  'displaystyle','textstyle','scriptstyle',
  'colon','ldots','cdots','vdots','ddots',
  'mathbb','mathcal','mathfrak','operatorname',
  'hat','tilde','bar','dot','widehat','widetilde',
  'binom','choose','over','atop',
  'div','star','ast','circ','bullet',
  'forall','exists','land','lor','neg','iff','implies',
  'leq','geq','prec','succ','subseteq','supseteq',
  'partial','nabla','degree','prime',
  'oplus','ominus','otimes','odot',
  'mathring','space','phantom','vphantom',
  'overset','underset','stackrel',
  'color','textcolor','rgb','href',
  'rule','kern','mskip','hspace','vspace',
  'tag','label','ref',
  'lvert','rvert','lVert','rVert','vert','Vert','|',
  'oint','iint','iiint',
  'because','therefore',
  'leftrightarrow','Leftarrow','longleftarrow','longrightarrow',
  'mapsto','hookrightarrow',
]);

const EMOJI_CHARS = ['✓','✗','⚠','🔥','📐','✅','❌','🎯','🔴','🟢','🟡','📊','📈','📉','🎓','📚','🧮','🔬','🌟','⭐','💡','🚀','🎉','📝','📖','🗒'];

const AUTHOR_NAMES = ['Латотин','Чеботаревский','Горбунова','Цыбулько','Шлыков','Подгорная'];

function scanFile(rel) {
  const fp = path.join(ROOT, rel);
  const src = fs.readFileSync(fp, 'utf8');
  const out = { file: rel, katex: [], optionsKatex: [], emoji: [], authors: [], pixels: [], g3d: {} };

  // 1. KaTeX ошибки: ищем backslash перед буквой (одиночный) в JS template literals и в HTML
  // Анализируем строчно
  const lines = src.split(/\r?\n/);
  // Regex: одиночный \ перед 2+ буквами, не предшествуемый \
  const reSingle = /(?<!\\)\\([a-zA-Z]{2,})/g;
  // Regex: двойной \\command (нормальный для JS-литералов)
  // Чтобы не плодить шум: репортим только когда команда из KATEX_CMDS

  // Делим на блоки: внутри <script> и вне
  let inScript = false;
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    if (/<script(\s|>)/i.test(line)) inScript = true;
    // Сначала проверим текущую строку
    // Внутри <script> — нужен \\cmd (т.к. JS экранирует). Одиночный \cmd = ошибка.
    // Вне <script> (чистый HTML, например в title) — \cmd нормально для KaTeX render.

    // Найдём все совпадения \cmd
    let m;
    const matches = [];
    const re = /\\+([a-zA-Z]{2,})/g;
    while ((m = re.exec(line)) !== null) {
      const bsCount = m[0].length - m[1].length; // число backslashes
      const cmd = m[1];
      if (!KATEX_CMDS.has(cmd)) continue;
      matches.push({ cmd, bsCount, idx: m.index, full: m[0] });
    }
    for (const mt of matches) {
      if (inScript) {
        // в JS template literals одиночный \X неправильно. Нужно \\X (bsCount>=2)
        if (mt.bsCount === 1) {
          out.katex.push({ line: i+1, cmd: mt.cmd, snippet: line.trim().slice(0, 140) });
        }
      } else {
        // в чистом HTML \cmd ок
      }
    }
    if (/<\/script>/i.test(line)) inScript = false;
  }

  // 2. <option> с сырым KaTeX (содержит $ или \\sqrt и т.п.)
  const optRe = /<option[^>]*>([^<]*)<\/option>/g;
  let om;
  while ((om = optRe.exec(src)) !== null) {
    const txt = om[1];
    if (/\$[^$]+\$/.test(txt) || /\\\\[a-zA-Z]+/.test(txt) || /\\(dfrac|sqrt|frac|sin|cos|pi|angle|vec|cdot|times)/.test(txt)) {
      out.optionsKatex.push({ snippet: om[0].slice(0,160) });
    }
  }
  // Также проверим динамический innerHTML на option в JS
  const scriptBlocks = [...src.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/g)].map(m=>m[1]);
  for (const sb of scriptBlocks) {
    // ищем <option ...>$...$</option> или \\sqrt в template-literals
    const re2 = /<option[^>]*>[^<]*(?:\$[^$]+\$|\\\\(?:dfrac|sqrt|frac|sin|cos|pi|angle|vec|cdot|times))[^<]*<\/option>/g;
    let mm;
    while ((mm = re2.exec(sb)) !== null) {
      out.optionsKatex.push({ snippet: mm[0].slice(0,160), inScript: true });
    }
  }

  // 3. Эмодзи (исключая SVG)
  for (const ch of EMOJI_CHARS) {
    let idx = 0;
    while ((idx = src.indexOf(ch, idx)) !== -1) {
      // Найти строку
      const upto = src.slice(0, idx);
      const lineNum = upto.split('\n').length;
      const lineStart = upto.lastIndexOf('\n') + 1;
      const lineEnd = src.indexOf('\n', idx);
      const lineText = src.slice(lineStart, lineEnd === -1 ? src.length : lineEnd);
      out.emoji.push({ char: ch, line: lineNum, snippet: lineText.trim().slice(0, 120) });
      idx++;
    }
  }

  // 4. Авторы
  for (const name of AUTHOR_NAMES) {
    if (src.includes(name)) {
      // Найдём строку
      const idx = src.indexOf(name);
      const upto = src.slice(0, idx);
      const lineNum = upto.split('\n').length;
      const lineStart = upto.lastIndexOf('\n') + 1;
      const lineEnd = src.indexOf('\n', idx);
      const lineText = src.slice(lineStart, lineEnd === -1 ? src.length : lineEnd);
      out.authors.push({ name, line: lineNum, snippet: lineText.trim().slice(0, 160) });
    }
  }

  // 5. Пиксели в подписях: ищем sliders с min/max, и подписи рядом
  // Простая эвристика: <input type="range" min="X" max="Y" ...> + value/label рядом
  const sliderRe = /<input[^>]*type=["']range["'][^>]*>/g;
  let sm;
  while ((sm = sliderRe.exec(src)) !== null) {
    const tag = sm[0];
    const minM = tag.match(/\bmin=["'](\d+)["']/);
    const maxM = tag.match(/\bmax=["'](\d+)["']/);
    const idM = tag.match(/\bid=["']([^"']+)["']/);
    if (minM && maxM) {
      const min = +minM[1], max = +maxM[1];
      // подозрительно если min>20 и max>100 (вероятно пиксели)
      const suspicious = min >= 20 && max >= 100;
      if (suspicious) {
        const upto = src.slice(0, sm.index);
        const lineNum = upto.split('\n').length;
        out.pixels.push({ line: lineNum, id: idM ? idM[1] : '', min, max, tag: tag.slice(0,180) });
      }
    }
  }

  // 6. G3D usage
  out.g3d.includesScript = /<script[^>]*src=["']\/js\/g3d\.js["'][^>]*>/.test(src);
  out.g3d.usesPrism = /G3D\.prismMesh/.test(src);
  out.g3d.usesCylinder = /G3D\.cylinderMesh/.test(src);
  out.g3d.usesPyramid = /G3D\.pyramidMesh/.test(src);
  out.g3d.usesCone = /G3D\.coneMesh/.test(src);
  out.g3d.usesSphere = /G3D\.sphereWireframe/.test(src);
  out.g3d.usesAttachOrbit = /G3D\.attachOrbit/.test(src);
  out.g3d.usesPresetView = /G3D\.presetView/.test(src);
  out.g3d.viewButtons = (src.match(/Изо|Спереди|Сверху|Сбоку/g) || []).length;

  // 7. Структура: theory cards (3), interactives (4), "Я прочитал"
  out.theoryCards = (src.match(/class=["'][^"']*theory[^"']*card/g) || []).length;
  out.interactiveCards = (src.match(/class=["'][^"']*interactive[^"']*/g) || []).length;
  out.readBtns = (src.match(/Я прочитал|я прочитал/g) || []).length;
  out.bossCount = (src.match(/босс|Босс|BOSS/g) || []).length;
  out.paragraphCount = (src.match(/§\d+/g) || []).length;
  out.finalSections = (src.match(/Финал|финал раздела|Финал раздела/g) || []).length;
  out.length = src.length;

  return out;
}

const results = FILES.map(scanFile);

// Report
console.log('='.repeat(80));
console.log('AUDIT GEOMETRY 11');
console.log('='.repeat(80));

let totals = { katex: 0, options: 0, emoji: 0, authors: 0, pixels: 0 };

for (const r of results) {
  console.log(`\n--- ${r.file} (${(r.length/1024).toFixed(1)} KB) ---`);
  console.log(`KaTeX errors: ${r.katex.length}`);
  if (r.katex.length) {
    r.katex.slice(0, 10).forEach(e => console.log(`  L${e.line}: \\${e.cmd} | ${e.snippet}`));
    if (r.katex.length > 10) console.log(`  ... +${r.katex.length-10} more`);
  }
  console.log(`<option> KaTeX: ${r.optionsKatex.length}`);
  r.optionsKatex.slice(0,5).forEach(o => console.log(`  ${o.inScript?'[JS] ':''}${o.snippet}`));
  console.log(`Emoji: ${r.emoji.length}`);
  r.emoji.slice(0,5).forEach(e => console.log(`  L${e.line} '${e.char}': ${e.snippet}`));
  console.log(`Authors: ${r.authors.length}`);
  r.authors.slice(0,5).forEach(a => console.log(`  L${a.line} '${a.name}': ${a.snippet}`));
  console.log(`Pixel sliders (susp.): ${r.pixels.length}`);
  r.pixels.slice(0,5).forEach(p => console.log(`  L${p.line} #${p.id} min=${p.min} max=${p.max}`));
  console.log(`G3D: script=${r.g3d.includesScript}, prism=${r.g3d.usesPrism}, cyl=${r.g3d.usesCylinder}, pyr=${r.g3d.usesPyramid}, cone=${r.g3d.usesCone}, sphere=${r.g3d.usesSphere}, orbit=${r.g3d.usesAttachOrbit}, presetView=${r.g3d.usesPresetView}, viewBtnHits=${r.g3d.viewButtons}`);
  console.log(`Structure: theoryCards=${r.theoryCards}, interactives=${r.interactiveCards}, readBtns=${r.readBtns}, bossHits=${r.bossCount}, §count=${r.paragraphCount}, finalHits=${r.finalSections}`);

  totals.katex += r.katex.length;
  totals.options += r.optionsKatex.length;
  totals.emoji += r.emoji.length;
  totals.authors += r.authors.length;
  totals.pixels += r.pixels.length;
}

console.log('\n' + '='.repeat(80));
console.log('TOTALS:', totals);
console.log('='.repeat(80));
