// src/frontend/AudioVisualizer.jsx
import React, { useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
import PropTypes from 'prop-types';
import * as dat from 'dat.gui';
import './AudioVisualizer.css'; // Import the CSS for styling

const AudioVisualizer = forwardRef(({ mediaElementSource }, ref) => {
  const canvasRef = useRef(null);
  const guiRef = useRef(null);
  const animationIdRef = useRef(null);
  const analyserRef = useRef(null);
  const freqsRef = useRef(null);

  useImperativeHandle(ref, () => ({
    startVisualization: () => {
      if (!mediaElementSource) {
        console.error('No MediaElementSource provided.');
        return;
      }
      initializeVisualizer();
    },
  }));

  const initializeVisualizer = () => {
    const canvas = canvasRef.current;
    const ctx = canvas.getContext('2d');

    // Visualization options
    const opts = {
      smoothing: 0.6,
      fft: 8,
      minDecibels: -70,
      scale: 0.2,
      glow: 10,
      color1: [203, 36, 128],
      color2: [41, 200, 192],
      color3: [24, 137, 218],
      fillOpacity: 0.6,
      lineWidth: 1,
      blend: 'screen',
      shift: 50,
      width: 60,
      amp: 1,
    };

    // Initialize dat.GUI
    const gui = new dat.GUI();
    gui.close(); // Hide by default

    // Add controls to GUI
    gui.addColor(opts, 'color1').name('Color 1');
    gui.addColor(opts, 'color2').name('Color 2');
    gui.addColor(opts, 'color3').name('Color 3');
    gui.add(opts, 'fillOpacity', 0, 1).name('Fill Opacity');
    gui.add(opts, 'lineWidth', 0, 10).step(1).name('Line Width');
    gui.add(opts, 'glow', 0, 100).name('Glow');
    gui
      .add(opts, 'blend', [
        'normal',
        'multiply',
        'screen',
        'overlay',
        'lighten',
        'difference',
      ])
      .name('Blend Mode');
    gui
      .add(opts, 'smoothing', 0, 1)
      .name('Smoothing')
      .onChange((value) => {
        if (analyserRef.current) {
          analyserRef.current.smoothingTimeConstant = value;
        }
      });
    gui
      .add(opts, 'minDecibels', -100, 0)
      .name('Min Decibels')
      .onChange((value) => {
        if (analyserRef.current) {
          analyserRef.current.minDecibels = value;
        }
      });
    gui.add(opts, 'amp', 0, 5).name('Amplitude');
    gui.add(opts, 'width', 0, 60).name('Width');
    gui.add(opts, 'shift', 0, 200).name('Shift');

    guiRef.current = gui;

    // Initialize AudioContext and Analyser
    const audioContext = mediaElementSource.context;
    const analyser = audioContext.createAnalyser();
    analyser.smoothingTimeConstant = opts.smoothing;
    analyser.fftSize = Math.pow(2, opts.fft);
    analyser.minDecibels = opts.minDecibels;
    analyser.maxDecibels = 0;
    analyserRef.current = analyser;

    // Connect MediaElementSource to Analyser
    mediaElementSource.connect(analyser);
    analyser.connect(audioContext.destination);

    // Frequency Data Array
    const freqs = new Uint8Array(analyser.frequencyBinCount);
    freqsRef.current = freqs;

    // Utility Functions
    const range = (i) => Array.from({ length: i }, (_, index) => index);
    const shuffle = [1, 3, 0, 4, 2];

    const freqFn = (channel, i) => freqs[2 * channel + shuffle[i] * 6] || 0;
    const scaleFn = (i) => {
      const x = Math.abs(2 - i);
      const s = 3 - x;
      return (s / 3) * opts.amp;
    };

    const path = (channel) => {
      const color = opts[`color${channel + 1}`].map(Math.floor);
      ctx.fillStyle = `rgba(${color}, ${opts.fillOpacity})`;
      ctx.strokeStyle = ctx.shadowColor = `rgb(${color})`;
      ctx.lineWidth = opts.lineWidth;
      ctx.shadowBlur = opts.glow;
      ctx.globalCompositeOperation = opts.blend;

      const m = canvas.height / 2;
      const offset = (canvas.width - 15 * opts.width) / 2;
      const xPositions = range(15).map(
        (i) => offset + channel * opts.shift + i * opts.width
      );

      const yValues = range(5).map((i) =>
        Math.max(0, m - scaleFn(i) * freqFn(channel, i))
      );
      const h = 2 * m;

      ctx.beginPath();
      ctx.moveTo(0, m);
      ctx.lineTo(xPositions[0], m + 1);

      // Upper Path
      for (let i = 0; i < 5; i++) {
        ctx.bezierCurveTo(
          xPositions[1 + i * 2],
          m + 1,
          xPositions[2 + i * 2],
          yValues[i],
          xPositions[3 + i * 2],
          yValues[i]
        );
      }

      ctx.bezierCurveTo(
        xPositions[13],
        m,
        xPositions[12],
        m,
        xPositions[13],
        m + 1
      );
      ctx.lineTo(canvas.width, m + 1);
      ctx.lineTo(xPositions[13], m - 1);

      // Lower Path
      for (let i = 4; i >= 0; i--) {
        ctx.bezierCurveTo(
          xPositions[12 - i * 2],
          m,
          xPositions[12 - i * 2],
          h - yValues[i],
          xPositions[11 - i * 2],
          h - yValues[i]
        );
      }

      ctx.lineTo(0, m);
      ctx.closePath();
      ctx.fill();
      ctx.stroke();
    };

    const visualize = () => {
      analyser.getByteFrequencyData(freqs);

      // Clear Canvas
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // Draw Paths for Each Channel
      path(0);
      path(1);
      path(2);

      // Schedule next frame
      animationIdRef.current = requestAnimationFrame(visualize);
    };

    // Start Visualization
    visualize();
  };

  // Cleanup on Unmount
  useEffect(() => {
    return () => {
      cancelAnimationFrame(animationIdRef.current);
      if (guiRef.current) {
        guiRef.current.destroy();
      }
      if (analyserRef.current) {
        analyserRef.current.disconnect();
      }
    };
  }, []);

  return (
    <canvas
      ref={canvasRef}
      width={1000}
      height={400}
      className="audio-visualizer-canvas"
    ></canvas>
  );
});

AudioVisualizer.propTypes = {
  mediaElementSource: PropTypes.object.isRequired,
};

export default AudioVisualizer;
