/* eslint-disable no-undef */
// Common React hooks and helpers for visualizations
const { useState, useEffect, useLayoutEffect, useRef, useMemo, useCallback } = React;

// --- useCanvas: handles DPR-aware sizing + redraw on params change ---
function useCanvas(drawFn, deps = []) {
  const ref = useRef(null);
  const drawFnRef = useRef(drawFn);
  drawFnRef.current = drawFn;

  const doDraw = useCallback(() => {
    const cvs = ref.current;
    if (!cvs) return false;
    const rect = cvs.getBoundingClientRect();
    if (rect.width < 4 || rect.height < 4) return false;
    const { ctx, W, H } = setupCanvas(cvs);
    try { drawFnRef.current(ctx, W, H); } catch (e) { console.error('draw error', e); }
    return true;
  }, []);

  // initial mount: just set up observers (no dep)
  useLayoutEffect(() => {
    const cvs = ref.current;
    if (!cvs) return;
    let raf = 0;
    let retries = 0;
    const tryDraw = () => {
      if (doDraw()) { retries = 0; return; }
      if (retries++ < 60) raf = requestAnimationFrame(tryDraw);
    };
    const scheduleDraw = () => {
      cancelAnimationFrame(raf);
      retries = 0;
      raf = requestAnimationFrame(tryDraw);
    };
    const onResize = scheduleDraw;
    window.addEventListener('resize', onResize);
    const ro = new ResizeObserver(scheduleDraw);
    if (cvs.parentElement) ro.observe(cvs.parentElement);
    scheduleDraw();
    cvs.__scheduleDraw = scheduleDraw;
    return () => {
      window.removeEventListener('resize', onResize);
      ro.disconnect();
      cancelAnimationFrame(raf);
    };
  }, []);

  // redraw on dep change
  useEffect(() => {
    doDraw();
  }, deps);

  return ref;
}

// --- small controls ---
function Slider({ label, value, setValue, min, max, step = 0.01, formatter }) {
  const fmt = formatter || ((v) => (typeof v === 'number' ? v.toFixed(2).replace(/\.00$/, '') : v));
  return (
    <div className="ctl">
      <div className="ctl-label">
        <span>{label}</span>
        <span className="val">{fmt(value)}</span>
      </div>
      <input
        type="range"
        min={min}
        max={max}
        step={step}
        value={value}
        onChange={(e) => setValue(parseFloat(e.target.value))}
      />
    </div>
  );
}

function Toggle({ label, value, setValue }) {
  return (
    <div className={`ctl-toggle ${value ? 'on' : ''}`} onClick={() => setValue(!value)}>
      <span>{label}</span>
      <span className="pip"></span>
    </div>
  );
}

function ButtonGroup({ options, value, setValue, columns = 2 }) {
  return (
    <div className="ctl-btn-group" style={{ gridTemplateColumns: `repeat(${columns},1fr)` }}>
      {options.map(opt => (
        <div
          key={opt.value}
          className={`ctl-btn ${value === opt.value ? 'active' : ''}`}
          onClick={() => setValue(opt.value)}
        >
          {opt.label}
        </div>
      ))}
    </div>
  );
}

// --- Viz wrapper ---
function VizWrap({ title, formula, children, controls, captionUnder }) {
  return (
    <React.Fragment>
      <div className="viz-wrap">
        <div className="viz-header">
          <span>{title}</span>
          {formula ? <span className="formula">{formula}</span> : null}
        </div>
        <div className="viz-canvas-host">
          {children}
        </div>
        <div className="viz-controls">{controls}</div>
      </div>
      {captionUnder ? <div className="viz-caption">{captionUnder}</div> : null}
    </React.Fragment>
  );
}

// expose
Object.assign(window, { useCanvas, Slider, Toggle, ButtonGroup, VizWrap, useState, useEffect, useLayoutEffect, useRef, useMemo, useCallback });
