'use strict';
/* ══════════════════════════════════════════════════════════════
   QuadraticSim — interactive quadratic equation explorer
   y = ax² + bx + c   ·   discriminant, roots, vertex
   ══════════════════════════════════════════════════════════════ */

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

    /* coefficients */
    this.a = 1;
    this.b = 0;
    this.c = -1;
    this._lastDSign = Math.sign(1 * 1 * 1 - 4 * 1 * (-1)); // track discriminant sign

    /* view */
    this.ox  = 0;
    this.oy  = 0;
    this.scl = 40; // px per unit

    /* interaction */
    this._drag = null;
    this.hx    = null; // hovered math x

    /* callback */
    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 { a: this.a, b: this.b, c: this.c }; }
  setParams({ a, b, c } = {}) {
    if (a !== undefined) this.a = +a;
    if (b !== undefined) this.b = +b;
    if (c !== undefined) this.c = +c;
    this.draw();
    this._emit();
  }

  resetView() { this.ox = 0; this.oy = 0; this.scl = 40; this.draw(); }
  zoomIn()    { this.scl = Math.min(800, this.scl * 1.3); this.draw(); }
  zoomOut()   { this.scl = Math.max(4,   this.scl / 1.3); this.draw(); }

  info() {
    const { a, b, c } = this;
    const D = b * b - 4 * a * c;
    let roots = '—';
    let rootCount = 0;
    if (a === 0) {
      roots = b !== 0 ? `x = ${this._fmt(-c / b)}` : '—';
      rootCount = b !== 0 ? 1 : 0;
    } else if (D > 0.0001) {
      const sqD = Math.sqrt(D);
      const x1 = (-b - sqD) / (2 * a);
      const x2 = (-b + sqD) / (2 * a);
      roots = `x₁=${this._fmt(x1)}, x₂=${this._fmt(x2)}`;
      rootCount = 2;
    } else if (Math.abs(D) <= 0.0001) {
      roots = `x = ${this._fmt(-b / (2 * a))}`;
      rootCount = 1;
    }

    const vx = a !== 0 ? -b / (2 * a) : 0;
    const vy = a !== 0 ? c - b * b / (4 * a) : 0;

    return {
      D: this._fmt(D),
      rootCount,
      roots,
      vertex: a !== 0 ? `(${this._fmt(vx)}; ${this._fmt(vy)})` : '—',
      equation: `y = ${a !== 1 ? (a === -1 ? '−' : this._fmt(a)) : ''}x² ${b >= 0 ? '+' : '−'} ${this._fmt(Math.abs(b))}x ${c >= 0 ? '+' : '−'} ${this._fmt(Math.abs(c))}`,
    };
  }

  /* ── internals ────────────────────────────────────── */

  _fmt(n) {
    if (Number.isInteger(n)) return String(n);
    return Math.abs(n) < 0.005 ? '0' : n.toFixed(2).replace(/\.?0+$/, '');
  }

  _f(x) {
    return this.a * x * x + this.b * x + this.c;
  }

  _emit() {
    if (this.onUpdate) this.onUpdate(this.info());
    if (window.LabFX) {
      const D = this.b * this.b - 4 * this.a * this.c;
      const sign = Math.sign(D);
      if (sign !== this._lastDSign) {
        this._lastDSign = sign;
        LabFX.sound.play('chime', { pitch: 1.3 });
      }
    }
  }

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

  _toPx(mx, my) {
    return [
      this.W / 2 + (mx - this.ox) * this.scl,
      this.H / 2 - (my - this.oy) * this.scl,
    ];
  }

  _toMath(px, py) {
    return [
      (px - this.W / 2) / this.scl + this.ox,
      -(py - this.H / 2) / this.scl + this.oy,
    ];
  }

  /* ── draw ─────────────────────────────────────────── */

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

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

    this._drawGrid(ctx, W, H);
    this._drawAxes(ctx, W, H);
    this._drawParabola(ctx, W, H);
    this._drawFeatures(ctx, W, H);
    if (this.hx !== null) this._drawHover(ctx, W, H);
  }

  /* ── grid & axes ──────────────────────────────────── */

  _niceStep() {
    const raw = this.W / this.scl / 8;
    const p = Math.pow(10, Math.floor(Math.log10(raw)));
    for (const m of [1, 2, 5, 10]) if (m * p >= raw) return m * p;
    return p;
  }

  _drawGrid(ctx, W, H) {
    const step = this._niceStep();
    const [x0] = this._toMath(0, 0), [x1] = this._toMath(W, 0);
    const [, y0] = this._toMath(0, H), [, y1] = this._toMath(0, 0);
    const gx = Math.floor(x0 / step) * step;
    const gy = Math.floor(y0 / step) * step;

    ctx.strokeStyle = 'rgba(255,255,255,0.065)';
    ctx.lineWidth = 1;
    for (let x = gx; x <= x1 + step; x += step) {
      const [px] = this._toPx(x, 0);
      ctx.beginPath(); ctx.moveTo(px, 0); ctx.lineTo(px, H); ctx.stroke();
    }
    for (let y = gy; y <= y1 + step; y += step) {
      const [, py] = this._toPx(0, y);
      ctx.beginPath(); ctx.moveTo(0, py); ctx.lineTo(W, py); ctx.stroke();
    }

    // labels
    ctx.font = '11px Manrope, system-ui, sans-serif';
    ctx.fillStyle = 'rgba(255,255,255,0.3)';
    const [axX, axY] = this._toPx(0, 0);
    const lblY = Math.max(4, Math.min(H - 18, axY + 5));
    const lblX = Math.max(28, Math.min(W - 6, axX - 5));

    ctx.textAlign = 'center'; ctx.textBaseline = 'top';
    for (let x = gx; x <= x1; x += step) {
      if (Math.abs(x) < step * 0.01) continue;
      const [px] = this._toPx(x, 0);
      if (px < 18 || px > W - 18) continue;
      ctx.fillText(this._fmtLabel(x, step), px, lblY);
    }
    ctx.textAlign = 'right'; ctx.textBaseline = 'middle';
    for (let y = gy; y <= y1; y += step) {
      if (Math.abs(y) < step * 0.01) continue;
      const [, py] = this._toPx(0, y);
      if (py < 12 || py > H - 12) continue;
      ctx.fillText(this._fmtLabel(y, step), lblX, py);
    }
  }

  _fmtLabel(n, step) {
    if (n === 0) return '0';
    if (step >= 1 && Number.isInteger(n)) return String(n);
    if (step < 0.001) return n.toExponential(1);
    const dec = Math.max(0, -Math.floor(Math.log10(step)));
    return n.toFixed(dec);
  }

  _drawAxes(ctx, W, H) {
    const [ax, ay] = this._toPx(0, 0);
    ctx.strokeStyle = 'rgba(255,255,255,0.4)';
    ctx.lineWidth = 1.5;
    ctx.beginPath(); ctx.moveTo(0, ay);  ctx.lineTo(W - 10, ay); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(ax, H); ctx.lineTo(ax, 8);      ctx.stroke();

    // arrowheads
    ctx.fillStyle = 'rgba(255,255,255,0.4)';
    this._arrowHead(ctx, W - 8, ay, 0);
    this._arrowHead(ctx, ax, 6, -Math.PI / 2);

    ctx.fillStyle = 'rgba(255,255,255,0.55)';
    ctx.font = 'bold 12px Manrope, sans-serif';
    ctx.textBaseline = 'middle'; ctx.textAlign = 'left';
    ctx.fillText('x', W - 10, ay - 13);
    ctx.textBaseline = 'top'; ctx.textAlign = 'left';
    ctx.fillText('y', ax + 7, 4);
  }

  _arrowHead(ctx, x, y, angle) {
    const s = 5;
    ctx.save(); ctx.translate(x, y); ctx.rotate(angle);
    ctx.beginPath();
    ctx.moveTo(0, 0); ctx.lineTo(-s * 1.6, -s * 0.6); ctx.lineTo(-s * 1.6, s * 0.6);
    ctx.closePath(); ctx.fill();
    ctx.restore();
  }

  /* ── parabola curve ───────────────────────────────── */

  _drawParabola(ctx, W, H) {
    const steps = Math.min(W * 2, 2000);
    const [x0] = this._toMath(0, 0), [x1] = this._toMath(W, 0);
    const dx   = (x1 - x0) / steps;

    // glow
    ctx.strokeStyle = 'rgba(155,93,229,0.15)';
    ctx.lineWidth = 8;
    ctx.lineJoin = 'round';
    ctx.beginPath();
    let pen = false;
    for (let i = 0; i <= steps; i++) {
      const mx = x0 + i * dx;
      const my = this._f(mx);
      if (!isFinite(my)) { pen = false; continue; }
      const [px, py] = this._toPx(mx, my);
      if (py < -200 || py > H + 200) { pen = false; continue; }
      pen ? ctx.lineTo(px, py) : ctx.moveTo(px, py);
      pen = true;
    }
    ctx.stroke();

    // main curve
    ctx.strokeStyle = '#9B5DE5';
    ctx.lineWidth = 2.5;
    ctx.beginPath();
    pen = false;
    for (let i = 0; i <= steps; i++) {
      const mx = x0 + i * dx;
      const my = this._f(mx);
      if (!isFinite(my)) { pen = false; continue; }
      const [px, py] = this._toPx(mx, my);
      if (py < -200 || py > H + 200) { pen = false; continue; }
      pen ? ctx.lineTo(px, py) : ctx.moveTo(px, py);
      pen = true;
    }
    ctx.stroke();
  }

  /* ── vertex, roots, axis of symmetry ──────────────── */

  _drawFeatures(ctx, W, H) {
    const { a, b, c } = this;
    if (a === 0) return; // linear — no features

    const vx = -b / (2 * a);
    const vy = this._f(vx);
    const D  = b * b - 4 * a * c;

    // axis of symmetry
    const [symPx] = this._toPx(vx, 0);
    ctx.strokeStyle = 'rgba(6,214,224,0.25)';
    ctx.lineWidth = 1;
    ctx.setLineDash([6, 4]);
    ctx.beginPath(); ctx.moveTo(symPx, 0); ctx.lineTo(symPx, H); ctx.stroke();
    ctx.setLineDash([]);

    // vertex point
    const [vpx, vpy] = this._toPx(vx, vy);
    if (vpy > -20 && vpy < H + 20) {
      ctx.fillStyle = '#06D6E0';
      ctx.beginPath(); ctx.arc(vpx, vpy, 6, 0, Math.PI * 2); ctx.fill();
      ctx.strokeStyle = 'rgba(255,255,255,0.8)'; ctx.lineWidth = 1.5; ctx.stroke();

      // label
      ctx.fillStyle = '#06D6E0';
      ctx.font = 'bold 12px Manrope, sans-serif';
      ctx.textAlign = 'center'; ctx.textBaseline = 'bottom';
      ctx.fillText(`(${this._fmt(vx)}; ${this._fmt(vy)})`, vpx, vpy - 12);
    }

    // roots
    if (D >= -0.0001) {
      ctx.fillStyle = '#EF476F';
      const roots = [];
      if (D > 0.0001) {
        const sqD = Math.sqrt(D);
        roots.push((-b - sqD) / (2 * a));
        roots.push((-b + sqD) / (2 * a));
      } else {
        roots.push(-b / (2 * a));
      }

      for (const rx of roots) {
        const [rpx, rpy] = this._toPx(rx, 0);
        if (rpx < -20 || rpx > W + 20) continue;

        // root dot with glow
        if (window.LabFX) {
          LabFX.glow.drawGlow(ctx, () => {
            ctx.fillStyle = '#EF476F';
            ctx.beginPath(); ctx.arc(rpx, rpy, 5.5, 0, Math.PI * 2); ctx.fill();
            ctx.strokeStyle = 'rgba(255,255,255,0.8)'; ctx.lineWidth = 1.5; ctx.stroke();
          }, { color: '#F59E0B', intensity: 8 });
        } else {
          ctx.fillStyle = '#EF476F';
          ctx.beginPath(); ctx.arc(rpx, rpy, 5.5, 0, Math.PI * 2); ctx.fill();
          ctx.strokeStyle = 'rgba(255,255,255,0.8)'; ctx.lineWidth = 1.5; ctx.stroke();
        }

        // label
        ctx.fillStyle = '#EF476F';
        ctx.font = '11px Manrope, sans-serif';
        ctx.textAlign = 'center'; ctx.textBaseline = 'top';
        ctx.fillText(this._fmt(rx), rpx, rpy + 10);
      }
    }

    // discriminant badge
    const badgeColor = D > 0.0001 ? '#7BF5A4' : (D < -0.0001 ? '#EF476F' : '#FFD166');
    const badgeText  = D > 0.0001 ? `D = ${this._fmt(D)} > 0  (2 корня)` :
                       D < -0.0001 ? `D = ${this._fmt(D)} < 0  (нет корней)` :
                       `D = 0  (1 корень)`;
    ctx.font = 'bold 12px Manrope, sans-serif';
    const tw = ctx.measureText(badgeText).width;
    const bx = W - tw - 28, by = 16;
    ctx.fillStyle = 'rgba(22,22,38,0.85)';
    ctx.beginPath(); ctx.roundRect(bx, by, tw + 20, 28, 8); ctx.fill();
    ctx.strokeStyle = badgeColor; ctx.lineWidth = 1;
    ctx.beginPath(); ctx.roundRect(bx, by, tw + 20, 28, 8); ctx.stroke();
    ctx.fillStyle = badgeColor;
    ctx.textAlign = 'left'; ctx.textBaseline = 'middle';
    ctx.fillText(badgeText, bx + 10, by + 14);
  }

  /* ── hover crosshair ──────────────────────────────── */

  _drawHover(ctx, W, H) {
    const [px] = this._toPx(this.hx, 0);
    const my = this._f(this.hx);
    if (!isFinite(my)) return;
    const [, py] = this._toPx(this.hx, my);

    // vertical line
    ctx.strokeStyle = 'rgba(255,255,255,0.15)';
    ctx.lineWidth = 1;
    ctx.setLineDash([5, 5]);
    ctx.beginPath(); ctx.moveTo(px, 0); ctx.lineTo(px, H); ctx.stroke();
    ctx.setLineDash([]);

    if (py < -20 || py > H + 20) return;

    // point
    ctx.fillStyle = '#FFD166';
    ctx.beginPath(); ctx.arc(px, py, 5, 0, Math.PI * 2); ctx.fill();
    ctx.strokeStyle = 'rgba(255,255,255,0.8)'; ctx.lineWidth = 1.5; ctx.stroke();

    // tooltip
    ctx.fillStyle = 'rgba(22,22,38,0.9)';
    const text = `(${this._fmt(this.hx)}, ${this._fmt(my)})`;
    ctx.font = '12px Manrope, sans-serif';
    const tw2 = ctx.measureText(text).width;
    const tx = px + 14, ty = py - 14;
    ctx.beginPath(); ctx.roundRect(tx, ty - 10, tw2 + 16, 22, 6); ctx.fill();
    ctx.fillStyle = '#FFD166';
    ctx.textAlign = 'left'; ctx.textBaseline = 'middle';
    ctx.fillText(text, tx + 8, ty + 1);
  }

  /* ── events ───────────────────────────────────────── */

  _bind() {
    const cv = this.canvas;

    cv.addEventListener('wheel', e => {
      e.preventDefault();
      const [mx, my] = this._toMath(e.offsetX, e.offsetY);
      this.scl = Math.max(4, Math.min(800, this.scl * (e.deltaY < 0 ? 1.15 : 1 / 1.15)));
      const [nx, ny] = this._toMath(e.offsetX, e.offsetY);
      this.ox -= nx - mx; this.oy -= ny - my;
      this.draw();
    }, { passive: false });

    cv.addEventListener('mousedown', e => {
      this._drag = { x: e.clientX, y: e.clientY, ox: this.ox, oy: this.oy };
      cv.style.cursor = 'grabbing';
    });
    window.addEventListener('mousemove', e => {
      if (this._drag) {
        this.ox = this._drag.ox - (e.clientX - this._drag.x) / this.scl;
        this.oy = this._drag.oy + (e.clientY - this._drag.y) / this.scl;
        this.draw();
      } else {
        const r = cv.getBoundingClientRect();
        const lx = e.clientX - r.left, ly = e.clientY - r.top;
        if (lx >= 0 && lx <= r.width && ly >= 0 && ly <= r.height) {
          this.hx = this._toMath(lx, ly)[0];
          this.draw();
        }
      }
    });
    window.addEventListener('mouseup', () => {
      this._drag = null;
      cv.style.cursor = 'crosshair';
    });
    cv.addEventListener('mouseleave', () => {
      this.hx = null; this.draw();
    });
    cv.style.cursor = 'crosshair';

    // touch
    let t0 = null;
    cv.addEventListener('touchstart', e => {
      if (e.touches.length === 1)
        t0 = { x: e.touches[0].clientX, y: e.touches[0].clientY, ox: this.ox, oy: this.oy };
    }, { passive: true });
    cv.addEventListener('touchmove', e => {
      e.preventDefault();
      if (e.touches.length === 1 && t0) {
        this.ox = t0.ox - (e.touches[0].clientX - t0.x) / this.scl;
        this.oy = t0.oy + (e.touches[0].clientY - t0.y) / this.scl;
        this.draw();
      }
    }, { passive: false });
    cv.addEventListener('touchend', () => { t0 = null; });
  }
}

/* ─── lab UI init ─────────────────────────────────── */
  function _openQuadratic() {
    document.getElementById('sim-topbar-title').textContent = 'Корни квадратного уравнения';
    _simShow('sim-quadratic');
    _registerSimState('quadratic', () => quadSim?.getParams(), st => quadSim?.setParams(st));
    if (_embedMode) _startStateEmit('quadratic');
    requestAnimationFrame(() => requestAnimationFrame(() => {
      if (!quadSim) {
        quadSim = new QuadraticSim(document.getElementById('quadratic-canvas'));
        quadSim.onUpdate = _quadUpdateUI;
      }
      quadSim.fit();
      quadSim.draw();
      quadSim._emit();
    }));
  }

  let _quadSoundTs = 0;
  function quadParam(name, val) {
    const v = parseFloat(val);
    document.getElementById('quad-' + name + '-val').textContent = v % 1 === 0 ? v : v.toFixed(1);
    if (quadSim) quadSim.setParams({ [name]: v });
    const now = performance.now();
    if (window.LabFX && now - _quadSoundTs > 80) {
      _quadSoundTs = now;
      LabFX.sound.play('tick', { volume: 0.1 });
    }
  }

  function quadPreset(a, b, c) {
    document.getElementById('sl-quad-a').value = a; document.getElementById('quad-a-val').textContent = a;
    document.getElementById('sl-quad-b').value = b; document.getElementById('quad-b-val').textContent = b;
    document.getElementById('sl-quad-c').value = c; document.getElementById('quad-c-val').textContent = c;
    if (quadSim) quadSim.setParams({ a, b, c });
  }

  function _quadUpdateUI(info) {
    const v = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
    v('qbar-v1', 'D = ' + info.D);
    v('qbar-v2', info.roots);
    v('qbar-v3', info.vertex);
    v('qbar-v4', info.equation);
  }

  /* ── normal distribution ── */
