'use strict';
/**
 * ElectrolysisSim v3 — Электролиз водных растворов
 * Закон Фарадея: m = M·I·t / (n·F),  F = 96485 Кл/моль
 * 6 электролитов, визуальная переработка, графики m(t)/V(t),
 * внешняя цепь с анимированными электронами, стеклянный сосуд.
 */
class ElectrolysisSim {
  static F    = 96485;
  static BG   = '#0b0b1a';
  static FONT = 'Manrope, system-ui, sans-serif';

  static ELECTROLYTES = {
    NaCl: {
      name: 'NaCl', displayName: 'NaCl (водный р-р)',
      cation: 'Na⁺', anion: 'Cl⁻',
      M: 2, n: 2, R: 8,
      solColor: [80, 160, 220],
      solAlpha: [0.08, 0.26],
      cathodeProduct: 'H₂↑', anodeProduct: 'Cl₂↑',
      depositColor: null, depositLabel: null,
      cathodeBubColor: 'rgba(160,210,255,0.65)',
      anodeBubColor:   'rgba(200,255,170,0.60)',
      cathodeEq: '2H₂O + 2e⁻ → H₂ + 2OH⁻',
      anodeEq:   '2Cl⁻ − 2e⁻ → Cl₂',
      voltage: 6,
    },
    CuSO4: {
      name: 'CuSO₄', displayName: 'CuSO₄ (водный р-р)',
      cation: 'Cu²⁺', anion: 'SO₄²⁻',
      M: 63.546, n: 2, R: 12,
      solColor: [30, 100, 220],
      solAlpha: [0.10, 0.38],
      cathodeProduct: 'Cu↓', anodeProduct: 'O₂↑',
      depositColor: '#c47a30', depositLabel: 'Cu',
      depositGrad: ['rgba(196,122,48,0.35)', 'rgba(196,122,48,0.9)'],
      cathodeBubColor: null,
      anodeBubColor:   'rgba(200,215,255,0.60)',
      cathodeEq: 'Cu²⁺ + 2e⁻ → Cu↓',
      anodeEq:   '2H₂O − 4e⁻ → O₂ + 4H⁺',
      voltage: 4,
    },
    H2SO4: {
      name: 'H₂SO₄', displayName: 'H₂SO₄ (водный р-р)',
      cation: 'H⁺', anion: 'SO₄²⁻',
      M: 2, n: 2, R: 6,
      solColor: [210, 215, 230],
      solAlpha: [0.04, 0.14],
      cathodeProduct: 'H₂↑', anodeProduct: 'O₂↑',
      depositColor: null, depositLabel: null,
      cathodeBubColor: 'rgba(160,210,255,0.65)',
      anodeBubColor:   'rgba(200,215,255,0.60)',
      cathodeEq: '2H⁺ + 2e⁻ → H₂',
      anodeEq:   '2H₂O − 4e⁻ → O₂ + 4H⁺',
      voltage: 3,
    },
    KI: {
      name: 'KI', displayName: 'KI (водный р-р)',
      cation: 'K⁺', anion: 'I⁻',
      M: 2, n: 2, R: 9,
      solColor: [190, 140, 60],
      solAlpha: [0.07, 0.28],
      cathodeProduct: 'H₂↑', anodeProduct: 'I₂',
      depositColor: null, depositLabel: null,
      cathodeBubColor: 'rgba(160,210,255,0.65)',
      anodeBubColor:   'rgba(160,90,20,0.60)',
      cathodeEq: '2H₂O + 2e⁻ → H₂ + 2OH⁻',
      anodeEq:   '2I⁻ − 2e⁻ → I₂',
      voltage: 5,
    },
    ZnSO4: {
      name: 'ZnSO₄', displayName: 'ZnSO₄ (водный р-р)',
      cation: 'Zn²⁺', anion: 'SO₄²⁻',
      M: 65.38, n: 2, R: 10,
      solColor: [200, 210, 210],
      solAlpha: [0.05, 0.18],
      cathodeProduct: 'Zn↓', anodeProduct: 'O₂↑',
      depositColor: '#9aabb0', depositLabel: 'Zn',
      depositGrad: ['rgba(154,171,176,0.35)', 'rgba(154,171,176,0.9)'],
      cathodeBubColor: null,
      anodeBubColor:   'rgba(200,215,255,0.60)',
      cathodeEq: 'Zn²⁺ + 2e⁻ → Zn↓',
      anodeEq:   '2H₂O − 4e⁻ → O₂ + 4H⁺',
      voltage: 5,
    },
    AgNO3: {
      name: 'AgNO₃', displayName: 'AgNO₃ (водный р-р)',
      cation: 'Ag⁺', anion: 'NO₃⁻',
      M: 107.87, n: 1, R: 7,
      solColor: [215, 215, 225],
      solAlpha: [0.04, 0.16],
      cathodeProduct: 'Ag↓', anodeProduct: 'O₂↑',
      depositColor: '#d8dde0', depositLabel: 'Ag',
      depositGrad: ['rgba(216,221,224,0.35)', 'rgba(216,221,224,0.9)'],
      cathodeBubColor: null,
      anodeBubColor:   'rgba(200,215,255,0.60)',
      cathodeEq: 'Ag⁺ + e⁻ → Ag↓',
      anodeEq:   '2H₂O − 4e⁻ → O₂ + 4H⁺',
      voltage: 3,
    },
  };

  constructor(canvas) {
    this.canvas = canvas;
    this.ctx    = canvas.getContext('2d');
    this.W = 0; this.H = 0;

    this.voltage      = 6;
    this.electrolyte  = 'NaCl';
    this.speed        = 1;

    // display toggles
    this.showElectrons = true;
    this.showIons      = true;
    this.showBubbles   = true;
    this.showGraphs    = false;

    this._time          = 0;
    this._massDeposit   = 0;
    this._gasVolume     = 0;
    this._chargeTotal   = 0;
    this._depositH      = 0;
    this._ions          = [];
    this._bubbles       = [];
    this._electronPhase = 0;
    this._wavePhase     = 0;

    // graph history
    this._graphMass  = [];
    this._graphGas   = [];
    this._graphTime  = [];
    this._graphLastT = 0;

    this._fxIonTrailAcc = 0;
    this._fxFizzAcc     = 0;

    this.playing  = false;
    this._raf     = null;
    this._lastTs  = null;
    this.onUpdate = null;

    new ResizeObserver(() => { this.fit(); this.draw(); }).observe(canvas.parentElement);
  }

  // ── public API ────────────────────────────────────────────────

  fit() {
    const dpr = window.devicePixelRatio || 1;
    const w = this.canvas.offsetWidth  || 640;
    const h = this.canvas.offsetHeight || 420;
    this.canvas.width  = w * dpr;
    this.canvas.height = h * dpr;
    this.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    this.W = w; this.H = h;
    this._initIons();
  }

  getParams() {
    return {
      voltage:    this.voltage,
      electrolyte: this.electrolyte,
      speed:      this.speed,
    };
  }

  setParams({ voltage, electrolyte, speed } = {}) {
    if (voltage !== undefined) this.voltage = Math.max(1, Math.min(12, +voltage));
    if (speed   !== undefined) this.speed   = +speed;
    if (electrolyte !== undefined) {
      const keyMap = {
        nacl:  'NaCl', cuso4: 'CuSO4', h2so4: 'H2SO4',
        ki:    'KI',   znso4: 'ZnSO4', agno3: 'AgNO3',
      };
      const key = keyMap[String(electrolyte).toLowerCase()] || electrolyte;
      if (ElectrolysisSim.ELECTROLYTES[key] && this.electrolyte !== key) {
        this.electrolyte = key;
        this.reset(); return;
      }
    }
    this.draw(); this._emit();
  }

  preset(name) {
    const map = {
      nacl:  ['NaCl',  6],
      cuso4: ['CuSO4', 4],
      h2so4: ['H2SO4', 3],
      ki:    ['KI',    5],
      znso4: ['ZnSO4', 5],
      agno3: ['AgNO3', 3],
    };
    const entry = map[String(name).toLowerCase()] || map.nacl;
    this.voltage = entry[1]; this.electrolyte = entry[0];
    this.reset();
  }

  reset() {
    this.pause();
    this._time = 0; this._massDeposit = 0;
    this._gasVolume = 0; this._chargeTotal = 0;
    this._depositH  = 0;
    this._bubbles   = []; this._electronPhase = 0; this._wavePhase = 0;
    this._graphMass = []; this._graphGas = []; this._graphTime = [];
    this._graphLastT = 0;
    this._fxIonTrailAcc = 0; this._fxFizzAcc = 0;
    this._initIons();
    this.draw(); this._emit();
  }

  play()  { if (this.playing) return; this.playing = true; this._lastTs = null; this._tick(); }
  pause() { this.playing = false; if (this._raf) { cancelAnimationFrame(this._raf); this._raf = null; } }
  start() { this.play(); }
  stop()  { this.pause(); }

  info() {
    const I = this._current();
    return {
      voltage:       this.voltage,
      current:       +I.toFixed(3),
      electrolyte:   this.electrolyte,
      massDeposited: +this._massDeposit.toFixed(4),
      gasVolume:     +this._gasVolume.toFixed(2),
      chargeTotal:   +this._chargeTotal.toFixed(2),
      electronCount: +(this._chargeTotal / 1.602e-19).toExponential(2),
      time:          +this._time.toFixed(1),
    };
  }

  // ── internals ─────────────────────────────────────────────────

  _emit()    { if (this.onUpdate) this.onUpdate(this.info()); }
  _el()      { return ElectrolysisSim.ELECTROLYTES[this.electrolyte]; }
  _current() { return this.voltage / this._el().R; }

  _cell() {
    const { W, H } = this;
    const cw = Math.min(W * 0.46, 290);
    const ch = Math.min(H * 0.46, 205);
    return { cx: (W - cw) / 2, cy: H * 0.30, cw, ch };
  }

  _electrodes() {
    const { cx, cy, cw, ch } = this._cell();
    const ew = 14, eh = ch * 0.72, gap = cw * 0.13;
    const ey = cy + ch - eh - 6;
    return {
      cathode: { x: cx + gap,           y: ey, w: ew, h: eh },
      anode:   { x: cx + cw - gap - ew, y: ey, w: ew, h: eh },
    };
  }

  _initIons() {
    this._ions = [];
    const { cx, cy, cw, ch } = this._cell();
    if (!cw || !ch) return;
    const el = this._el();
    for (let i = 0; i < 34; i++) {
      const isCat = i < 17;
      const angle = Math.random() * Math.PI * 2;
      const spd   = 0.3 + Math.random() * 0.5;
      this._ions.push({
        x:      cx + 20 + Math.random() * (cw - 40),
        y:      cy + 14 + Math.random() * (ch - 28),
        vx:     Math.cos(angle) * spd,
        vy:     Math.sin(angle) * spd,
        charge: isCat ? 1 : -1,
        label:  isCat ? el.cation : el.anion,
        color:  isCat ? '#EF476F' : '#06D6E0',
        trail:  [],
        r:      5,
      });
    }
  }

  _spawnIon(charge) {
    const { cx, cy, cw, ch } = this._cell();
    const el = this._el();
    const spd = 0.4 + Math.random() * 0.4;
    this._ions.push({
      x:      charge > 0 ? cx + 10 : cx + cw - 10,
      y:      cy + 14 + Math.random() * (ch - 28),
      vx:     charge > 0 ? spd : -spd,
      vy:     (Math.random() - 0.5) * 0.5,
      charge,
      label:  charge > 0 ? el.cation : el.anion,
      color:  charge > 0 ? '#EF476F' : '#06D6E0',
      trail:  [],
      r:      5,
    });
  }

  _spawnBubble(x, y, color, baseR) {
    this._bubbles.push({
      x, y,
      r:     (baseR || 1.5) + Math.random() * 2,
      vx:    (Math.random() - 0.5) * 0.4,
      vy:    -(0.4 + Math.random() * 0.8),
      life:  1,
      decay: 0.004 + Math.random() * 0.006,
      color,
      born:  y,
    });
  }

  // ── simulation tick ────────────────────────────────────────────

  _tick() {
    if (!this.playing) return;
    this._raf = requestAnimationFrame(ts => {
      if (!this._lastTs) this._lastTs = ts;
      const rawDt = Math.min((ts - this._lastTs) / 1000, 0.05);
      const dt    = rawDt * this.speed;
      this._lastTs = ts;
      if (window.LabFX) LabFX.particles.update(rawDt);
      this._step(dt); this.draw(); this._emit(); this._tick();
    });
  }

  _step(dt) {
    const el   = this._el();
    const I    = this._current();
    const { cx, cy, cw, ch } = this._cell();
    const elec = this._electrodes();

    this._time         += dt;
    this._chargeTotal  += I * dt;
    this._wavePhase    += dt * 1.8;
    this._electronPhase = (this._electronPhase + dt * I * 1.4) % 1;

    // Faraday's law
    const molesPS = I / (el.n * ElectrolysisSim.F);
    if (el.depositColor) {
      this._massDeposit += el.M * molesPS * dt;
      this._depositH     = Math.min(elec.cathode.h * 0.74, this._depositH + dt * 0.15 * I);
    }
    this._gasVolume += molesPS * 22400 * dt;

    // Graph sampling every 0.5 sim-seconds
    if (this._time - this._graphLastT >= 0.5) {
      this._graphLastT = this._time;
      this._graphMass.push(this._massDeposit);
      this._graphGas.push(this._gasVolume);
      this._graphTime.push(this._time);
      if (this._graphTime.length > 200) {
        this._graphMass.shift(); this._graphGas.shift(); this._graphTime.shift();
      }
    }

    // Ion drift + thermal jitter
    const drift = I * 0.55;
    this._fxIonTrailAcc += dt;
    const doTrail = this._fxIonTrailAcc >= 0.08;
    if (doTrail) this._fxIonTrailAcc = 0;

    for (const ion of this._ions) {
      ion.vx += (ion.charge > 0 ? -drift : drift) * dt + (Math.random() - 0.5) * 0.22;
      ion.vy += (Math.random() - 0.5) * 0.16;
      ion.vx  = Math.max(-4, Math.min(4, ion.vx * 0.96));
      ion.vy  = Math.max(-3.5, Math.min(3.5, ion.vy * 0.96));
      if (doTrail) {
        ion.trail.push({ x: ion.x, y: ion.y });
        if (ion.trail.length > 4) ion.trail.shift();
      }
      ion.x  += ion.vx; ion.y += ion.vy;
      ion.x   = Math.max(cx + 5, Math.min(cx + cw - 5, ion.x));
      ion.y   = Math.max(cy + 5, Math.min(cy + ch - 5, ion.y));

      if (window.LabFX && doTrail && Math.random() < 0.25) {
        LabFX.particles.emit({ ctx: this.ctx, x: ion.x, y: ion.y, count: 1,
          color: '#FFD166', speed: 3, spread: Math.PI * 2, angle: 0,
          gravity: 0, life: 280, shape: 'dot', glow: true });
      }
    }

    // Ions reaching electrodes → discharge + bubbles
    const rm = new Set();
    for (let i = 0; i < this._ions.length; i++) {
      const ion = this._ions[i];
      if (ion.charge > 0 && ion.x <= elec.cathode.x + elec.cathode.w + 6) {
        rm.add(i);
        if (el.cathodeBubColor) {
          for (let b = 0; b < 2; b++) {
            this._spawnBubble(
              elec.cathode.x + elec.cathode.w + 3 + Math.random() * 5,
              elec.cathode.y + 10 + Math.random() * (elec.cathode.h - 20),
              el.cathodeBubColor);
          }
          if (window.LabFX) {
            LabFX.particles.emit({ ctx: this.ctx,
              x: elec.cathode.x + elec.cathode.w + 4,
              y: elec.cathode.y + Math.random() * elec.cathode.h,
              count: 1, color: '#FFFFFF', speed: 18, spread: 0.5, angle: -Math.PI / 2,
              gravity: -55, life: 1400, shape: 'ring' });
          }
        }
      }
      if (ion.charge < 0 && ion.x >= elec.anode.x - 6) {
        rm.add(i);
        if (el.anodeBubColor) {
          for (let b = 0; b < 2; b++) {
            this._spawnBubble(
              elec.anode.x - 3 - Math.random() * 5,
              elec.anode.y + 10 + Math.random() * (elec.anode.h - 20),
              el.anodeBubColor);
          }
          if (window.LabFX) {
            LabFX.particles.emit({ ctx: this.ctx,
              x: elec.anode.x - 4,
              y: elec.anode.y + Math.random() * elec.anode.h,
              count: 1, color: '#FFFFFF', speed: 18, spread: 0.5, angle: -Math.PI / 2,
              gravity: -55, life: 1400, shape: 'ring' });
          }
        }
      }
    }

    if (window.LabFX && I > 0.01) {
      this._fxFizzAcc += dt;
      if (this._fxFizzAcc >= 2.2) {
        this._fxFizzAcc = 0;
        LabFX.sound.play('fizz', { volume: 0.18 });
      }
    }

    this._ions = this._ions.filter((_, i) => !rm.has(i));

    // Replenish ions
    let cat = 0, an = 0;
    for (const ion of this._ions) ion.charge > 0 ? cat++ : an++;
    while (cat < 17) { this._spawnIon(1);  cat++; }
    while (an  < 17) { this._spawnIon(-1); an++;  }

    // Bubble physics — bubble grows as it rises
    const surfaceY = cy + 4;
    this._bubbles = this._bubbles.filter(b => {
      b.x  += b.vx + Math.sin(b.life * 20) * 0.15;
      b.y  += b.vy;
      // grow slightly as bubble rises
      const risen = Math.max(0, b.born - b.y);
      b.r   = b.r + risen * 0.0008;
      b.life -= b.decay;
      if (b.y <= surfaceY + b.r) {
        // pop — spawn micro-splash (LabFX)
        if (window.LabFX && Math.random() < 0.5) {
          LabFX.particles.emit({ ctx: this.ctx, x: b.x, y: surfaceY,
            count: 2, color: b.color, speed: 15, spread: Math.PI, angle: -Math.PI / 2,
            gravity: 30, life: 400, shape: 'dot', glow: false });
        }
        return false;
      }
      return b.life > 0;
    });
  }

  // ── draw ──────────────────────────────────────────────────────

  draw() {
    const { ctx, W, H } = this;
    if (!W || !H) return;

    // Background
    ctx.fillStyle = ElectrolysisSim.BG; ctx.fillRect(0, 0, W, H);

    // Dot grid
    ctx.fillStyle = 'rgba(255,255,255,0.018)';
    for (let x = 22; x < W; x += 22)
      for (let y = 22; y < H; y += 22) {
        ctx.beginPath(); ctx.arc(x, y, 0.7, 0, Math.PI * 2); ctx.fill();
      }

    if (window.ChemVisuals) {
      const { cx, cy, cw, ch } = this._cell();
      ChemVisuals.drawDeskBackground(ctx, W, H, cy + ch + 6);
      ChemVisuals.drawVesselShadow(ctx, cx + cw / 2, cy + ch + 4, cw * 0.55);
    }

    if (this.showElectrons) this._drawWiresAndBattery();
    this._drawCellBody();
    this._drawSolution();
    this._drawDeposit();
    this._drawElectrodes();
    if (this.showBubbles) this._drawBubbles();
    if (this.showIons)    this._drawIons();
    this._drawLabels();
    this._drawFaradayPanel();
    if (this.showGraphs)  this._drawGraphs();
    if (window.LabFX) LabFX.particles.draw(this.ctx);
  }

  // ── glass vessel ─────────────────────────────────────────────

  _drawCellBody() {
    const { ctx } = this;
    const { cx, cy, cw, ch } = this._cell();
    const r = 10; // corner radius

    ctx.save();

    // Outer glass wall (vessel silhouette)
    ctx.beginPath();
    ctx.moveTo(cx + r, cy);
    ctx.lineTo(cx + cw - r, cy);
    ctx.quadraticCurveTo(cx + cw, cy, cx + cw, cy + r);
    ctx.lineTo(cx + cw, cy + ch - r);
    ctx.quadraticCurveTo(cx + cw, cy + ch, cx + cw - r, cy + ch);
    ctx.lineTo(cx + r, cy + ch);
    ctx.quadraticCurveTo(cx, cy + ch, cx, cy + ch - r);
    ctx.lineTo(cx, cy + r);
    ctx.quadraticCurveTo(cx, cy, cx + r, cy);
    ctx.closePath();

    // glass inner fill
    const glassG = ctx.createLinearGradient(cx, cy, cx + cw, cy);
    glassG.addColorStop(0,   'rgba(160,200,255,0.04)');
    glassG.addColorStop(0.1, 'rgba(255,255,255,0.06)');
    glassG.addColorStop(0.5, 'rgba(130,170,240,0.02)');
    glassG.addColorStop(1,   'rgba(255,255,255,0.05)');
    ctx.fillStyle = glassG;
    ctx.fill();

    // glass border
    ctx.strokeStyle = 'rgba(200,220,255,0.22)';
    ctx.lineWidth = 1.5;
    ctx.stroke();

    // Left highlight streak
    const hlG = ctx.createLinearGradient(cx, cy, cx + 14, cy);
    hlG.addColorStop(0, 'rgba(255,255,255,0.12)');
    hlG.addColorStop(1, 'rgba(255,255,255,0)');
    ctx.fillStyle = hlG;
    ctx.fillRect(cx + 2, cy + 4, 12, ch - 8);

    // Right reflection
    const rrG = ctx.createLinearGradient(cx + cw - 10, cy, cx + cw, cy);
    rrG.addColorStop(0, 'rgba(255,255,255,0)');
    rrG.addColorStop(1, 'rgba(255,255,255,0.07)');
    ctx.fillStyle = rrG;
    ctx.fillRect(cx + cw - 10, cy + 4, 8, ch - 8);

    // Bottom glass thickness line
    ctx.strokeStyle = 'rgba(200,220,255,0.14)';
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(cx + r, cy + ch);
    ctx.lineTo(cx + cw - r, cy + ch);
    ctx.stroke();

    ctx.restore();
  }

  _drawSolution() {
    const { ctx } = this;
    const { cx, cy, cw, ch } = this._cell();
    const [r, g, b] = this._el().solColor;
    const [a0, a1]  = this._el().solAlpha;

    ctx.save();

    // Wave on surface
    const waveH = 4;
    const t     = this._wavePhase;
    ctx.beginPath();
    ctx.moveTo(cx + 2, cy + 2 + waveH);
    for (let px = 0; px <= cw - 4; px += 3) {
      const wave = Math.sin((px / (cw - 4)) * Math.PI * 4 + t) * waveH * 0.5;
      ctx.lineTo(cx + 2 + px, cy + 2 + waveH * 0.5 + wave);
    }
    ctx.lineTo(cx + cw - 2, cy + ch - 2);
    ctx.lineTo(cx + 2, cy + ch - 2);
    ctx.closePath();

    const sg = ctx.createLinearGradient(cx, cy, cx, cy + ch);
    sg.addColorStop(0, `rgba(${r},${g},${b},${a0})`);
    sg.addColorStop(1, `rgba(${r},${g},${b},${a1})`);
    ctx.fillStyle = sg;
    ctx.fill();

    // Subtle wave highlight line at surface
    ctx.beginPath();
    ctx.moveTo(cx + 2, cy + 2 + waveH);
    for (let px = 0; px <= cw - 4; px += 3) {
      const wave = Math.sin((px / (cw - 4)) * Math.PI * 4 + t) * waveH * 0.5;
      ctx.lineTo(cx + 2 + px, cy + 2 + waveH * 0.5 + wave);
    }
    ctx.strokeStyle = `rgba(${r},${g},${b},0.4)`;
    ctx.lineWidth = 1.2;
    ctx.stroke();

    ctx.restore();
  }

  _drawElectrodes() {
    const { ctx } = this;
    const e  = this._electrodes();
    const FN = ElectrolysisSim.FONT;

    // Cathode (−) — dark with cyan tint
    const catG = ctx.createLinearGradient(e.cathode.x, 0, e.cathode.x + e.cathode.w, 0);
    catG.addColorStop(0, '#2a2a3e');
    catG.addColorStop(0.4, '#3c3c54');
    catG.addColorStop(1, '#252534');
    ctx.fillStyle = catG;
    ctx.beginPath(); ctx.roundRect(e.cathode.x, e.cathode.y, e.cathode.w, e.cathode.h, 4); ctx.fill();
    ctx.strokeStyle = 'rgba(6,214,224,0.45)'; ctx.lineWidth = 1.2;
    ctx.beginPath(); ctx.roundRect(e.cathode.x, e.cathode.y, e.cathode.w, e.cathode.h, 4); ctx.stroke();
    // cathode bevel highlight
    ctx.strokeStyle = 'rgba(255,255,255,0.1)'; ctx.lineWidth = 0.8;
    ctx.beginPath(); ctx.moveTo(e.cathode.x + 2, e.cathode.y + 4); ctx.lineTo(e.cathode.x + 2, e.cathode.y + e.cathode.h - 4); ctx.stroke();

    // Anode (+) — dark with red tint
    const anG = ctx.createLinearGradient(e.anode.x, 0, e.anode.x + e.anode.w, 0);
    anG.addColorStop(0, '#2c2530');
    anG.addColorStop(0.6, '#3e3048');
    anG.addColorStop(1, '#28222e');
    ctx.fillStyle = anG;
    ctx.beginPath(); ctx.roundRect(e.anode.x, e.anode.y, e.anode.w, e.anode.h, 4); ctx.fill();
    ctx.strokeStyle = 'rgba(239,71,111,0.45)'; ctx.lineWidth = 1.2;
    ctx.beginPath(); ctx.roundRect(e.anode.x, e.anode.y, e.anode.w, e.anode.h, 4); ctx.stroke();
    ctx.strokeStyle = 'rgba(255,255,255,0.1)'; ctx.lineWidth = 0.8;
    ctx.beginPath(); ctx.moveTo(e.anode.x + 2, e.anode.y + 4); ctx.lineTo(e.anode.x + 2, e.anode.y + e.anode.h - 4); ctx.stroke();

    // Polarity badges
    const bdR = 9;
    const catBx = e.cathode.x + e.cathode.w / 2;
    const catBy = e.cathode.y - 16;
    ctx.fillStyle = 'rgba(6,214,224,0.18)';
    ctx.beginPath(); ctx.arc(catBx, catBy, bdR, 0, Math.PI * 2); ctx.fill();
    ctx.strokeStyle = 'rgba(6,214,224,0.6)'; ctx.lineWidth = 1;
    ctx.beginPath(); ctx.arc(catBx, catBy, bdR, 0, Math.PI * 2); ctx.stroke();
    ctx.fillStyle = '#06D6E0'; ctx.font = `bold 13px ${FN}`;
    ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
    ctx.fillText('−', catBx, catBy + 1);

    const anBx = e.anode.x + e.anode.w / 2;
    const anBy = e.anode.y - 16;
    ctx.fillStyle = 'rgba(239,71,111,0.18)';
    ctx.beginPath(); ctx.arc(anBx, anBy, bdR, 0, Math.PI * 2); ctx.fill();
    ctx.strokeStyle = 'rgba(239,71,111,0.6)'; ctx.lineWidth = 1;
    ctx.beginPath(); ctx.arc(anBx, anBy, bdR, 0, Math.PI * 2); ctx.stroke();
    ctx.fillStyle = '#EF476F';
    ctx.fillText('+', anBx, anBy + 1);
  }

  _drawDeposit() {
    const el = this._el();
    if (!el.depositColor || this._depositH < 0.5) return;
    const { ctx } = this;
    const c  = this._electrodes().cathode;
    const dh = Math.min(this._depositH, c.h * 0.74);
    const FN = ElectrolysisSim.FONT;

    ctx.save();
    const grad = el.depositGrad || ['rgba(180,140,80,0.4)', 'rgba(180,140,80,0.9)'];
    const dg = ctx.createLinearGradient(c.x + c.w, c.y + c.h - dh, c.x + c.w + 12, c.y + c.h);
    dg.addColorStop(0, grad[0]);
    dg.addColorStop(1, grad[1]);
    ctx.fillStyle = dg;
    ctx.beginPath(); ctx.roundRect(c.x + c.w, c.y + c.h - dh, 11, dh, [3, 3, 0, 0]); ctx.fill();

    // Gloss edge
    ctx.shadowColor = el.depositColor; ctx.shadowBlur = 8;
    const topG = ctx.createLinearGradient(c.x + c.w, c.y + c.h - dh, c.x + c.w + 11, c.y + c.h - dh + 4);
    topG.addColorStop(0, 'rgba(255,255,255,0.4)');
    topG.addColorStop(1, 'rgba(255,255,255,0)');
    ctx.fillStyle = topG;
    ctx.beginPath(); ctx.roundRect(c.x + c.w, c.y + c.h - dh, 11, 4, [3, 3, 0, 0]); ctx.fill();
    ctx.shadowBlur = 0;

    // Label
    if (el.depositLabel && dh > 12) {
      ctx.fillStyle = 'rgba(255,255,255,0.75)';
      ctx.font = `bold 8px ${FN}`;
      ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
      ctx.fillText(el.depositLabel, c.x + c.w + 5, c.y + c.h - dh / 2);
    }
    ctx.restore();
  }

  _drawIons() {
    const { ctx } = this;
    const FN = ElectrolysisSim.FONT;
    ctx.save();
    for (const ion of this._ions) {
      // Trail
      if (ion.trail.length >= 2) {
        for (let t = 0; t < ion.trail.length; t++) {
          const alpha = (t / ion.trail.length) * 0.35;
          const tr    = (t / ion.trail.length) * 3;
          ctx.globalAlpha = alpha;
          ctx.fillStyle   = ion.color;
          ctx.beginPath(); ctx.arc(ion.trail[t].x, ion.trail[t].y, tr, 0, Math.PI * 2); ctx.fill();
        }
        ctx.globalAlpha = 1;
      }

      // Glow halo
      const g = ctx.createRadialGradient(ion.x, ion.y, 0, ion.x, ion.y, 13);
      g.addColorStop(0, ion.color + '28'); g.addColorStop(1, ion.color + '00');
      ctx.fillStyle = g;
      ctx.beginPath(); ctx.arc(ion.x, ion.y, 13, 0, Math.PI * 2); ctx.fill();

      // Badge background
      ctx.fillStyle = ion.color + 'cc';
      ctx.beginPath(); ctx.arc(ion.x, ion.y, ion.r, 0, Math.PI * 2); ctx.fill();

      // Badge border
      ctx.strokeStyle = 'rgba(255,255,255,0.3)'; ctx.lineWidth = 0.7;
      ctx.beginPath(); ctx.arc(ion.x, ion.y, ion.r, 0, Math.PI * 2); ctx.stroke();

      // Label
      ctx.fillStyle = 'rgba(255,255,255,0.92)';
      ctx.font = `bold 7px ${FN}`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
      ctx.fillText(ion.label, ion.x, ion.y);
    }
    ctx.restore();
  }

  _drawBubbles() {
    const { ctx } = this;
    ctx.save();
    for (const b of this._bubbles) {
      ctx.globalAlpha = b.life * 0.7;
      // bubble body
      const bg = ctx.createRadialGradient(b.x - b.r * 0.3, b.y - b.r * 0.3, 0, b.x, b.y, b.r);
      bg.addColorStop(0, 'rgba(255,255,255,0.22)');
      bg.addColorStop(0.6, 'rgba(255,255,255,0)');
      ctx.fillStyle = bg;
      ctx.beginPath(); ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2); ctx.fill();
      // outline
      ctx.strokeStyle = b.color; ctx.lineWidth = 0.9;
      ctx.beginPath(); ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2); ctx.stroke();
      // specular dot
      ctx.globalAlpha = b.life * 0.5;
      ctx.fillStyle = 'rgba(255,255,255,0.7)';
      ctx.beginPath(); ctx.arc(b.x - b.r * 0.32, b.y - b.r * 0.32, b.r * 0.28, 0, Math.PI * 2); ctx.fill();
    }
    ctx.globalAlpha = 1;
    ctx.restore();
  }

  _drawWiresAndBattery() {
    const { ctx } = this;
    const { cx, cy, cw } = this._cell();
    const e  = this._electrodes();
    const FN = ElectrolysisSim.FONT;

    const cXt = e.cathode.x + e.cathode.w / 2;
    const aXt = e.anode.x   + e.anode.w   / 2;
    const bx  = cx + cw / 2;
    const by  = cy - Math.max(46, this.H * 0.10);

    ctx.save();

    // Wires
    ctx.strokeStyle = 'rgba(255,255,255,0.18)'; ctx.lineWidth = 2;
    ctx.lineCap = 'round'; ctx.lineJoin = 'round';
    // Cathode wire
    ctx.beginPath();
    ctx.moveTo(cXt, e.cathode.y); ctx.lineTo(cXt, by); ctx.lineTo(bx - 26, by);
    ctx.stroke();
    // Anode wire
    ctx.beginPath();
    ctx.moveTo(aXt, e.anode.y); ctx.lineTo(aXt, by); ctx.lineTo(bx + 26, by);
    ctx.stroke();

    // Animated electrons: cathode side — from battery (−) toward cathode
    const dist = (bx - 26) - cXt;
    const I    = this._current();
    const dotCount = Math.max(3, Math.min(6, Math.round(I * 4)));
    for (let i = 0; i < dotCount; i++) {
      const t  = ((this._electronPhase + i / dotCount) % 1);
      // electrons flow: battery − (right side of left wire) → down to cathode
      let ex, ey;
      const totalPath = Math.abs(dist) + Math.abs(e.cathode.y - by);
      const seg1frac  = Math.abs(dist) / totalPath;
      if (t < seg1frac) {
        // horizontal segment: bx-26 → cXt
        const st = t / seg1frac;
        ex = (bx - 26) - st * Math.abs(dist);
        ey = by;
      } else {
        // vertical segment: by → e.cathode.y
        const st = (t - seg1frac) / (1 - seg1frac);
        ex = cXt;
        ey = by + st * (e.cathode.y - by);
      }
      ctx.fillStyle = '#4CC9F0';
      ctx.shadowColor = '#4CC9F0'; ctx.shadowBlur = 6;
      ctx.beginPath(); ctx.arc(ex, ey, 3, 0, Math.PI * 2); ctx.fill();
      ctx.shadowBlur = 0;
    }

    // Battery body
    const bw = 44, bh = 28;
    const bbg = ctx.createLinearGradient(bx - bw / 2, by - bh / 2, bx + bw / 2, by + bh / 2);
    bbg.addColorStop(0, 'rgba(50,50,80,0.9)');
    bbg.addColorStop(1, 'rgba(30,30,55,0.95)');
    ctx.fillStyle = bbg;
    ctx.beginPath(); ctx.roundRect(bx - bw / 2, by - bh / 2, bw, bh, 5); ctx.fill();
    ctx.strokeStyle = 'rgba(255,255,255,0.15)'; ctx.lineWidth = 1;
    ctx.beginPath(); ctx.roundRect(bx - bw / 2, by - bh / 2, bw, bh, 5); ctx.stroke();

    // Battery plates inside
    // Negative plate (−, left)
    ctx.strokeStyle = '#06D6E0'; ctx.lineWidth = 2.5;
    ctx.beginPath(); ctx.moveTo(bx - 14, by - 9); ctx.lineTo(bx - 14, by + 9); ctx.stroke();
    // Positive plate (+, right)
    ctx.strokeStyle = '#EF476F'; ctx.lineWidth = 4;
    ctx.beginPath(); ctx.moveTo(bx + 14, by - 13); ctx.lineTo(bx + 14, by + 13); ctx.stroke();

    // Voltage label above battery
    ctx.fillStyle = '#FFD166';
    ctx.font = `bold 13px ${FN}`; ctx.textAlign = 'center'; ctx.textBaseline = 'bottom';
    ctx.shadowColor = '#FFD166'; ctx.shadowBlur = 4;
    ctx.fillText(this.voltage.toFixed(1) + ' В', bx, by - bh / 2 - 5);
    ctx.shadowBlur = 0;

    // +/− labels outside battery
    ctx.font = `bold 11px ${FN}`; ctx.textBaseline = 'middle';
    ctx.fillStyle = '#EF476F'; ctx.textAlign = 'left';
    ctx.fillText('+', bx + bw / 2 + 4, by);
    ctx.fillStyle = '#06D6E0'; ctx.textAlign = 'right';
    ctx.fillText('−', bx - bw / 2 - 4, by);

    ctx.restore();
  }

  _drawLabels() {
    const { ctx } = this;
    const el  = this._el(), e = this._electrodes();
    const { cx, cy, cw, ch } = this._cell();
    const FN  = ElectrolysisSim.FONT;
    const bot = cy + ch + 7;

    ctx.save();
    ctx.textAlign = 'center';

    // Electrode labels
    ctx.font = `bold 11px ${FN}`; ctx.textBaseline = 'top';
    ctx.fillStyle = '#06D6E0';
    ctx.fillText('Катод (−)', e.cathode.x + e.cathode.w / 2, bot);
    ctx.fillStyle = '#EF476F';
    ctx.fillText('Анод (+)', e.anode.x + e.anode.w / 2, bot);

    // Products
    ctx.font = `10px ${FN}`; ctx.fillStyle = 'rgba(255,255,255,0.52)';
    ctx.fillText(el.cathodeProduct, e.cathode.x + e.cathode.w / 2, bot + 16);
    ctx.fillText(el.anodeProduct,   e.anode.x   + e.anode.w   / 2, bot + 16);

    // Electrolyte name
    ctx.font = `bold 12px ${FN}`; ctx.fillStyle = '#9B5DE5';
    ctx.fillText(el.displayName, cx + cw / 2, bot + 32);

    // Equations
    ctx.font = `9px ${FN}`;
    ctx.fillStyle = 'rgba(6,214,224,0.6)';
    ctx.fillText(el.cathodeEq, e.cathode.x + e.cathode.w / 2, bot + 48);
    ctx.fillStyle = 'rgba(239,71,111,0.6)';
    ctx.fillText(el.anodeEq,   e.anode.x   + e.anode.w   / 2, bot + 48);

    ctx.restore();
  }

  _drawFaradayPanel() {
    const { ctx, W } = this;
    const el  = this._el();
    const I   = this._current();
    const inf = this.info();
    const FN  = ElectrolysisSim.FONT;

    const pw = Math.min(186, W * 0.28);
    const px = 10, py = 10;
    const lh = 16;

    // Rows: U, I, t, Q, charge count, mass/gas
    const rows = [
      ['U', this.voltage.toFixed(1) + ' В',    'rgba(255,209,102,0.9)'],
      ['I', I.toFixed(3) + ' А',               this.playing ? '#ff8a8a' : 'rgba(255,255,255,0.85)'],
      ['Т', this._fmtTime(inf.time),            this.playing ? '#ff8a8a' : 'rgba(255,255,255,0.85)'],
      ['Q', inf.chargeTotal.toFixed(1) + ' Кл', '#9B5DE5'],
    ];
    if (el.depositColor) {
      rows.push(['m(' + (el.depositLabel || '?') + ')',
        inf.massDeposited.toFixed(4) + ' г', '#c47a30']);
    }
    rows.push(['V(газ)', inf.gasVolume.toFixed(2) + ' мл', '#06D6E0']);

    const ph = 14 + rows.length * lh + 26;
    ctx.save();
    ctx.fillStyle = 'rgba(5,5,22,0.88)';
    ctx.beginPath(); ctx.roundRect(px, py, pw, ph, 9); ctx.fill();
    ctx.strokeStyle = 'rgba(255,255,255,0.08)'; ctx.lineWidth = 1;
    ctx.beginPath(); ctx.roundRect(px, py, pw, ph, 9); ctx.stroke();

    ctx.font = `10px ${FN}`; ctx.textBaseline = 'middle';
    rows.forEach(([k, v, clr], i) => {
      const ry = py + 12 + i * lh + lh / 2;
      ctx.fillStyle = 'rgba(255,255,255,0.40)'; ctx.textAlign = 'left';
      ctx.fillText(k, px + 10, ry);
      ctx.fillStyle = clr || 'rgba(255,255,255,0.88)'; ctx.textAlign = 'right';
      ctx.fillText(v, px + pw - 10, ry);
    });

    // Faraday formula line
    const fy = py + ph - 18;
    ctx.fillStyle = 'rgba(255,255,255,0.18)';
    ctx.font = `italic 8px ${FN}`; ctx.textAlign = 'left';
    ctx.fillText('m = M·I·t / (n·F)', px + 10, fy);

    ctx.restore();
  }

  _drawGraphs() {
    const { ctx, W, H } = this;
    if (this._graphTime.length < 2) return;
    const FN = ElectrolysisSim.FONT;

    const gw = Math.min(W * 0.38, 200);
    const gh = 75;
    const gx = W - gw - 10;
    const gy = H - gh - 10;

    ctx.save();
    ctx.fillStyle = 'rgba(5,5,22,0.88)';
    ctx.beginPath(); ctx.roundRect(gx, gy, gw, gh, 8); ctx.fill();
    ctx.strokeStyle = 'rgba(255,255,255,0.08)'; ctx.lineWidth = 1;
    ctx.beginPath(); ctx.roundRect(gx, gy, gw, gh, 8); ctx.stroke();

    const pad = { l: 8, r: 8, t: 14, b: 10 };
    const iw  = gw - pad.l - pad.r;
    const ih  = gh - pad.t - pad.b;

    const massMax = Math.max(...this._graphMass, 0.0001);
    const gasMax  = Math.max(...this._graphGas,  0.0001);
    const n       = this._graphTime.length;

    const drawLine = (data, maxVal, color) => {
      ctx.beginPath();
      for (let i = 0; i < n; i++) {
        const x = gx + pad.l + (i / (n - 1)) * iw;
        const y = gy + pad.t + ih - (data[i] / maxVal) * ih;
        i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
      }
      ctx.strokeStyle = color; ctx.lineWidth = 1.5;
      ctx.stroke();
    };

    const el = this._el();
    if (el.depositColor) {
      drawLine(this._graphMass, massMax, '#9B5DE5');
    }
    drawLine(this._graphGas, gasMax, '#06D6E0');

    // Legend
    ctx.font = `8px ${FN}`; ctx.textBaseline = 'top';
    if (el.depositColor) {
      ctx.fillStyle = '#9B5DE5'; ctx.textAlign = 'left';
      ctx.fillText('m(г)', gx + pad.l, gy + 3);
    }
    ctx.fillStyle = '#06D6E0'; ctx.textAlign = 'right';
    ctx.fillText('V(мл)', gx + gw - pad.r, gy + 3);

    ctx.restore();
  }

  _fmtTime(s) {
    if (s < 60) return s.toFixed(1) + ' с';
    return Math.floor(s / 60) + ' мин ' + (s % 60).toFixed(0) + ' с';
  }
}

if (typeof module !== 'undefined') module.exports = ElectrolysisSim;

/* ─── lab UI init ─────────────────────────────────────────────── */

function _openElectrolysis() {
  document.getElementById('sim-topbar-title').textContent = 'Электролиз';
  _simShow('sim-electrolysis');
  _registerSimState('electrolysis', () => elecSim?.getParams(), st => elecSim?.setParams(st));
  if (_embedMode) _startStateEmit('electrolysis');
  requestAnimationFrame(() => requestAnimationFrame(() => {
    if (!elecSim) {
      elecSim = new ElectrolysisSim(document.getElementById('electrolysis-canvas'));
      elecSim.onUpdate = _elecUpdateUI;
    }
    elecSim.fit();
    elecSim.reset();
    elecSim.play();
    // sync display toggle checkboxes
    _elecSyncToggles();
  }));
}

function elecParam(name, val) {
  const v = parseFloat(val);
  if (name === 'voltage') {
    const vEl = document.getElementById('elec-V-val');
    if (vEl) vEl.textContent = v.toFixed(1);
    if (window.LabFX) LabFX.sound.play('spark', { volume: 0.3 });
  }
  if (elecSim) elecSim.setParams({ [name]: v });
}

function elecPreset(name, btn) {
  document.querySelectorAll('.elec-type-btn').forEach(b => b.classList.remove('active'));
  if (btn) btn.classList.add('active');
  const voltages = { nacl: 6, cuso4: 4, h2so4: 3, ki: 5, znso4: 5, agno3: 3 };
  const vt = voltages[name] || 6;
  const sl = document.getElementById('sl-elec-V');
  if (sl) sl.value = vt;
  const vl = document.getElementById('elec-V-val');
  if (vl) vl.textContent = vt.toFixed(1);
  if (elecSim) {
    elecSim.setParams({ electrolyte: name, voltage: vt });
    elecSim.reset();
    elecSim.play();
  }
  _elecUpdateEquations();
}

function elecToggle(name, checked) {
  if (!elecSim) return;
  const map = {
    electrons: 'showElectrons',
    ions:      'showIons',
    bubbles:   'showBubbles',
    graphs:    'showGraphs',
  };
  if (map[name]) elecSim[map[name]] = checked;
}

function elecSpeed(val, btn) {
  document.querySelectorAll('.elec-speed-btn').forEach(b => b.classList.remove('active'));
  if (btn) btn.classList.add('active');
  if (elecSim) elecSim.speed = +val;
}

function _elecSyncToggles() {
  if (!elecSim) return;
  const pairs = [
    ['elec-chk-electrons', 'showElectrons'],
    ['elec-chk-ions',      'showIons'],
    ['elec-chk-bubbles',   'showBubbles'],
    ['elec-chk-graphs',    'showGraphs'],
  ];
  pairs.forEach(([id, prop]) => {
    const el = document.getElementById(id);
    if (el) el.checked = elecSim[prop];
  });
  _elecUpdateEquations();
}

function _elecUpdateEquations() {
  const key = elecSim ? elecSim.electrolyte : 'NaCl';
  const el  = ElectrolysisSim.ELECTROLYTES[key];
  if (!el) return;
  const catEl = document.getElementById('elec-eq-cathode');
  const anEl  = document.getElementById('elec-eq-anode');
  if (catEl) catEl.textContent = el.cathodeEq;
  if (anEl)  anEl.textContent  = el.anodeEq;
}

function _elecUpdateUI(info) {
  const v = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
  v('elecbar-v1', typeof info.current       === 'number' ? info.current.toFixed(2) + ' A'       : '—');
  v('elecbar-v2', typeof info.massDeposited === 'number' ? info.massDeposited.toFixed(3) + ' г' : '—');
  v('elecbar-v3', typeof info.gasVolume     === 'number' ? info.gasVolume.toFixed(1) + ' мл' : '—');
  v('elecbar-v4', typeof info.time          === 'number' ? info.time.toFixed(0) + ' с'       : '—');
  v('elecbar-v5', typeof info.chargeTotal   === 'number' ? info.chargeTotal.toFixed(1) + ' Кл' : '—');
  v('elecbar-v6', typeof info.electronCount === 'string' ? info.electronCount + ' шт'   : '—');
}
