'use strict';
/**
 * NormalDistSim v2 — интерактивное нормальное распределение
 * μ, σ · правило 68-95-99.7 · Z-score · закрашивание области
 * Чистый рерайт: без SVG-строк в info(), лучшая визуализация.
 */
class NormalDistSim {
  constructor(canvas) {
    this.canvas = canvas;
    this.ctx    = canvas.getContext('2d');
    this.W = 0; this.H = 0;

    this.mu    = 0;
    this.sigma = 1;
    this.shade = '1s';   // 'none' | '1s' | '2s' | '3s' | 'custom'
    this.zLow  = -1;
    this.zHigh =  1;
    this.hx    = null;

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

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

  fit() {
    const dpr = window.devicePixelRatio || 1;
    const w = this.canvas.offsetWidth  || 600;
    const h = this.canvas.offsetHeight || 400;
    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;
  }

  getParams() { return { mu: this.mu, sigma: this.sigma, shade: this.shade, zLow: this.zLow, zHigh: this.zHigh }; }
  setParams({ mu, sigma, shade, zLow, zHigh } = {}) {
    if (mu    !== undefined) this.mu    = +mu;
    if (sigma !== undefined) this.sigma = Math.max(0.1, +sigma);
    if (shade !== undefined) this.shade = shade;
    if (zLow  !== undefined) this.zLow  = +zLow;
    if (zHigh !== undefined) this.zHigh = +zHigh;
    this.draw(); this._emit();
  }

  info() {
    const { mu, sigma, shade } = this;
    let areaLabel = '\u2014', areaPct = 0;
    if      (shade === '1s')     { areaPct = 68.27; areaLabel = '\u03bc \u00b1 1\u03c3  \u2192  68.27%'; }
    else if (shade === '2s')     { areaPct = 95.45; areaLabel = '\u03bc \u00b1 2\u03c3  \u2192  95.45%'; }
    else if (shade === '3s')     { areaPct = 99.73; areaLabel = '\u03bc \u00b1 3\u03c3  \u2192  99.73%'; }
    else if (shade === 'custom') {
      areaPct = (this._phi(this.zHigh) - this._phi(this.zLow)) * 100;
      areaLabel = `Z \u2208 [${this.zLow.toFixed(1)}, ${this.zHigh.toFixed(1)}]  \u2192  ${areaPct.toFixed(2)}%`;
    }
    return {
      mu:       mu.toFixed(1),
      sigma:    sigma.toFixed(2),
      peak:     (1 / (sigma * Math.sqrt(2 * Math.PI))).toFixed(4),
      area:     areaLabel,
      areaPct:  areaPct.toFixed(2),
    };
  }

  // ── math ─────────────────────────────────────────────────────

  _pdf(x) {
    const z = (x - this.mu) / this.sigma;
    return Math.exp(-0.5 * z * z) / (this.sigma * Math.sqrt(2 * Math.PI));
  }

  _phi(z) {
    const a1=0.254829592, a2=-0.284496736, a3=1.421413741, a4=-1.453152027, a5=1.061405429, p=0.3275911;
    const sign = z < 0 ? -1 : 1;
    const t = 1 / (1 + p * Math.abs(z) / Math.SQRT2);
    const y = 1 - (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t * Math.exp(-z*z/2);
    return 0.5 * (1 + sign * y);
  }

  _emit() { if (this.onUpdate) this.onUpdate(this.info()); }

  // ── coordinate transforms ─────────────────────────────────────

  _pad()  { return { PL: 52, PR: 22, PT: 38, PB: 50 }; }

  _xToP(x, xMin, xMax, PL, pw) { return PL + (x - xMin) / (xMax - xMin) * pw; }
  _yToP(y, yMax, PT, ph)       { return PT + ph - (y / yMax) * ph; }
  _pToX(px, xMin, xMax, PL, pw){ return xMin + (px - PL) / pw * (xMax - xMin); }

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

  draw() {
    const { ctx, W, H, mu, sigma } = this;
    if (!W || !H) return;
    const { PL, PR, PT, PB } = this._pad();
    const pw = W - PL - PR, ph = H - PT - PB;
    const xMin = mu - 4.5 * sigma, xMax = mu + 4.5 * sigma;
    const yMax = this._pdf(mu) * 1.18;

    ctx.fillStyle = '#0D0D1A'; ctx.fillRect(0, 0, W, H);

    this._drawGrid   (PL, PT, pw, ph, xMin, xMax, yMax);
    this._drawShade  (PL, PT, pw, ph, xMin, xMax, yMax);
    this._drawCurve  (PL, PT, pw, ph, xMin, xMax, yMax);
    this._drawLabels (PL, PT, pw, ph, xMin, xMax, yMax);
    this._drawBadge  (PL, PT, pw, ph);
    if (this.hx !== null) this._drawHover(PL, PT, pw, ph, xMin, xMax, yMax);
  }

  _drawGrid(PL, PT, pw, ph, xMin, xMax, yMax) {
    const { ctx, mu, sigma } = this;
    const bottom = PT + ph;
    const FN = 'Manrope, sans-serif';

    // Horizontal grid
    ctx.strokeStyle = 'rgba(255,255,255,0.05)'; ctx.lineWidth = 1;
    for (let i = 1; i <= 4; i++) {
      const py = PT + ph * (1 - i / 4);
      ctx.beginPath(); ctx.moveTo(PL, py); ctx.lineTo(PL + pw, py); ctx.stroke();
    }

    // Vertical sigma grid lines
    for (let s = -4; s <= 4; s++) {
      const x = mu + s * sigma;
      if (x < xMin || x > xMax) continue;
      const px = this._xToP(x, xMin, xMax, PL, pw);
      ctx.strokeStyle = s === 0
        ? 'rgba(6,214,224,0.22)'
        : `rgba(255,255,255,${0.04 + (Math.abs(s) <= 2 ? 0.03 : 0)})`;
      ctx.lineWidth = 1;
      ctx.beginPath(); ctx.moveTo(px, PT); ctx.lineTo(px, bottom); ctx.stroke();
    }

    // Axes
    ctx.strokeStyle = 'rgba(255,255,255,0.3)'; ctx.lineWidth = 1.5;
    ctx.beginPath(); ctx.moveTo(PL, bottom); ctx.lineTo(PL + pw, bottom); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(PL, PT);     ctx.lineTo(PL, bottom);      ctx.stroke();

    // X-axis labels (sigma notation)
    ctx.font = `11px ${FN}`; ctx.textAlign = 'center'; ctx.textBaseline = 'top';
    ctx.fillStyle = 'rgba(255,255,255,0.3)';
    for (let s = -4; s <= 4; s++) {
      const x = mu + s * sigma;
      if (x < xMin || x > xMax) continue;
      const px = this._xToP(x, xMin, xMax, PL, pw);
      const lbl = s === 0 ? '\u03bc' : (s > 0 ? `+${s}\u03c3` : `${s}\u03c3`);
      ctx.fillText(lbl, px, bottom + 6);
    }

    // Actual x values below
    ctx.font = `9px ${FN}`; ctx.fillStyle = 'rgba(255,255,255,0.18)';
    for (let s = -3; s <= 3; s++) {
      const x = mu + s * sigma;
      if (x < xMin || x > xMax) continue;
      const px = this._xToP(x, xMin, xMax, PL, pw);
      const dec = sigma < 1 ? 1 : 0;
      ctx.fillText(x.toFixed(dec), px, bottom + 20);
    }

    // Y-axis labels
    ctx.textAlign = 'right'; ctx.textBaseline = 'middle'; ctx.fillStyle = 'rgba(255,255,255,0.25)';
    ctx.font = `10px ${FN}`;
    for (let i = 0; i <= 4; i++) {
      const v = (yMax / 4) * i;
      const py = PT + ph - (v / yMax) * ph;
      ctx.fillText(v.toFixed(2), PL - 6, py);
    }

    // Axis names
    ctx.fillStyle = 'rgba(255,255,255,0.2)'; ctx.font = `10px ${FN}`;
    ctx.textAlign = 'center'; ctx.textBaseline = 'top';
    ctx.fillText('x', PL + pw / 2, PT + ph + 36);
    ctx.textAlign = 'left'; ctx.textBaseline = 'top';
    ctx.fillText('f(x)', PL + 6, PT);
  }

  _drawShade(PL, PT, pw, ph, xMin, xMax, yMax) {
    const { ctx, mu, sigma, shade } = this;
    let lo, hi;
    if      (shade === '1s')     { lo = mu - sigma;             hi = mu + sigma; }
    else if (shade === '2s')     { lo = mu - 2 * sigma;         hi = mu + 2 * sigma; }
    else if (shade === '3s')     { lo = mu - 3 * sigma;         hi = mu + 3 * sigma; }
    else if (shade === 'custom') { lo = mu + this.zLow * sigma; hi = mu + this.zHigh * sigma; }
    else return;

    const bottom = PT + ph;
    const steps = 240;
    const dx = (hi - lo) / steps;
    const xp = x => this._xToP(x, xMin, xMax, PL, pw);
    const yp = y => this._yToP(y, yMax, PT, ph);

    // Filled area with gradient (pulsing alpha when LabFX available)
    const _pulseA = window.LabFX ? (0.10 + LabFX.glow.pulse(performance.now(), 3000) * 0.12) : 0.10;
    const _pulseB = window.LabFX ? (0.22 + LabFX.glow.pulse(performance.now(), 3000) * 0.16) : 0.30;
    const grd = ctx.createLinearGradient(xp(lo), 0, xp(hi), 0);
    grd.addColorStop(0,   `rgba(155,93,229,${_pulseA.toFixed(3)})`);
    grd.addColorStop(0.5, `rgba(155,93,229,${_pulseB.toFixed(3)})`);
    grd.addColorStop(1,   `rgba(155,93,229,${_pulseA.toFixed(3)})`);
    ctx.fillStyle = grd;
    ctx.beginPath();
    ctx.moveTo(xp(lo), bottom);
    for (let i = 0; i <= steps; i++) {
      const x = lo + i * dx;
      ctx.lineTo(xp(x), yp(this._pdf(x)));
    }
    ctx.lineTo(xp(hi), bottom);
    ctx.closePath(); ctx.fill();

    // Border dashes
    ctx.strokeStyle = 'rgba(155,93,229,0.55)'; ctx.lineWidth = 1; ctx.setLineDash([4, 4]);
    for (const bx of [lo, hi]) {
      const px = xp(bx);
      ctx.beginPath(); ctx.moveTo(px, PT); ctx.lineTo(px, bottom); ctx.stroke();
    }
    ctx.setLineDash([]);
  }

  _drawCurve(PL, PT, pw, ph, xMin, xMax, yMax) {
    const { ctx } = this;
    const steps = Math.min(pw * 2, 500);
    const dx = (xMax - xMin) / steps;
    const xp = x => this._xToP(x, xMin, xMax, PL, pw);
    const yp = y => this._yToP(y, yMax, PT, ph);

    const drawBell = () => {
      // Glow layer
      ctx.strokeStyle = 'rgba(155,93,229,0.1)'; ctx.lineWidth = 10; ctx.lineJoin = 'round';
      ctx.beginPath();
      for (let i = 0; i <= steps; i++) {
        const x = xMin + i * dx;
        i === 0 ? ctx.moveTo(xp(x), yp(this._pdf(x))) : ctx.lineTo(xp(x), yp(this._pdf(x)));
      }
      ctx.stroke();

      // Main curve
      ctx.strokeStyle = '#9B5DE5'; ctx.lineWidth = 2.5; ctx.lineJoin = 'round';
      ctx.beginPath();
      for (let i = 0; i <= steps; i++) {
        const x = xMin + i * dx;
        i === 0 ? ctx.moveTo(xp(x), yp(this._pdf(x))) : ctx.lineTo(xp(x), yp(this._pdf(x)));
      }
      ctx.stroke();
    };

    if (window.LabFX) {
      LabFX.glow.drawGlow(ctx, drawBell, { color: '#9B5DE5', intensity: 4 });
    } else {
      drawBell();
    }

    // μ marker
    const muPx = xp(this.mu);
    const bottom = PT + ph;
    ctx.strokeStyle = '#06D6E0'; ctx.lineWidth = 1.5; ctx.setLineDash([6, 4]);
    ctx.beginPath(); ctx.moveTo(muPx, PT); ctx.lineTo(muPx, bottom); ctx.stroke();
    ctx.setLineDash([]);

    ctx.fillStyle = '#06D6E0';
    ctx.font = 'bold 11px Manrope, sans-serif';
    ctx.textAlign = 'center'; ctx.textBaseline = 'bottom';
    ctx.fillText(`\u03bc = ${this.mu.toFixed(1)}`, muPx, PT - 4);

    // Peak label
    const peakPx = xp(this.mu);
    const peakPy = yp(this._pdf(this.mu));
    ctx.fillStyle = 'rgba(155,93,229,0.5)';
    ctx.font = '9px Manrope, sans-serif';
    ctx.textAlign = 'left'; ctx.textBaseline = 'bottom';
    const peakVal = (1 / (this.sigma * Math.sqrt(2 * Math.PI))).toFixed(3);
    ctx.fillText('f(μ) = ' + peakVal, peakPx + 6, peakPy - 2);
  }

  _drawLabels(PL, PT, pw, ph, xMin, xMax, yMax) {
    // sigma annotation brackets
    const { ctx, mu, sigma, shade } = this;
    if (shade === 'none') return;
    const nSig = shade === '1s' ? 1 : shade === '2s' ? 2 : shade === '3s' ? 3 : null;
    if (!nSig) return;

    const bottom = PT + ph;
    const FN = 'Manrope, sans-serif';
    const xp = x => this._xToP(x, xMin, xMax, PL, pw);
    const yp = y => this._yToP(y, yMax, PT, ph);

    // Annotate ±nσ points with small bracket
    const lo = mu - nSig * sigma, hi = mu + nSig * sigma;
    const loPx = xp(lo), hiPx = xp(hi);
    const midY = bottom + 32;

    ctx.save();
    ctx.strokeStyle = 'rgba(155,93,229,0.38)'; ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(loPx, bottom + 4); ctx.lineTo(loPx, midY);
    ctx.lineTo(hiPx, midY);       ctx.lineTo(hiPx, bottom + 4);
    ctx.stroke();

    ctx.fillStyle = 'rgba(155,93,229,0.55)';
    ctx.font = `10px ${FN}`; ctx.textAlign = 'center'; ctx.textBaseline = 'top';
    ctx.fillText('\u00b1' + nSig + '\u03c3', (loPx + hiPx) / 2, midY + 2);
    ctx.restore();
  }

  _drawBadge(PL, PT, pw, ph) {
    const { ctx, shade } = this;
    if (shade === 'none') return;
    const info = this.info();
    const pct = parseFloat(info.areaPct);
    if (!pct) return;

    const FN = 'Manrope, sans-serif';
    ctx.save();
    ctx.font = `bold 15px ${FN}`;
    const text = pct.toFixed(2) + '%';
    const tw = ctx.measureText(text).width;
    const bw = tw + 24, bh = 28;
    const bx = PL + pw - bw - 4, by = PT + 4;

    ctx.fillStyle = 'rgba(155,93,229,0.16)';
    ctx.beginPath(); ctx.roundRect(bx, by, bw, bh, 6); ctx.fill();
    ctx.strokeStyle = 'rgba(155,93,229,0.38)'; ctx.lineWidth = 1;
    ctx.beginPath(); ctx.roundRect(bx, by, bw, bh, 6); ctx.stroke();
    ctx.fillStyle = '#9B5DE5'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
    ctx.fillText(text, bx + bw / 2, by + bh / 2);

    const shadeNames = { '1s': '\u03bc \u00b1 1\u03c3', '2s': '\u03bc \u00b1 2\u03c3', '3s': '\u03bc \u00b1 3\u03c3', custom: 'произвольный Z' };
    ctx.font = `9px ${FN}`; ctx.fillStyle = 'rgba(155,93,229,0.55)';
    ctx.fillText(shadeNames[shade] || '', bx + bw / 2, by + bh + 10);
    ctx.restore();
  }

  _drawHover(PL, PT, pw, ph, xMin, xMax, yMax) {
    const { ctx, W } = this;
    const x = this.hx;
    if (x < xMin || x > xMax) return;
    const px = this._xToP(x, xMin, xMax, PL, pw);
    const y  = this._pdf(x);
    const py = this._yToP(y, yMax, PT, ph);
    const bottom = PT + ph;
    const FN = 'Manrope, sans-serif';

    // Vertical crosshair
    ctx.strokeStyle = 'rgba(255,255,255,0.12)'; ctx.lineWidth = 1; ctx.setLineDash([5, 5]);
    ctx.beginPath(); ctx.moveTo(px, PT); ctx.lineTo(px, bottom); ctx.stroke();
    ctx.setLineDash([]);

    // Point on curve
    ctx.fillStyle = '#FFD166'; ctx.shadowColor = '#FFD166'; ctx.shadowBlur = 8;
    ctx.beginPath(); ctx.arc(px, py, 5, 0, Math.PI * 2); ctx.fill();
    ctx.shadowBlur = 0;
    ctx.strokeStyle = 'rgba(255,255,255,0.6)'; ctx.lineWidth = 1.5;
    ctx.beginPath(); ctx.arc(px, py, 5, 0, Math.PI * 2); ctx.stroke();

    // Tooltip
    const z = (x - this.mu) / this.sigma;
    const rows = [
      ['x',    x.toFixed(3)],
      ['z',    z.toFixed(3)],
      ['f(x)', y.toFixed(5)],
      ['\u03a6(z)', (this._phi(z) * 100).toFixed(2) + '%'],
    ];
    ctx.font = `11px ${FN}`;
    const maxKW = Math.max(...rows.map(([k]) => ctx.measureText(k).width));
    const maxVW = Math.max(...rows.map(([, v]) => ctx.measureText(v).width));
    const tw = maxKW + maxVW + 26, th = rows.length * 18 + 14;
    let tx = px + 14, ty = py - th / 2;
    if (tx + tw > W - 8)   tx = px - tw - 14;
    if (ty < PT + 4)       ty = PT + 4;
    if (ty + th > bottom)  ty = bottom - th;

    ctx.fillStyle = 'rgba(10,10,28,0.95)';
    ctx.beginPath(); ctx.roundRect(tx, ty, tw, th, 8); ctx.fill();
    ctx.strokeStyle = 'rgba(255,255,255,0.08)'; ctx.lineWidth = 1;
    ctx.beginPath(); ctx.roundRect(tx, ty, tw, th, 8); ctx.stroke();

    ctx.textBaseline = 'middle';
    rows.forEach(([k, v], i) => {
      const ry = ty + 7 + i * 18 + 9;
      ctx.fillStyle = 'rgba(255,255,255,0.38)'; ctx.textAlign = 'left';  ctx.fillText(k, tx + 10, ry);
      ctx.fillStyle = '#FFD166';               ctx.textAlign = 'right'; ctx.fillText(v, tx + tw - 10, ry);
    });
  }

  // ── events ────────────────────────────────────────────────────

  _bind() {
    const cv = this.canvas;
    const getHx = e => {
      const r = cv.getBoundingClientRect();
      const { PL, PR } = this._pad();
      const pw = this.W - PL - PR;
      const xMin = this.mu - 4.5 * this.sigma;
      const xMax = this.mu + 4.5 * this.sigma;
      return this._pToX(e.clientX - r.left, xMin, xMax, PL, pw);
    };
    cv.addEventListener('mousemove', e => { this.hx = getHx(e); this.draw(); });
    cv.addEventListener('mouseleave', () => { this.hx = null; this.draw(); });
    cv.addEventListener('touchmove', e => {
      e.preventDefault();
      if (e.touches.length === 1) { this.hx = getHx(e.touches[0]); this.draw(); }
    }, { passive: false });
    cv.addEventListener('touchend', () => { this.hx = null; this.draw(); });
  }
}

/* ─── lab UI init ─────────────────────────────────── */
  var ndSim = null;

  let _ndPulseRaf = null;
  function _openNormalDist() {
    document.getElementById('sim-topbar-title').textContent = 'Нормальное распределение';
    _simShow('sim-normaldist');
    _registerSimState('normaldist', () => ndSim?.getParams(), st => ndSim?.setParams(st));
    if (_embedMode) _startStateEmit('normaldist');
    requestAnimationFrame(() => requestAnimationFrame(() => {
      if (!ndSim) {
        ndSim = new NormalDistSim(document.getElementById('normaldist-canvas'));
        ndSim.onUpdate = _ndUpdateUI;
      }
      ndSim.fit();
      ndSim.draw();
      ndSim._emit();
      // Pulsing loop for shade area animation
      if (!_ndPulseRaf && window.LabFX) {
        let _ndLast = performance.now();
        const pulse = (now) => {
          const dt = (now - _ndLast) / 1000; _ndLast = now;
          LabFX.particles.update(dt);
          if (ndSim && ndSim.shade !== 'none') ndSim.draw();
          _ndPulseRaf = requestAnimationFrame(pulse);
        };
        _ndPulseRaf = requestAnimationFrame(pulse);
      }
    }));
  }

  let _ndSoundTs = 0;
  function ndParam(name, val) {
    const v = parseFloat(val);
    const elId = name === 'mu' ? 'nd-mu-val' : 'nd-sigma-val';
    document.getElementById(elId).textContent = v % 1 === 0 ? v : v.toFixed(1);
    if (ndSim) ndSim.setParams({ [name]: v });
    const now = performance.now();
    if (window.LabFX && now - _ndSoundTs > 80) {
      _ndSoundTs = now;
      const sigma = name === 'sigma' ? v : (ndSim ? ndSim.sigma : 1);
      LabFX.sound.play('tick', { pitch: 0.7 + sigma * 0.3, volume: 0.1 });
    }
  }

  function ndShade(mode, btn) {
    document.querySelectorAll('.nd-shade-btn').forEach(b => b.classList.remove('active'));
    if (btn) btn.classList.add('active');
    if (ndSim) ndSim.setParams({ shade: mode });
  }

  function ndPreset(mu, sigma) {
    document.getElementById('sl-nd-mu').value = mu;     document.getElementById('nd-mu-val').textContent = mu;
    document.getElementById('sl-nd-sigma').value = sigma; document.getElementById('nd-sigma-val').textContent = sigma;
    if (ndSim) ndSim.setParams({ mu, sigma });
  }

  function _ndUpdateUI(info) {
    const v = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
    v('ndbar-v1', info.mu);
    v('ndbar-v2', info.sigma);
    v('ndbar-v3', info.peak);
    v('ndbar-v4', info.area);
  }

  /* ── graph transform ── */
