'use strict';
/*
 * Регресс-тесты химического ядра frontend/js/biochem-core.js (window.BIO).
 * Чистые функции (формулы, VSEPR, заряды, диполь, баланс, SMILES) — без DOM.
 */
const test = require('node:test');
const assert = require('node:assert');
const path = require('node:path');

// shim browser global, then load the frontend module
global.window = global;
require(path.join(__dirname, '..', '..', 'frontend', 'js', 'biochem-core.js'));
const B = global.BIO;

const m = (atoms, bonds) => ({ atoms, bonds });
const water   = m([{ id: 1, s: 'O' }, { id: 2, s: 'H' }, { id: 3, s: 'H' }], [{ f: 1, t: 2, o: 1 }, { f: 1, t: 3, o: 1 }]);
const co2     = m([{ id: 1, s: 'C' }, { id: 2, s: 'O' }, { id: 3, s: 'O' }], [{ f: 1, t: 2, o: 2 }, { f: 1, t: 3, o: 2 }]);
const methane = m([{ id: 1, s: 'C' }, { id: 2, s: 'H' }, { id: 3, s: 'H' }, { id: 4, s: 'H' }, { id: 5, s: 'H' }],
  [{ f: 1, t: 2, o: 1 }, { f: 1, t: 3, o: 1 }, { f: 1, t: 4, o: 1 }, { f: 1, t: 5, o: 1 }]);

function bondAngle(a3, c, n1, n2) {
  const C = a3.find(a => a.id === c), P = a3.find(a => a.id === n1), Q = a3.find(a => a.id === n2);
  const v1 = [P.x - C.x, P.y - C.y, P.z - C.z], v2 = [Q.x - C.x, Q.y - C.y, Q.z - C.z];
  const d = v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
  return Math.acos(d / (Math.hypot(...v1) * Math.hypot(...v2))) * 180 / Math.PI;
}

test('hillFormula & molarMass', () => {
  assert.equal(B.hillFormula(methane.atoms), 'CH4');
  assert.ok(Math.abs(B.molarMass(methane.atoms) - 16.04) < 0.05);
});

test('parseFormula handles parentheses', () => {
  assert.deepEqual(B.parseFormula('Ca(OH)2'), { Ca: 1, O: 2, H: 2 });
});

test('VSEPR geometry: water bent, methane tetrahedral, CO2 linear', () => {
  const gw = B.vsepr(water.atoms, water.bonds);
  assert.equal(gw.shape, 'угловая');
  assert.equal(gw.hybridization, 'sp³');

  const gm = B.vsepr(methane.atoms, methane.bonds);
  assert.equal(gm.shape, 'тетраэдрическая');
  assert.ok(Math.abs(bondAngle(gm.atoms3d, 1, 2, 3) - 109.5) < 1.5);

  const gc = B.vsepr(co2.atoms, co2.bonds);
  assert.equal(gc.shape, 'линейная');
  assert.ok(Math.abs(bondAngle(gc.atoms3d, 1, 2, 3) - 180) < 1);
});

test('partial charges: water O negative, H positive', () => {
  const q = B.partialCharges(water.atoms, water.bonds);
  assert.ok(q[1] < -0.3, 'O should be δ−');
  assert.ok(q[2] > 0.1 && q[3] > 0.1, 'H should be δ+');
});

test('polarity: symmetric molecules nonpolar, water polar', () => {
  assert.equal(B.polarity(co2.atoms, co2.bonds).label, 'Неполярная');
  assert.equal(B.polarity(methane.atoms, methane.bonds).label, 'Неполярная');
  assert.ok(B.polarity(water.atoms, water.bonds).dipole > 0.3);
});

test('balance: classic equations', () => {
  assert.deepEqual(B.balance(['H2', 'O2'], ['H2O']).coefficients, [2, 1, 2]);
  assert.deepEqual(B.balance(['CH4', 'O2'], ['CO2', 'H2O']).coefficients, [1, 2, 1, 2]);
  assert.deepEqual(B.balance(['Fe', 'O2'], ['Fe2O3']).coefficients, [4, 3, 2]);
  assert.deepEqual(B.balance(['Ca(OH)2', 'HCl'], ['CaCl2', 'H2O']).coefficients, [1, 2, 1, 2]);
});

test('parseSmiles: skeleton + implicit H', () => {
  assert.equal(B.hillFormula(B.parseSmiles('CCO').atoms), 'C2H6O');
  assert.equal(B.hillFormula(B.parseSmiles('CC(=O)O').atoms), 'C2H4O2');
  assert.equal(B.hillFormula(B.parseSmiles('C1=CC=CC=C1').atoms), 'C6H6');
  assert.equal(B.hillFormula(B.parseSmiles('ClC(Cl)(Cl)Cl').atoms), 'CCl4');
  assert.equal(B.parseSmiles('bad[x]'), null);
});

test('analyze returns a complete report', () => {
  const a = B.analyze(water.atoms, water.bonds);
  assert.equal(a.formula, 'H2O');
  assert.ok(a.geometry && a.geometry.shape === 'угловая');
  assert.ok(a.dipole > 0);
  assert.ok(a.massFractions.O > 80);
});
