'use strict';
/* ══════════════════════════════════════════════════════════════
   SimUtil — shared utility functions for lab simulations.
   Loaded once before all sim scripts.
   ══════════════════════════════════════════════════════════════ */

const SimUtil = (() => {

  /**
   * DPR-aware canvas resize.  Sets canvas pixel size and applies
   * `setTransform` so subsequent drawing is in CSS-pixel coords.
   * Returns { W, H, dpr }.
   */
  function fitCanvas(canvas, ctx) {
    const dpr = window.devicePixelRatio || 1;
    const w   = canvas.offsetWidth  || canvas.parentElement?.offsetWidth  || 600;
    const h   = canvas.offsetHeight || canvas.parentElement?.offsetHeight || 400;
    canvas.width  = w * dpr;
    canvas.height = h * dpr;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    return { W: w, H: h, dpr };
  }

  /**
   * Compute a "nice" grid step for the given pixel range and target ~n divisions.
   */
  function niceStep(rangePx, scale, n) {
    if (!n) n = 8;
    const raw = rangePx / scale / n;
    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;
  }

  /**
   * Format number for grid/axis labels.
   */
  function fmt(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);
  }

  /**
   * Draw an arrow from (x1,y1) to (x2,y2).
   */
  function arrow(ctx, x1, y1, x2, y2, color, lw) {
    const dx = x2 - x1, dy = y2 - y1;
    const len = Math.sqrt(dx * dx + dy * dy);
    if (len < 1) return;
    const ux = dx / len, uy = dy / len;
    const hs = Math.min(10, len * 0.3); // head size

    ctx.save();
    ctx.strokeStyle = color || '#fff';
    ctx.fillStyle   = color || '#fff';
    ctx.lineWidth   = lw || 2;
    ctx.lineCap     = 'round';

    // shaft
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2 - ux * hs * 0.5, y2 - uy * hs * 0.5);
    ctx.stroke();

    // head
    ctx.beginPath();
    ctx.moveTo(x2, y2);
    ctx.lineTo(x2 - ux * hs - uy * hs * 0.4, y2 - uy * hs + ux * hs * 0.4);
    ctx.lineTo(x2 - ux * hs + uy * hs * 0.4, y2 - uy * hs - ux * hs * 0.4);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }

  /**
   * Draw a coordinate grid with labels.
   * @param {object} opts — { ox, oy, scl, font, gridColor, labelColor }
   */
  function drawGrid(ctx, W, H, opts) {
    const { ox = 0, oy = 0, scl = 50, font, gridColor, labelColor } = opts || {};

    const step = niceStep(W, scl);

    // math <svg class="ic" viewBox="0 0 24 24"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg> pixel helpers
    const toPx = (mx, my) => [W / 2 + (mx - ox) * scl, H / 2 - (my - oy) * scl];
    const toMx = (px) => (px - W / 2) / scl + ox;
    const toMy = (py) => -(py - H / 2) / scl + oy;

    const x0 = toMx(0), x1 = toMx(W);
    const y0 = toMy(H), y1 = toMy(0);
    const gx = Math.floor(x0 / step) * step;
    const gy = Math.floor(y0 / step) * step;

    // grid lines
    ctx.strokeStyle = gridColor || 'rgba(255,255,255,0.065)';
    ctx.lineWidth = 1;
    for (let x = gx; x <= x1 + step; x += step) {
      const [px] = 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] = toPx(0, y);
      ctx.beginPath(); ctx.moveTo(0, py); ctx.lineTo(W, py); ctx.stroke();
    }

    // labels
    ctx.font = font || '11px Manrope, system-ui, sans-serif';
    ctx.fillStyle = labelColor || 'rgba(255,255,255,0.3)';
    const [axX, axY] = 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] = toPx(x, 0);
      if (px < 18 || px > W - 18) continue;
      ctx.fillText(fmt(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] = toPx(0, y);
      if (py < 12 || py > H - 12) continue;
      ctx.fillText(fmt(y, step), lblX, py);
    }

    // axes
    ctx.strokeStyle = 'rgba(255,255,255,0.4)';
    ctx.lineWidth = 1.5;
    ctx.beginPath(); ctx.moveTo(0, axY);  ctx.lineTo(W - 10, axY); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(axX, H); ctx.lineTo(axX, 8);      ctx.stroke();

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

    // axis labels
    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, axY - 13);
    ctx.textBaseline = 'top'; ctx.textAlign = 'left';
    ctx.fillText('y', axX + 7, 4);
  }

  function _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();
  }

  /**
   * Draw a tooltip panel at (x, y).
   * @param {string[]} rows — lines of text
   * @param {object} opts — { bg, fg, font, padding, radius }
   */
  function tooltip(ctx, x, y, rows, opts) {
    const { bg = 'rgba(22,22,38,0.92)', fg = '#ddd', font = '12px Manrope, sans-serif',
            padding = 8, radius = 8 } = opts || {};
    ctx.save();
    ctx.font = font;
    let maxW = 0;
    for (const r of rows) maxW = Math.max(maxW, ctx.measureText(r).width);
    const w = maxW + padding * 2;
    const lineH = 17;
    const h = rows.length * lineH + padding * 2;
    const tx = x + 14, ty = y - h / 2;

    ctx.fillStyle = bg;
    ctx.beginPath();
    ctx.roundRect(tx, ty, w, h, radius);
    ctx.fill();
    ctx.fillStyle = fg;
    ctx.textBaseline = 'top';
    ctx.textAlign = 'left';
    for (let i = 0; i < rows.length; i++) {
      ctx.fillText(rows[i], tx + padding, ty + padding + i * lineH);
    }
    ctx.restore();
  }

  return { fitCanvas, niceStep, fmt, arrow, drawGrid, tooltip };
})();
