import { AudioPlaybackContext } from '@/lib/useAudioPlayback';
import styled from '@emotion/styled';
import { useCallback, useContext, useEffect, useMemo, useRef } from 'react';

const Wrapper = styled.div`
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;

  display: flex;
`;

const Canvas = styled.canvas`
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
`;

const FAST_HALF_LIFE = 0.250;

const energy: number[] = [];

const pills: {x: number, y: number, t: number, r: number, h: number, color: string }[] = [];
for (let i = 0; i < 20; i ++) {
  pills.push({
    x: Math.random(),
    y: Math.random(),
    t: Math.random() * 2 * Math.PI,
    r: Math.random() * 0.2,
    h: Math.random() * 0.15,
    color: `hsl(${Math.random() * 360}, 100%, 50%)`
  })
}

const TrackVisualizer = ({ bands }: { bands: number[][] }) => {

  const { playing, duration, getCurrentTime } = useContext(AudioPlaybackContext)

  const scaledBands = useMemo(() => {
    return bands.map((b) => {
      const squared = b.map((v) => v * v);
      const avg = squared.reduce((a, v) => a + v, 0) / squared.length;
      return squared.map((v) => v / avg);
    })
  }, [bands]);

  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const ctxRef = useRef<CanvasRenderingContext2D | null>(null);
  const playingRef = useRef<boolean>(false);

  useEffect(() => {
    const handleResize = () => {
      const canvas = canvasRef.current;
      if (!canvas) return;
      canvas.width = Math.round(canvas.parentElement!.clientWidth / 2) * 2;
      canvas.height = Math.round(canvas.parentElement!.clientHeight / 2) * 2;
    }
    window.addEventListener('resize', handleResize);
  }, []);

  const withXform = (ctx: CanvasRenderingContext2D, x: number, y: number, t: number, callback: () => void) => {
    ctx.save();
    ctx.translate(x, y);
    ctx.rotate(t);
    callback();
    ctx.restore();
  }

  const pill = (
    ctx: CanvasRenderingContext2D,
    r: number,
    h: number
  ) => {
    ctx.save();
    ctx.translate(0, -h/2);
    ctx.moveTo(r, 0);
    ctx.arc(0, 0, r, -Math.PI, 0);
    ctx.lineTo(r, h);
    ctx.arc(0, h, r, 0, Math.PI);
    ctx.lineTo(-r, 0);
    ctx.restore();
  }

  const startPlaying = useCallback(() => {
    const canvas = canvasRef.current;
    const ctx = ctxRef.current;
    if (!canvas || !ctx) return () => {};

    canvas.width = Math.round(canvas.parentElement!.clientWidth / 2) * 2;
    canvas.height = Math.round(canvas.parentElement!.clientHeight / 2) * 2;
    ctx.globalCompositeOperation = 'xor';

    const bandLength = scaledBands[0].length;

    let x = 0;
    let y = 0;

    const frame = (frameDelta: number) => {
      const time = getCurrentTime();
      const delta = frameDelta;
      const decayPow = delta / FAST_HALF_LIFE;

      const progress = time / duration;
      const index = Math.floor(progress * bandLength);

      scaledBands.forEach((b, i) => {
        if (!energy[i]) energy[i] = 0;
        energy[i] -= delta / 2;
        energy[i] *= (Math.pow(0.25, decayPow));
        if (playingRef.current) {
          energy[i] += b[index] * 0.05;
        }
      });

      const w = canvas.width;
      const h = canvas.height;

      const minD = Math.min(w, h);

      const s = (Math.max(energy[0], energy[1], energy[2], energy[3]) - 0.01) * 20
      if (s < 0) return;
      ctx.clearRect(0, 0, w, h);


      pills.forEach((p) => {
        ctx.fillStyle = p.color;
        ctx.beginPath();
        withXform(
          ctx,
          p.x * w,
          p.y * h,
          p.t,
          () => pill(ctx, p.r * minD + (energy[0] * 10), p.h * minD + (energy[3] * 10))
        );
        p.x += energy[1] * 0.0001 * Math.sin(p.t);
        p.y += energy[1] * 0.0001 * Math.cos(p.t);
        if (Math.abs(p.x) > 1.5) {
          p.x *= -1;
        }
        if (Math.abs(p.y) > 1.5) {
          p.y *= -1;
        }
        p.t += s * 0.0001;

        ctx.fill();
      });
    }

    let stopped = false;

    let lastFrameTime = Date.now();
    const frameLoop = (restart?: boolean) => {
      if (!stopped) {
        const now = Date.now();
        frame((now - lastFrameTime) / 1000);
        lastFrameTime = now;
        requestAnimationFrame(() => frameLoop());
      }
    }

    frameLoop(true);

    return () => stopped = true;
  }, [duration, scaledBands, getCurrentTime]);

  const receiveCanvasRef = useCallback((canvas: HTMLCanvasElement | null) => {
    if (canvas) {
      canvasRef.current = canvas;
      ctxRef.current = canvas.getContext('2d');
    } else {
      canvasRef.current = null;
      ctxRef.current = null;
    }
  }, [bands, playing]);

  useEffect(() => {
    return startPlaying();
  }, [startPlaying]);

  useEffect(() => {
    playingRef.current = playing;
  }, [playing]);

  return (
    <Wrapper>
      <Canvas ref={receiveCanvasRef} />
    </Wrapper>
  )
}

export default TrackVisualizer;
