/* eslint-disable no-undef */
// =====================================================
// Phase portrait viz for 2D linear autonomous system
//   dx/dt = a*x + b*y
//   dy/dt = c*x + d*y
// =====================================================

const PHASE_PRESETS = {
  stableNode: {
    label: 'Устойчивый узел',
    a: -1, b: 0, c: 0, d: -2,
    note: 'λ₁ = −1, λ₂ = −2 — оба отрицательные ⇒ устойчиво.',
  },
  unstableNode: {
    label: 'Неустойчивый узел',
    a: 1, b: 0, c: 0, d: 2,
    note: 'λ₁ = 1, λ₂ = 2 — оба положительные.',
  },
  saddle: {
    label: 'Седло',
    a: 1, b: 0, c: 0, d: -1,
    note: 'λ₁ = 1, λ₂ = −1 — разных знаков. Всегда неустойчиво.',
  },
  stableFocus: {
    label: 'Устойчивый фокус',
    a: -0.3, b: -1, c: 1, d: -0.3,
    note: 'λ₁,₂ = −0.3 ± i — комплексные с Re < 0.',
  },
  unstableFocus: {
    label: 'Неустойчивый фокус',
    a: 0.3, b: -1, c: 1, d: 0.3,
    note: 'λ₁,₂ = 0.3 ± i — комплексные с Re > 0.',
  },
  center: {
    label: 'Центр',
    a: 0, b: -1, c: 1, d: 0,
    note: 'λ₁,₂ = ±i — чисто мнимые. Устойчив по Ляпунову, не асимптотически.',
  },
  degenerate: {
    label: 'Вырожденный узел',
    a: -1, b: 1, c: 0, d: -1,
    note: 'λ₁ = λ₂ = −1, жорданова клетка — лишь одно собственное направление.',
  },
  dicritical: {
    label: 'Дикритический узел',
    a: -1, b: 0, c: 0, d: -1,
    note: 'A = −E. Все направления собственные — траектории — прямые из центра.',
  },
};

function eigsOf2x2(a, b, c, d) {
  const tr = a + d;
  const det = a * d - b * c;
  const D = tr * tr - 4 * det;
  if (D >= 0) {
    const s = Math.sqrt(D);
    return { type: 'real', l1: (tr + s) / 2, l2: (tr - s) / 2, tr, det, D };
  } else {
    return { type: 'complex', re: tr / 2, im: Math.sqrt(-D) / 2, tr, det, D };
  }
}

function eigenvectorsReal(a, b, c, d, l) {
  // (A - l I) v = 0
  // [a-l   b   ] [vx]   [0]
  // [c     d-l ] [vy] = [0]
  // Try [b, l-a] if b!=0, else [d-l, -c] if c!=0, else [1,0] or [0,1]
  if (Math.abs(b) > 1e-9) {
    const v = [b, l - a];
    const n = Math.hypot(v[0], v[1]);
    return [v[0] / n, v[1] / n];
  }
  if (Math.abs(c) > 1e-9) {
    const v = [d - l, -c];
    const n = Math.hypot(v[0], v[1]);
    return [v[0] / n, v[1] / n];
  }
  // diagonal
  if (Math.abs(a - l) < 1e-9) return [1, 0];
  return [0, 1];
}

function PhasePortraitViz({ presetDefault = 'stableNode', showPresetButtons = true }) {
  const [preset, setPreset] = useState(presetDefault);
  const P0 = PHASE_PRESETS[preset];
  const [a, setA] = useState(P0.a);
  const [b, setB] = useState(P0.b);
  const [c, setC] = useState(P0.c);
  const [d, setD] = useState(P0.d);
  const [showField, setShowField] = useState(true);
  const [trajCount, setTrajCount] = useState(8);
  const [animate, setAnimate] = useState(false);

  useEffect(() => {
    const P = PHASE_PRESETS[preset];
    setA(P.a); setB(P.b); setC(P.c); setD(P.d);
  }, [preset]);

  const eigs = eigsOf2x2(a, b, c, d);
  const F = useCallback((y) => [a * y[0] + b * y[1], c * y[0] + d * y[1]], [a, b, c, d]);

  // animation tick
  const [tick, setTick] = useState(0);
  useEffect(() => {
    if (!animate) return;
    let raf;
    const start = performance.now();
    const loop = () => { setTick((performance.now() - start) / 1000); raf = requestAnimationFrame(loop); };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, [animate]);

  const canvasRef = useCanvas((ctx, W, H) => {
    const T = makeTransform({ xMin: -3, xMax: 3, yMin: -3, yMax: 3, W, H, pad: 38 });
    drawAxes(ctx, T, { tickStep: 1, xLabel: 'x₁', yLabel: 'x₂', gridColor: 'rgba(255,255,255,0.035)' });

    // background streamlines
    if (showField) {
      drawStreamlines(ctx, T, F, {
        seeds: 14 * 14,
        color: 'rgba(70,60,40,0.32)',
        lineWidth: 1.0,
        steps: 60,
        h: 0.05,
        arrowSize: 3.5,
      });
    }

    // characteristic directions (real eigvecs as straight lines)
    if (eigs.type === 'real') {
      const v1 = eigenvectorsReal(a, b, c, d, eigs.l1);
      const v2 = eigenvectorsReal(a, b, c, d, eigs.l2);
      const drawEvLine = (v, color) => {
        const L = 5;
        const pts = [[-L * v[0], -L * v[1]], [L * v[0], L * v[1]]];
        drawPath(ctx, T, pts, { color, lineWidth: 1.5, dashed: true });
      };
      drawEvLine(v1, 'rgba(42,94,158,0.4)');
      const same = Math.abs(eigs.l1 - eigs.l2) < 1e-6 && Math.abs(v1[0] - v2[0]) < 1e-3 && Math.abs(v1[1] - v2[1]) < 1e-3;
      if (!same) drawEvLine(v2, 'rgba(31,138,118,0.4)');
    }

    // initial conditions for trajectories — ring around origin
    const N = trajCount;
    const initPts = [];
    for (let i = 0; i < N; i++) {
      const ang = (i / N) * Math.PI * 2;
      const r = 2.4;
      initPts.push([r * Math.cos(ang), r * Math.sin(ang)]);
    }
    // for saddle and node, also include points along eigenvectors
    if (eigs.type === 'real') {
      const v1 = eigenvectorsReal(a, b, c, d, eigs.l1);
      const v2 = eigenvectorsReal(a, b, c, d, eigs.l2);
      [0.15, -0.15].forEach(s => {
        initPts.push([s * v1[0], s * v1[1]]);
        initPts.push([s * v2[0], s * v2[1]]);
      });
    }

    // determine integration direction depending on stability
    // For asymptotically stable: integrate backward from initial ring (to show trajectories arriving)
    // For unstable: forward
    // Saddle: do both
    const tr = a + d, det = a * d - b * c;
    const D = tr * tr - 4 * det;
    let direction = 'forward';
    if (eigs.type === 'real') {
      if (eigs.l1 < 0 && eigs.l2 < 0) direction = 'mixed';
      else if (eigs.l1 > 0 && eigs.l2 > 0) direction = 'forward';
      else direction = 'both';
    } else {
      if (eigs.re < 0) direction = 'mixed';
      else if (eigs.re > 0) direction = 'forward';
      else direction = 'forward'; // center
    }

    const tEnd = 8;
    const tBack = -8;
    initPts.forEach((p, idx) => {
      const color = TRAJ_COLORS[idx % TRAJ_COLORS.length];
      let pts = [];
      if (direction === 'forward' || direction === 'both' || direction === 'mixed') {
        const fwd = integrateVector(F, 0, p, tEnd, 0.02, { yBound: 1e3, maxSteps: 2000 });
        pts = fwd.map(s => s.y);
      }
      if (direction === 'both' || direction === 'mixed') {
        const bwd = integrateVector(F, 0, p, tBack, -0.02, { yBound: 1e3, maxSteps: 2000 });
        const bpts = bwd.map(s => s.y);
        pts = bpts.reverse().concat(pts.slice(1));
      }
      drawPath(ctx, T, pts, { color, lineWidth: 2.2 });
      // arrow on trajectory
      const arrowFrac = direction === 'mixed' ? 0.78 : 0.18;
      if (pts.length > 2) drawArrowHeadAt(ctx, T, pts, arrowFrac, { color, size: 8 });
    });

    // animated dots traveling along trajectories
    if (animate) {
      const t = tick % 4;
      initPts.forEach((p, idx) => {
        let cur = p.slice();
        const sign = direction === 'mixed' ? -1 : 1;
        let s = 0;
        const dt = 0.01;
        while (s < t) {
          cur = rk4Step((tt, yy) => F(yy), 0, cur, sign * dt);
          s += dt;
        }
        ctx.save();
        ctx.fillStyle = TRAJ_COLORS[idx % TRAJ_COLORS.length];
        ctx.beginPath();
        ctx.arc(T.X(cur[0]), T.Y(cur[1]), 4, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
      });
    }

    // origin marker
    drawDot(ctx, T, 0, 0, { color: '#1c1810', stroke: '#fbf6e9', r: 5 });

  }, [a, b, c, d, showField, trajCount, animate, tick, F, eigs]);

  const eigText = eigs.type === 'real'
    ? `λ₁ = ${eigs.l1.toFixed(2)},  λ₂ = ${eigs.l2.toFixed(2)}`
    : `λ₁,₂ = ${eigs.re.toFixed(2)} ${eigs.im >= 0 ? '+' : '−'} ${Math.abs(eigs.im).toFixed(2)} i`;

  return (
    <VizWrap
      title={PHASE_PRESETS[preset]?.label || 'Фазовый портрет'}
      formula={`ẋ = Ax`}
      captionUnder={PHASE_PRESETS[preset]?.note}
      controls={
        <React.Fragment>
          {showPresetButtons ? (
            <React.Fragment>
              <h4>Тип особой точки</h4>
              <ButtonGroup
                options={[
                  { value: 'stableNode',    label: 'устойчивый узел' },
                  { value: 'unstableNode',  label: 'неустойчивый узел' },
                  { value: 'saddle',        label: 'седло' },
                  { value: 'stableFocus',   label: 'устойч. фокус' },
                  { value: 'unstableFocus', label: 'неустойч. фокус' },
                  { value: 'center',        label: 'центр' },
                  { value: 'degenerate',    label: 'вырожденный' },
                  { value: 'dicritical',    label: 'дикритический' },
                ]}
                value={preset}
                setValue={setPreset}
                columns={1}
              />
            </React.Fragment>
          ) : null}

          <h4>Матрица A</h4>
          <div className="ctl-row">
            <Slider label="a₁₁" value={a} setValue={setA} min={-3} max={3} step={0.05} />
            <Slider label="a₁₂" value={b} setValue={setB} min={-3} max={3} step={0.05} />
          </div>
          <div className="ctl-row">
            <Slider label="a₂₁" value={c} setValue={setC} min={-3} max={3} step={0.05} />
            <Slider label="a₂₂" value={d} setValue={setD} min={-3} max={3} step={0.05} />
          </div>

          <h4>Отображение</h4>
          <Slider label="Число траекторий" value={trajCount} setValue={setTrajCount} min={2} max={16} step={1} formatter={(v) => Math.round(v)} />
          <Toggle label="Линии тока (фон)" value={showField} setValue={setShowField} />
          <Toggle label="Анимация движения" value={animate} setValue={setAnimate} />

          <div className="ctl-note" style={{ marginTop: 10 }}>
            tr A = {(a + d).toFixed(2)},  det A = {(a * d - b * c).toFixed(2)}<br />
            D = (tr)² − 4 det = {((a + d) ** 2 - 4 * (a * d - b * c)).toFixed(2)}
          </div>
        </React.Fragment>
      }
    >
      <canvas ref={canvasRef} />
      <div className="viz-legend chip top-right">{eigText}</div>
    </VizWrap>
  );
}

window.PhasePortraitViz = PhasePortraitViz;
window.PHASE_PRESETS = PHASE_PRESETS;
window.eigsOf2x2 = eigsOf2x2;
