/* eslint-disable no-undef */
// =====================================================
// Phase flow playground — 2D autonomous systems
// "Кинь шарик в фазовое поле и почувствуй динамику"
// =====================================================

const PHASE_FLOW_SYSTEMS = {
  vdp: {
    label: 'Ван дер Поль',
    formula: "ẋ = y,  ẏ = μ(1−x²)y − x",
    F: ([x, y], params) => [y, params.mu * (1 - x * x) * y - x],
    params: { mu: { label: 'μ', min: 0, max: 3, step: 0.05, default: 1.0 } },
    xRange: [-3.5, 3.5], yRange: [-4.5, 4.5],
    seeds: [[0.1, 0], [2.5, 0], [-2.5, 0], [0, 3], [0, -3]],
  },
  predator: {
    label: 'Хищник–жертва',
    formula: "ẋ = αx − βxy,  ẏ = δxy − γy",
    F: ([x, y], p) => [p.a * x - p.b * x * y, p.d * x * y - p.g * y],
    params: {
      a: { label: 'α', min: 0.2, max: 2, step: 0.05, default: 1.0 },
      b: { label: 'β', min: 0.2, max: 2, step: 0.05, default: 1.0 },
      d: { label: 'δ', min: 0.2, max: 2, step: 0.05, default: 1.0 },
      g: { label: 'γ', min: 0.2, max: 2, step: 0.05, default: 1.0 },
    },
    xRange: [0, 4], yRange: [0, 4],
    seeds: [[1.5, 1.5], [2, 1], [1, 2], [0.5, 0.5]],
  },
  pendulum: {
    label: 'Маятник с трением',
    formula: "ẋ = y,  ẏ = −sin x − γy",
    F: ([x, y], p) => [y, -Math.sin(x) - p.gamma * y],
    params: { gamma: { label: 'γ', min: 0, max: 1.5, step: 0.02, default: 0.3 } },
    xRange: [-2 * Math.PI, 2 * Math.PI], yRange: [-3.5, 3.5],
    seeds: [[0, 2.5], [-Math.PI / 2, 0], [Math.PI / 2, 0], [3, 0]],
  },
  duffing: {
    label: 'Осциллятор Дюффинга',
    formula: "ẋ = y,  ẏ = x − x³ − δy",
    F: ([x, y], p) => [y, x - x * x * x - p.delta * y],
    params: { delta: { label: 'δ', min: 0, max: 1, step: 0.02, default: 0.15 } },
    xRange: [-2.2, 2.2], yRange: [-1.7, 1.7],
    seeds: [[0.5, 0.5], [-0.5, -0.5], [1.5, 0], [-1.5, 0]],
  },
  saddle: {
    label: 'Линейное седло',
    formula: "ẋ = x + y,  ẏ = x − y",
    F: ([x, y]) => [x + y, x - y],
    params: {},
    xRange: [-3, 3], yRange: [-3, 3],
    seeds: [[2, 0.1], [-2, -0.1], [0.1, 2], [-0.1, -2]],
  },
};

function PhaseFlowPlaygroundViz() {
  const [systemKey, setSystemKey] = useState('vdp');
  const [showField, setShowField] = useState(true);
  const [colorBy, setColorBy] = useState('speed');
  const [particleSpeed, setParticleSpeed] = useState(1);

  const sys = PHASE_FLOW_SYSTEMS[systemKey];

  // params state — separate values per system
  const [paramVals, setParamVals] = useState(() => {
    const init = {};
    for (const key of Object.keys(PHASE_FLOW_SYSTEMS)) {
      init[key] = {};
      for (const pk of Object.keys(PHASE_FLOW_SYSTEMS[key].params)) {
        init[key][pk] = PHASE_FLOW_SYSTEMS[key].params[pk].default;
      }
    }
    return init;
  });

  const params = paramVals[systemKey];

  const F = useCallback((p) => sys.F(p, params), [sys, params]);

  const particlesRef = useRef([]);
  const canvasElRef = useRef(null);

  // seed two initial particles to demonstrate the dynamics; user can clear
  useEffect(() => {
    particlesRef.current = [];
    for (const seed of sys.seeds.slice(0, 2)) {
      particlesRef.current.push({
        x: seed[0], y: seed[1],
        trail: [[seed[0], seed[1]]],
        age: 0, maxAge: 14 + Math.random() * 4,
        hue: 30 + Math.random() * 280,
      });
    }
  }, [systemKey]);

  const addParticle = (x, y) => {
    particlesRef.current.push({
      x, y, trail: [[x, y]],
      age: 0, maxAge: 10 + Math.random() * 5,
      hue: 30 + Math.random() * 280,
    });
    if (particlesRef.current.length > 80) {
      particlesRef.current.splice(0, particlesRef.current.length - 80);
    }
  };

  // animation
  useEffect(() => {
    const cvs = canvasElRef.current;
    if (!cvs) return;
    let raf = 0;
    let lastT = performance.now();
    let alive = true;

    const tick = (now) => {
      if (!alive) return;
      try {
      const dtReal = Math.min(0.05, (now - lastT) / 1000);
      lastT = now;
      const dt = dtReal * particleSpeed;

      // integrate
      const next = [];
      let maxSpeed = 0.1;
      for (const p of particlesRef.current) {
        try {
          const newPos = rk4Step((t, y) => F(y), 0, [p.x, p.y], dt);
          if (!isFinite(newPos[0]) || !isFinite(newPos[1])) continue;
          p.x = newPos[0]; p.y = newPos[1];
          p.trail.push([p.x, p.y]);
          if (p.trail.length > 120) p.trail.shift();
          p.age += dtReal;
          // bounds + age culling
          if (p.x < sys.xRange[0] - 0.5 || p.x > sys.xRange[1] + 0.5) continue;
          if (p.y < sys.yRange[0] - 0.5 || p.y > sys.yRange[1] + 0.5) continue;
          if (p.age > p.maxAge) continue;
          // track max speed for normalization
          const v = F([p.x, p.y]);
          const sp = Math.hypot(v[0], v[1]);
          if (sp > maxSpeed) maxSpeed = sp;
          next.push(p);
        } catch (e) { /* skip */ }
      }
      particlesRef.current = next;

      // draw
      const { ctx, W, H } = setupCanvas(cvs);
      const T = makeTransform({
        xMin: sys.xRange[0], xMax: sys.xRange[1],
        yMin: sys.yRange[0], yMax: sys.yRange[1],
        W, H, pad: 36,
      });
      drawAxes(ctx, T, { tickStep: systemKey === 'pendulum' ? Math.PI / 2 : 1, xLabel: 'x', yLabel: 'y' });

      if (showField) {
        drawVectorField(ctx, T, F, {
          nx: 28, ny: 18,
          color: 'rgba(60,55,40,0.32)',
          length: 12,
        });
      }

      const speedNorm = (s) => Math.min(1, s / Math.max(0.5, maxSpeed * 0.7));

      // draw particles
      for (const p of particlesRef.current) {
        const fadeOut = Math.max(0, 1 - p.age / p.maxAge);
        if (p.trail.length > 1) {
          for (let i = 1; i < p.trail.length; i++) {
            const t = i / p.trail.length;
            const [px, py] = p.trail[i - 1];
            const [cx, cy] = p.trail[i];
            const v = F([(px + cx) / 2, (py + cy) / 2]);
            const sp = Math.hypot(v[0], v[1]);
            const sn = speedNorm(sp);
            let color;
            if (colorBy === 'speed') {
              const hue = 210 - sn * 200;
              color = `hsla(${hue}, 65%, 45%, ${fadeOut * t * 0.85})`;
            } else if (colorBy === 'angle') {
              const ang = Math.atan2(v[1], v[0]);
              const hue = ((ang + Math.PI) / (2 * Math.PI)) * 360;
              color = `hsla(${hue}, 60%, 45%, ${fadeOut * t * 0.85})`;
            } else {
              color = `hsla(${p.hue}, 60%, 45%, ${fadeOut * t * 0.85})`;
            }
            ctx.strokeStyle = color;
            ctx.lineWidth = 1.4 + 1.8 * t * fadeOut;
            ctx.beginPath();
            ctx.moveTo(T.X(px), T.Y(py));
            ctx.lineTo(T.X(cx), T.Y(cy));
            ctx.stroke();
          }
        }
        const v = F([p.x, p.y]);
        const sp = Math.hypot(v[0], v[1]);
        const sn = speedNorm(sp);
        let headColor;
        if (colorBy === 'speed') {
          const hue = 210 - sn * 200;
          headColor = `hsl(${hue}, 70%, 42%)`;
        } else if (colorBy === 'angle') {
          const ang = Math.atan2(v[1], v[0]);
          headColor = `hsl(${((ang + Math.PI) / (2 * Math.PI)) * 360}, 70%, 42%)`;
        } else {
          headColor = `hsl(${p.hue}, 70%, 42%)`;
        }
        ctx.save();
        ctx.shadowColor = headColor;
        ctx.shadowBlur = 6;
        ctx.fillStyle = headColor;
        ctx.beginPath();
        ctx.arc(T.X(p.x), T.Y(p.y), 4.2 * fadeOut + 1.2, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
      }

      // (hint text is rendered below the chart, not on the canvas)

      raf = requestAnimationFrame(tick);
      } catch (err) { console.error('[phase-flow tick]', err); alive = false; }
    };
    raf = requestAnimationFrame(tick);
    return () => { alive = false; cancelAnimationFrame(raf); };
  }, [systemKey, paramVals, showField, colorBy, particleSpeed]);

  const onClick = (e) => {
    const cvs = canvasElRef.current;
    if (!cvs) return;
    const rect = cvs.getBoundingClientRect();
    const px = e.clientX - rect.left;
    const py = e.clientY - rect.top;
    const W = rect.width, H = rect.height;
    const T = makeTransform({
      xMin: sys.xRange[0], xMax: sys.xRange[1],
      yMin: sys.yRange[0], yMax: sys.yRange[1],
      W, H, pad: 36,
    });
    const x = sys.xRange[0] + (px - T.pad) / (W - 2 * T.pad) * (sys.xRange[1] - sys.xRange[0]);
    const y = sys.yRange[0] + ((H - py - T.pad) / (H - 2 * T.pad)) * (sys.yRange[1] - sys.yRange[0]);
    // single particle per click
    addParticle(x, y);
  };

  const reset = () => { particlesRef.current = []; };

  const setParam = (key, val) => {
    setParamVals(prev => ({ ...prev, [systemKey]: { ...prev[systemKey], [key]: val } }));
  };

  return (
    <VizWrap
      title="Песочница: фазовый поток"
      formula={sys.formula}
      captionUnder="Кликни по полю — оттуда стартанёт один шарик и покатится вдоль фазового потока. Цвет хвоста — скорость движения в каждой точке: чем теплее, тем быстрее."
      controls={
        <React.Fragment>
          <h4>Система</h4>
          <ButtonGroup
            options={Object.entries(PHASE_FLOW_SYSTEMS).map(([k, v]) => ({ value: k, label: v.label }))}
            value={systemKey}
            setValue={setSystemKey}
            columns={1}
          />

          {Object.keys(sys.params).length > 0 ? (
            <React.Fragment>
              <h4>Параметры</h4>
              {Object.entries(sys.params).map(([pk, pdef]) => (
                <Slider
                  key={pk}
                  label={pdef.label}
                  value={params[pk]}
                  setValue={(v) => setParam(pk, v)}
                  min={pdef.min} max={pdef.max} step={pdef.step}
                />
              ))}
            </React.Fragment>
          ) : null}

          <h4>Раскраска</h4>
          <ButtonGroup
            options={[
              { value: 'speed', label: 'скорость' },
              { value: 'angle', label: 'направление' },
              { value: 'time',  label: 'случайно' },
            ]}
            value={colorBy}
            setValue={setColorBy}
            columns={1}
          />

          <h4>Темп</h4>
          <Slider label="× темп" value={particleSpeed} setValue={setParticleSpeed} min={0.2} max={3} step={0.1} formatter={(v) => `×${v.toFixed(1)}`} />

          <Toggle label="Векторное поле" value={showField} setValue={setShowField} />

          <div className="ctl-btn" onClick={reset} style={{ marginTop: 12 }}>Очистить</div>
          <div className="ctl-note">Кликни по графику — шарики стартанут по полю. Попробуй Ван-дер-Поль: внутри и снаружи предельного цикла всё стекается на него.</div>
        </React.Fragment>
      }
    >
      <canvas ref={canvasElRef} onClick={onClick} style={{ cursor: 'crosshair' }} />
    </VizWrap>
  );
}

window.PhaseFlowPlaygroundViz = PhaseFlowPlaygroundViz;
