'use strict';
/* ══════════════════════════════════════════════════════════════
   GraphPanel — reusable stacked time-series widget
   Shows up to 3 traces (x/v/a or custom) in stacked sub-plots.

   Usage:
     const gp = new GraphPanel(canvas, {
       traces:  ['x', 'v', 'a'],
       labels:  ['x', 'v', 'a'],
       units:   ['м', 'м/с', 'м/с²'],
       colors:  ['#06D6E0', '#FFD166', '#EF476F'],
       maxPoints: 600
     });
     gp.push(t, [xVal, vVal, aVal]);   // call every frame
     gp.draw();                         // call every frame
     gp.clear();                        // reset
   ══════════════════════════════════════════════════════════════ */

class GraphPanel {
  constructor(canvas, opts) {
    opts = opts || {};
    this.canvas     = canvas;
    this.ctx        = canvas.getContext('2d');
    this.traces     = opts.traces    || ['x', 'v', 'a'];
    this.colors     = opts.colors    || ['#06D6E0', '#FFD166', '#EF476F'];
    this.labels     = opts.labels    || ['x', 'v', 'a'];
    this.units      = opts.units     || ['м', 'м/с', 'м/с²'];
    this.maxPoints  = opts.maxPoints || 600;
    this.bgColor    = opts.bgColor   || 'rgba(10,10,22,0.97)';
    this.gridColor  = opts.gridColor || 'rgba(255,255,255,0.06)';
    this.axisColor  = opts.axisColor || 'rgba(255,255,255,0.18)';
    this.frozen     = false;
    this._frozenSnap = null;
    this.t    = [];
    this.data = this.traces.map(() => []);
  }

  /* ─── Data feed ─────────────────────────────────────────────── */

  push(t, values) {
    if (this.frozen) return;
    this.t.push(t);
    values.forEach((v, i) => {
      if (this.data[i]) this.data[i].push(isFinite(v) ? v : 0);
    });
    while (this.t.length > this.maxPoints) {
      this.t.shift();
      this.data.forEach(arr => arr.shift());
    }
  }

  clear() {
    this.t = [];
    this.data.forEach(arr => { arr.length = 0; });
    this.frozen = false;
    this._frozenSnap = null;
    this.draw();
  }

  freeze() {
    this.frozen = !this.frozen;
    if (this.frozen) {
      this._frozenSnap = {
        t:      [...this.t],
        data:   this.data.map(arr => [...arr]),
        traces: this.traces,
        labels: this.labels
      };
    } else {
      this._frozenSnap = null;
    }
    return this._frozenSnap;
  }

  exportPNG() { return this.canvas.toDataURL('image/png'); }

  /* ─── Draw ───────────────────────────────────────────────────── */

  draw() {
    const { canvas, ctx } = this;
    const W = canvas.width;
    const H = canvas.height;
    const n = this.traces.length;
    if (!n) return;

    ctx.clearRect(0, 0, W, H);

    /* background */
    ctx.fillStyle = this.bgColor;
    ctx.fillRect(0, 0, W, H);

    /* frozen overlay badge */
    if (this.frozen) {
      ctx.save();
      ctx.fillStyle = 'rgba(255,209,102,0.12)';
      ctx.fillRect(0, 0, W, H);
      ctx.restore();
    }

    const PAD_L = 42;
    const PAD_R = 8;
    const PAD_TOP = 6;
    const PAD_BOT = 6;
    const plotH = Math.floor((H - PAD_TOP - PAD_BOT) / n);

    for (let ti = 0; ti < n; ti++) {
      const y0 = PAD_TOP + ti * plotH;
      const y1 = y0 + plotH;
      const pw = W - PAD_L - PAD_R;
      const ph = plotH - 2;

      /* sub-plot frame */
      if (ti > 0) {
        ctx.save();
        ctx.strokeStyle = this.gridColor;
        ctx.lineWidth = 1;
        ctx.setLineDash([3, 4]);
        ctx.beginPath();
        ctx.moveTo(PAD_L, y0);
        ctx.lineTo(W - PAD_R, y0);
        ctx.stroke();
        ctx.setLineDash([]);
        ctx.restore();
      }

      /* label */
      ctx.save();
      ctx.font = '10px Manrope,sans-serif';
      ctx.fillStyle = this.colors[ti] || '#fff';
      ctx.textAlign = 'left';
      ctx.fillText(this.labels[ti] || this.traces[ti], 2, y0 + 13);
      ctx.fillStyle = 'rgba(255,255,255,0.3)';
      ctx.font = '8px Manrope,sans-serif';
      ctx.fillText(this.units[ti] || '', 2, y0 + 23);
      ctx.restore();

      const arr = this.data[ti];
      if (!arr || arr.length < 2) continue;

      /* auto-scale */
      let mn = arr[0], mx = arr[0];
      for (let k = 1; k < arr.length; k++) {
        if (arr[k] < mn) mn = arr[k];
        if (arr[k] > mx) mx = arr[k];
      }
      const span = Math.max(mx - mn, 1e-9);
      const pad  = span * 0.12;
      const yMin = mn - pad;
      const yMax = mx + pad;

      const mapX = (idx) => PAD_L + (idx / (this.maxPoints - 1)) * pw;
      const mapY = (v)   => y0 + ph - ((v - yMin) / (yMax - yMin)) * ph;

      /* grid lines (3 horizontal) */
      ctx.save();
      ctx.strokeStyle = this.gridColor;
      ctx.lineWidth = 0.5;
      for (let g = 0; g <= 2; g++) {
        const gy = y0 + (g / 2) * ph;
        ctx.beginPath(); ctx.moveTo(PAD_L, gy); ctx.lineTo(W - PAD_R, gy); ctx.stroke();
      }
      ctx.restore();

      /* zero line */
      if (yMin < 0 && yMax > 0) {
        const zy = mapY(0);
        ctx.save();
        ctx.strokeStyle = 'rgba(255,255,255,0.22)';
        ctx.lineWidth = 0.8;
        ctx.setLineDash([2, 3]);
        ctx.beginPath(); ctx.moveTo(PAD_L, zy); ctx.lineTo(W - PAD_R, zy); ctx.stroke();
        ctx.setLineDash([]);
        ctx.restore();
      }

      /* y-axis min/max labels */
      ctx.save();
      ctx.font = '8px Manrope,sans-serif';
      ctx.fillStyle = 'rgba(255,255,255,0.28)';
      ctx.textAlign = 'right';
      ctx.fillText(_gpFmt(yMax), PAD_L - 2, y0 + 9);
      ctx.fillText(_gpFmt(yMin), PAD_L - 2, y0 + ph + 1);
      ctx.restore();

      /* trace */
      ctx.save();
      ctx.strokeStyle = this.colors[ti] || '#fff';
      ctx.lineWidth = 1.5;
      ctx.lineJoin = 'round';
      ctx.beginPath();
      const startIdx = Math.max(0, this.maxPoints - arr.length);
      let first = true;
      for (let k = 0; k < arr.length; k++) {
        const px = mapX(startIdx + k);
        const py = mapY(arr[k]);
        if (first) { ctx.moveTo(px, py); first = false; }
        else        { ctx.lineTo(px, py); }
      }
      ctx.stroke();
      ctx.restore();

      /* current-value dot + readout */
      const lastV = arr[arr.length - 1];
      const dotX  = mapX(startIdx + arr.length - 1);
      const dotY  = mapY(lastV);
      ctx.save();
      ctx.fillStyle = this.colors[ti] || '#fff';
      ctx.shadowColor = this.colors[ti] || '#fff';
      ctx.shadowBlur  = 6;
      ctx.beginPath();
      ctx.arc(dotX, dotY, 3, 0, Math.PI * 2);
      ctx.fill();
      ctx.shadowBlur = 0;

      ctx.font = 'bold 9px Manrope,sans-serif';
      ctx.fillStyle = this.colors[ti] || '#fff';
      ctx.textAlign = 'right';
      ctx.fillText(_gpFmt(lastV), W - PAD_R - 1, y0 + 12);
      ctx.restore();
    }

    /* frozen badge */
    if (this.frozen) {
      ctx.save();
      ctx.font = 'bold 9px Manrope,sans-serif';
      ctx.fillStyle = '#FFD166';
      ctx.textAlign = 'center';
      ctx.fillText('ЗАМОРОЖЕНО', W / 2, H - 3);
      ctx.restore();
    }
  }
}

/* compact number format helper */
function _gpFmt(v) {
  const av = Math.abs(v);
  if (av === 0)       return '0';
  if (av >= 1000)     return v.toFixed(0);
  if (av >= 100)      return v.toFixed(1);
  if (av >= 10)       return v.toFixed(2);
  if (av >= 0.1)      return v.toFixed(3);
  return v.toExponential(1);
}

window.LSGraphPanel = GraphPanel;

/* ══════════════════════════════════════════════════════════════
   GraphPanelUI — self-contained overlay widget for a sim panel
   Creates a bottom-overlay <div> with canvas + controls.

   Usage:
     const ui = new GraphPanelUI(parentWrap, panelOpts);
     ui.push(t, values);   // every RAF frame
     ui.isOn               // read whether active
   ══════════════════════════════════════════════════════════════ */

class GraphPanelUI {
  constructor(parentEl, opts) {
    opts = opts || {};
    this.isOn = false;
    this._parent = parentEl;
    this._panelOpts = opts;
    this._gp = null;
    this._el = null;

    // body-selector support (optional)
    this._bodySelector = opts.bodySelector || null;
    this._selectedBody = 0;
  }

  toggle() {
    this.isOn = !this.isOn;
    if (this.isOn) {
      this._build();
    } else {
      this._destroy();
    }
    return this.isOn;
  }

  _build() {
    if (this._el) return;

    const wrap = document.createElement('div');
    wrap.id = this._panelOpts.domId || 'gp-overlay-' + Date.now();
    wrap.style.cssText = [
      'position:absolute', 'bottom:0', 'right:0',
      'width:320px', 'height:248px',
      'background:rgba(10,10,22,0.97)',
      'border:1px solid rgba(255,255,255,0.1)',
      'border-radius:10px 0 0 0',
      'display:flex', 'flex-direction:column',
      'z-index:20', 'overflow:hidden',
    ].join(';');

    /* title bar */
    const bar = document.createElement('div');
    bar.style.cssText = 'display:flex;align-items:center;gap:4px;padding:4px 6px;background:rgba(255,255,255,0.04);border-bottom:1px solid rgba(255,255,255,0.07);flex-shrink:0';

    const title = document.createElement('span');
    title.style.cssText = 'flex:1;font-size:.72rem;color:rgba(255,255,255,.55);font-family:Manrope,sans-serif';
    title.textContent = this._panelOpts.title || 'Графики';
    bar.appendChild(title);

    /* body selector (optional) */
    if (this._bodySelector) {
      const sel = document.createElement('select');
      sel.style.cssText = 'font-size:.68rem;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:5px;padding:1px 4px;margin-right:4px';
      this._bodySelector.forEach((label, i) => {
        const opt = document.createElement('option');
        opt.value = i; opt.textContent = label;
        sel.appendChild(opt);
      });
      sel.addEventListener('change', () => {
        this._selectedBody = +sel.value;
        if (this._gp) this._gp.clear();
      });
      this._selEl = sel;
      bar.appendChild(sel);
    }

    /* clear button */
    const btnClr = document.createElement('button');
    btnClr.textContent = 'Сброс';
    btnClr.style.cssText = _gpBtnStyle();
    btnClr.addEventListener('click', () => this._gp && this._gp.clear());
    bar.appendChild(btnClr);

    /* freeze button */
    const btnFrz = document.createElement('button');
    btnFrz.textContent = 'Стоп';
    btnFrz.style.cssText = _gpBtnStyle();
    btnFrz.addEventListener('click', () => {
      if (!this._gp) return;
      this._gp.freeze();
      btnFrz.style.color = this._gp.frozen ? '#FFD166' : '';
    });
    bar.appendChild(btnFrz);

    /* export PNG button */
    const btnExp = document.createElement('button');
    btnExp.title = 'Экспорт PNG';
    btnExp.style.cssText = _gpBtnStyle();
    btnExp.innerHTML = '<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>';
    btnExp.addEventListener('click', () => {
      if (!this._gp) return;
      const a = document.createElement('a');
      a.href     = this._gp.exportPNG();
      a.download = 'graph.png';
      a.click();
    });
    bar.appendChild(btnExp);

    /* close button */
    const btnX = document.createElement('button');
    btnX.innerHTML = '<svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
    btnX.style.cssText = _gpBtnStyle('rgba(239,71,111,0.6)');
    btnX.addEventListener('click', () => {
      this.isOn = false;
      this._destroy();
      const toggleBtn = document.getElementById(this._panelOpts.toggleBtnId);
      if (toggleBtn) toggleBtn.classList.remove('active');
    });
    bar.appendChild(btnX);

    wrap.appendChild(bar);

    /* canvas */
    const cv = document.createElement('canvas');
    cv.style.cssText = 'flex:1;width:100%;display:block';
    wrap.appendChild(cv);

    this._parent.style.position = 'relative';
    this._parent.appendChild(wrap);
    this._el = wrap;

    /* init GraphPanel after layout */
    requestAnimationFrame(() => {
      cv.width  = cv.offsetWidth  || 320;
      cv.height = cv.offsetHeight || 214;
      this._gp = new GraphPanel(cv, this._panelOpts);
      this._gp.draw();
    });
  }

  _destroy() {
    if (this._el) {
      this._el.remove();
      this._el = null;
      this._gp = null;
    }
  }

  push(t, values) {
    if (!this.isOn || !this._gp) return;
    this._gp.push(t, values);
    this._gp.draw();
  }

  get selectedBody() { return this._selectedBody; }
}

function _gpBtnStyle(color) {
  return [
    'font-size:.65rem', 'padding:2px 6px',
    'border-radius:5px',
    'border:1px solid rgba(255,255,255,0.12)',
    'background:rgba(255,255,255,0.05)',
    'color:' + (color || 'rgba(255,255,255,0.55)'),
    'cursor:pointer', 'white-space:nowrap',
  ].join(';');
}

window.LSGraphPanelUI = GraphPanelUI;
