/* chem8_mol.js — 3D-модели молекул и кристаллических решёток (U4).
 * Поверх biochem-core (window.BIO): vsepr + render3D. Вращение мышью/пальцем
 * (window-listeners, без setPointerCapture). Экспорт: window.Chem8Mol.
 */
(function (W) {
  'use strict';
  var D = W.document;
  function BIO() { return W.BIO; }
  function C() { return W.Chem8 || {}; }

  /* предопределённые молекулы: atoms + bonds */
  var MOL = {
    H2:  { atoms: [{ id: 1, s: 'H' }, { id: 2, s: 'H' }], bonds: [{ f: 1, t: 2, o: 1 }], name: 'Водород H₂' },
    Cl2: { atoms: [{ id: 1, s: 'Cl' }, { id: 2, s: 'Cl' }], bonds: [{ f: 1, t: 2, o: 1 }], name: 'Хлор Cl₂' },
    O2:  { atoms: [{ id: 1, s: 'O' }, { id: 2, s: 'O' }], bonds: [{ f: 1, t: 2, o: 2 }], name: 'Кислород O₂' },
    N2:  { atoms: [{ id: 1, s: 'N' }, { id: 2, s: 'N' }], bonds: [{ f: 1, t: 2, o: 3 }], name: 'Азот N₂' },
    HCl: { atoms: [{ id: 1, s: 'H' }, { id: 2, s: 'Cl' }], bonds: [{ f: 1, t: 2, o: 1 }], name: 'Хлороводород HCl' },
    H2O: { atoms: [{ id: 1, s: 'O' }, { id: 2, s: 'H' }, { id: 3, s: 'H' }], bonds: [{ f: 1, t: 2, o: 1 }, { f: 1, t: 3, o: 1 }], name: 'Вода H₂O' },
    CO2: { atoms: [{ id: 1, s: 'C' }, { id: 2, s: 'O' }, { id: 3, s: 'O' }], bonds: [{ f: 1, t: 2, o: 2 }, { f: 1, t: 3, o: 2 }], name: 'Углекислый газ CO₂' },
    NH3: { atoms: [{ id: 1, s: 'N' }, { id: 2, s: 'H' }, { id: 3, s: 'H' }, { id: 4, s: 'H' }], bonds: [{ f: 1, t: 2, o: 1 }, { f: 1, t: 3, o: 1 }, { f: 1, t: 4, o: 1 }], name: 'Аммиак NH₃' },
    CH4: { atoms: [{ id: 1, s: 'C' }, { id: 2, s: 'H' }, { id: 3, s: 'H' }, { id: 4, s: 'H' }, { id: 5, s: 'H' }], bonds: [{ f: 1, t: 2, o: 1 }, { f: 1, t: 3, o: 1 }, { f: 1, t: 4, o: 1 }, { f: 1, t: 5, o: 1 }], name: 'Метан CH₄' }
  };

  function mkCanvas(host, h) {
    var cv = D.createElement('canvas'); cv.className = 'mol-cv';
    cv.style.width = '100%'; cv.style.height = (h || 200) + 'px'; cv.style.touchAction = 'none';
    cv.style.borderRadius = '12px'; cv.style.display = 'block';
    host.appendChild(cv); return cv;
  }
  function fit(cv) {
    var dpr = W.devicePixelRatio || 1, w = cv.offsetWidth || 280, h = cv.offsetHeight || 200;
    cv.width = Math.round(w * dpr); cv.height = Math.round(h * dpr);
    var ctx = cv.getContext && cv.getContext('2d'); if (!ctx) return null; // jsdom без canvas
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    return { ctx: ctx, W: w, H: h };
  }

  /* общий движок вращения: state выше redraw, window-listeners */
  function attachRotate(cv, state, redraw) {
    var dragging = false, lx = 0, ly = 0;
    cv.addEventListener('pointerdown', function (e) { dragging = true; lx = e.clientX; ly = e.clientY; state.spin = false; });
    W.addEventListener('pointermove', function (e) {
      if (!dragging) return;
      state.rotY += (e.clientX - lx) * 0.01; state.rotX += (e.clientY - ly) * 0.01;
      lx = e.clientX; ly = e.clientY; redraw();
    });
    W.addEventListener('pointerup', function () { dragging = false; });
  }

  /* ── 3D-модель молекулы ── */
  function molModel(mount, key) {
    var host = typeof mount === 'string' ? D.querySelector(mount) : mount;
    if (!host || !BIO()) return null;
    var keys = Object.keys(MOL);
    host.innerHTML = '<div class="fld"><label>Молекула</label><select class="mol-sel">' +
      keys.map(function (k) { return '<option value="' + k + '"' + (k === key ? ' selected' : '') + '>' + MOL[k].name + '</option>'; }).join('') + '</select>'
      + '<button class="btn mol-spin">⟳ Вращение</button></div>';
    var stage = D.createElement('div'); host.appendChild(stage);
    var cv = mkCanvas(stage, 200);
    var info = D.createElement('div'); info.className = 'out mol-info'; host.appendChild(info);
    var sel = host.querySelector('.mol-sel'), spinBtn = host.querySelector('.mol-spin');
    var state = { rotX: -0.35, rotY: 0.6, scale: 2.6, spin: true };
    var cur;
    function load(k) {
      cur = MOL[k]; var g = BIO().vsepr(cur.atoms, cur.bonds); cur.g = g;
      var pol = BIO().polarity(cur.atoms, cur.bonds);
      var mr = C().molarMass ? C().molarMass(k) : BIO().molarMass(cur.atoms);
      var bondTxt = cur.atoms.length === 2 && C().bondClass
        ? C().bondClass(cur.atoms[0].s, cur.atoms[1].s).type
        : (pol.label === 'Ионная' ? 'ионная' : 'ковалентная');
      info.className = 'out mol-info ok';
      info.innerHTML = '<span class="bd"><b>' + cur.name + '</b> · M = ' + (C().fmt ? C().fmt(mr) : mr) + ' г/моль<br>'
        + 'Связь: ' + bondTxt + ' · молекула: <b>' + pol.label.toLowerCase() + '</b>'
        + (g.shape ? ' · форма: ' + g.shape : '') + '</span>';
    }
    function redraw() {
      var d = fit(cv); if (!d) return;
      BIO().render3D(d.ctx, cur.g.atoms3d, cur.bonds, { W: d.W, H: d.H, rotX: state.rotX, rotY: state.rotY, scale: state.scale }, { bg: '#0b1220' });
    }
    sel.addEventListener('change', function () { load(sel.value); redraw(); });
    spinBtn.addEventListener('click', function () { state.spin = !state.spin; spinBtn.classList.toggle('primary', state.spin); });
    attachRotate(cv, state, redraw);
    load(key && MOL[key] ? key : keys[0]);
    redraw();
    if (fit(cv)) (function loop() { if (state.spin) { state.rotY += 0.012; redraw(); } W.requestAnimationFrame(loop); })(); // не стартуем цикл без canvas-контекста (jsdom)
    return { el: host };
  }

  /* ── кристаллические решётки (§41) ── */
  var LAT = {
    ionic: { name: 'Ионная (NaCl)', build: function () { return cube(['Na', 'Cl']); }, note: 'Узлы — ионы Na⁺ и Cl⁻. Прочная решётка → тугоплавкие, твёрдые вещества.' },
    atomic: { name: 'Атомная (алмаз)', build: function () { return cube(['C', 'C']); }, note: 'Узлы — атомы, связанные ковалентно. Очень твёрдые, тугоплавкие.' },
    molecular: { name: 'Молекулярная (лёд)', build: function () { return cube(['O', 'O']); }, note: 'Узлы — молекулы со слабым притяжением. Летучие, легкоплавкие.' },
    metallic: { name: 'Металлическая (Fe)', build: function () { return cube(['Fe', 'Fe'], true); }, note: 'Ион-остовы металла в «электронном газе». Ковкие, проводят ток.' }
  };
  function cube(symPair, electrons) {
    var L = 16, atoms = [], id = 1;
    for (var xi = -1; xi <= 1; xi += 2) for (var yi = -1; yi <= 1; yi += 2) for (var zi = -1; zi <= 1; zi += 2) {
      var parity = ((xi + yi + zi) / 2 + 3) % 2;
      atoms.push({ id: id++, s: symPair[parity], x: xi * L, y: yi * L, z: zi * L });
    }
    var bonds = [];
    for (var i = 0; i < atoms.length; i++) for (var j = i + 1; j < atoms.length; j++) {
      var a = atoms[i], b = atoms[j], dd = Math.abs(a.x - b.x) + Math.abs(a.y - b.y) + Math.abs(a.z - b.z);
      if (dd === 2 * L) bonds.push({ f: a.id, t: b.id, o: 1 });
    }
    if (electrons) for (var e = 0; e < 6; e++) atoms.push({ id: id++, s: 'H', x: (e % 3 - 1) * L, y: ((e / 3 | 0) * 2 - 1) * L * 0.5, z: 0 }); // «электроны» как мелкие точки (H — мелкий радиус)
    return { atoms: atoms, bonds: bonds };
  }
  function latticeViewer(mount, type) {
    var host = typeof mount === 'string' ? D.querySelector(mount) : mount;
    if (!host || !BIO()) return null;
    var keys = Object.keys(LAT);
    host.innerHTML = '<div class="fld"><label>Тип решётки</label><select class="lat-sel">' +
      keys.map(function (k) { return '<option value="' + k + '"' + (k === type ? ' selected' : '') + '>' + LAT[k].name + '</option>'; }).join('') + '</select></div>';
    var stage = D.createElement('div'); host.appendChild(stage);
    var cv = mkCanvas(stage, 200);
    var info = D.createElement('div'); info.className = 'out'; host.appendChild(info);
    var sel = host.querySelector('.lat-sel');
    var state = { rotX: -0.4, rotY: 0.5, scale: 2.4, spin: true };
    var cur;
    function load(k) { var l = LAT[k]; cur = l.build(); info.className = 'out ok'; info.innerHTML = '<span class="bd"><b>' + l.name + '</b><br>' + l.note + '</span>'; }
    function redraw() { var d = fit(cv); if (!d) return; BIO().render3D(d.ctx, cur.atoms, cur.bonds, { W: d.W, H: d.H, rotX: state.rotX, rotY: state.rotY, scale: state.scale }, { bg: '#0b1220' }); }
    sel.addEventListener('change', function () { load(sel.value); redraw(); });
    attachRotate(cv, state, redraw);
    load(type && LAT[type] ? type : keys[0]); redraw();
    if (fit(cv)) (function loop() { if (state.spin) { state.rotY += 0.01; redraw(); } W.requestAnimationFrame(loop); })();
    return { el: host };
  }

  W.Chem8Mol = { molModel: molModel, latticeViewer: latticeViewer, MOL: MOL };
})(window);
