import { useCallback, useEffect, useRef } from 'react';

let isRunning = false;
const callbacks: (() => void)[] = [];

const run = () => {
  requestAnimationFrame(run);
  isRunning = true;
  callbacks.forEach((c) => c());
};

const useAnimationFrame = (callback: () => void, shouldRun: boolean = true) => {
  useEffect(() => {
    if (!isRunning) {
      run();
    }
    if (shouldRun) {
      callbacks.push(callback);
      return () => {
        callbacks.splice(callbacks.indexOf(callback), 1);
      };
    }
  }, [callback, shouldRun]);
};

const TAU = 2 * Math.PI;
const LINE_ITERATIONS = 300;
const ITERATION_DIVISOR = LINE_ITERATIONS - 1;

const SineCycle = ({
  cycles = 2,
  width = 50,
  height = 50,
  padding = 4,
  color = 'white',
  speed = 1,
}: {
  cycles?: number;
  width?: number;
  height?: number;
  padding?: number;
  color?: string;
  speed?: number;
}) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const ctxRef = useRef<CanvasRenderingContext2D | null>(null);
  const offsetRef = useRef(0);
  useAnimationFrame(
    useCallback(() => {
      if (!canvasRef.current) return;
      if (!ctxRef.current) {
        ctxRef.current = canvasRef.current.getContext('2d');
      }
      const ctx = ctxRef.current;
      if (!ctx) return;
      const doublePadding = padding * 2;
      const w = width - doublePadding;
      const h = height - doublePadding;
      const halfW = w / 2;
      const halfH = h / 2;

      const radiansPerIteration = (cycles * TAU) / ITERATION_DIVISOR;

      offsetRef.current += speed;
      ctx.save();
      ctx.scale(1, 1);
      ctx.translate(padding, padding);
      ctx.clearRect(-padding, -padding, w + doublePadding, h + doublePadding);
      ctx.beginPath();
      ctx.strokeStyle = color;
      for (let i = 0; i < LINE_ITERATIONS; i++) {
        const x = Math.sin((((i + offsetRef.current) % ITERATION_DIVISOR) / ITERATION_DIVISOR) * TAU) * halfW + halfW;
        const s = Math.sin(i * radiansPerIteration);
        const y =
          halfH + halfH * s + Math.sin((((i - offsetRef.current) % ITERATION_DIVISOR) / ITERATION_DIVISOR) * TAU);
        if (i === 0) {
          ctx.moveTo(x, y);
        } else {
          ctx.lineTo(x, y);
        }
      }
      ctx.stroke();
      ctx.closePath();
      ctx.restore();
    }, [color, cycles, height, padding, speed, width])
  );

  return <canvas ref={canvasRef} width={width} height={height} style={{ width, height }} />;
};

export default SineCycle;
