import { useAnimationFrame } from 'framer-motion';
import { useCallback, useContext, useEffect, useRef } from 'react';

import audioContext from '@/lib/audioContext';

import { LiveRadioContext } from './LiveRadioProvider';

interface AudioVisualizerProps {
  /** Optional CSS class name for styling the canvas element */
  className?: string;
  /** Target width for each bar in pixels. Default is 10 */
  targetBarWidth?: number;
  /** Minimum number of bars to display. Default is 20 */
  minBars?: number;
  /** Maximum number of bars to display. Default is 200 */
  maxBars?: number;
}

const AudioVisualizer = ({
  className = '',
  targetBarWidth = 10,
  minBars = 20,
  maxBars = 200,
}: AudioVisualizerProps) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const analyserRef = useRef<AnalyserNode | null>(null);
  const timeDataRef = useRef<Uint8Array<ArrayBuffer>>(new Uint8Array());
  const volumeHistoryRef = useRef<number[]>([]);
  const randomWaveformRef = useRef<number[]>([]);
  const lastSmoothedVolumeRef = useRef<number>(0);
  const { sourceNode, isPlaying, isAudioContextPostUserInteraction } =
    useContext(LiveRadioContext);

  // Setup ResizeObserver to handle container resize
  useEffect(() => {
    if (!canvasRef.current) return;

    // Get the parent element to observe
    const parentElement = canvasRef.current.parentElement;
    if (!parentElement) return;

    const resizeObserver = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        const canvas = canvasRef.current;
        if (!canvas) return;

        // Update canvas size with device pixel ratio
        const dpr = window.devicePixelRatio || 1;
        canvas.width = width * dpr;
        canvas.height = height * dpr;

        // Scale the canvas back down using CSS
        canvas.style.width = `${width}px`;
        canvas.style.height = `${height}px`;
      }
    });

    resizeObserver.observe(parentElement);

    // Also set initial size
    const rect = parentElement.getBoundingClientRect();
    const dpr = window.devicePixelRatio || 1;
    canvasRef.current.width = rect.width * dpr;
    canvasRef.current.height = rect.height * dpr;
    canvasRef.current.style.width = `${rect.width}px`;
    canvasRef.current.style.height = `${rect.height}px`;

    return () => {
      resizeObserver.disconnect();
    };
  }, []);

  const calculateVolume = useCallback((timeData: Uint8Array): number => {
    // Check if we have valid data
    if (!timeData || timeData.length === 0) {
      return 0;
    }

    let sum = 0;
    let validSamples = 0;

    for (let i = 0; i < timeData.length; i++) {
      // Check if the value is in the expected range (0-255 for byte data)
      if (timeData[i] >= 0 && timeData[i] <= 255) {
        const normalized = (timeData[i] - 128) / 128; // Convert from 0-255 to -1 to 1
        sum += normalized * normalized; // RMS calculation
        validSamples++;
      }
    }

    // If we don't have enough valid samples, return 0
    if (validSamples < timeData.length * 0.5) {
      return 0;
    }

    const rms = Math.sqrt(sum / validSamples);

    // Clamp the result to prevent unexpected values
    return Math.max(0, Math.min(1, rms));
  }, []);
  // Setup audio analysis
  useEffect(() => {
    if (!sourceNode.current) return;

    // Resume AudioContext if suspended (common in production)
    if (audioContext.state === 'suspended') {
      audioContext.resume().catch(console.error);
    }

    // Clean up existing connections
    if (analyserRef.current) {
      try {
        analyserRef.current.disconnect();
      } catch (e) {
        // Ignore disconnection errors
      }
    }

    try {
      // Create analyser if it doesn't exist
      if (!analyserRef.current) {
        analyserRef.current = audioContext.createAnalyser();
        analyserRef.current.fftSize = 256;
        analyserRef.current.smoothingTimeConstant = 0.8;
      }
      // Initialize time data array
      timeDataRef.current = new Uint8Array(
        analyserRef.current.frequencyBinCount || 128
      );

      // Connect nodes
      sourceNode.current.connect(analyserRef.current);
    } catch (error) {
      console.error('Error setting up audio analysis:', error);
    }

    return () => {
      // Cleanup on unmount
      if (analyserRef.current) {
        try {
          analyserRef.current.disconnect();
        } catch (e) {
          // Ignore disconnection errors
        }
      }
    };
  }, [isAudioContextPostUserInteraction]);

  // Animation frame for drawing
  useAnimationFrame(() => {
    if (!canvasRef.current) return;
    const ctx = canvasRef.current.getContext('2d');
    if (!ctx) return;

    // Check if audio is ready and playing
    if (!isPlaying || !analyserRef.current) {
      return;
    }

    // Get time domain data
    analyserRef.current?.getByteTimeDomainData(timeDataRef.current);
    if (!timeDataRef.current) {
      return;
    }

    // Get the current canvas size
    const dpr = window.devicePixelRatio || 1;
    const width = canvasRef.current.width / dpr;
    const height = canvasRef.current.height / dpr;

    // Save the current context state
    ctx.save();

    // Scale for device pixel ratio
    ctx.scale(dpr, dpr);

    // Clear the canvas
    ctx.clearRect(0, 0, width, height);

    const rawVolume = calculateVolume(timeDataRef.current);

    // Validate the raw volume
    const validatedVolume =
      isNaN(rawVolume) || !isFinite(rawVolume) ? 0 : rawVolume;

    // Apply smoothing to volume for smoother transitions
    const smoothingFactor = 0.75; // Adjust between 0 (no smoothing) and 1 (heavy smoothing)
    const smoothedVolume =
      lastSmoothedVolumeRef.current * smoothingFactor +
      validatedVolume * (1 - smoothingFactor);
    lastSmoothedVolumeRef.current = smoothedVolume;

    // Only add to history if it's a valid value
    if (!isNaN(smoothedVolume) && isFinite(smoothedVolume)) {
      volumeHistoryRef.current.push(smoothedVolume);
    } else {
      // Add a minimal value if invalid
      volumeHistoryRef.current.push(0.05);
    }

    // Calculate number of bars based on container width and target bar width
    const calculatedBars = Math.floor(width / targetBarWidth);
    const numBars = Math.max(minBars, Math.min(maxBars, calculatedBars));
    const halfBars = Math.floor(numBars / 2);

    // Adjust bar width to fit perfectly in the container
    const actualBarWidth = width / numBars;

    // Maintain the volume history at the right size
    while (volumeHistoryRef.current.length > halfBars) {
      volumeHistoryRef.current.shift();
    }

    // Adjust random waveform array size if needed
    while (randomWaveformRef.current.length > halfBars) {
      randomWaveformRef.current.shift();
    }

    const halfWidth = width / 2;

    // LEFT HALF - Actual waveform (white)
    for (let i = 0; i < volumeHistoryRef.current.length; i++) {
      const volume = volumeHistoryRef.current[i];
      const barHeight = volume * height * 2; // Amplify for visibility

      // Position: rightmost of left half is newest, leftmost is oldest
      const x =
        halfWidth -
        (volumeHistoryRef.current.length - i) * actualBarWidth +
        actualBarWidth / 4;
      const y = (height - barHeight) / 2; // Center vertically

      // Add some transparency to older bars for a fade effect
      const age =
        (volumeHistoryRef.current.length - i) / volumeHistoryRef.current.length;
      const alpha = Math.max(0.3, 1 - age * 0.5); // Reduced transparency range for sharper appearance

      ctx.globalAlpha = alpha;
      ctx.beginPath();
      ctx.roundRect(x, y, actualBarWidth / 2, barHeight, 10);
      ctx.fillStyle = 'rgba(255, 255, 255, 1)'; // Full opacity for sharper rendering
      ctx.fill();
    }

    // RIGHT HALF - Smooth random rainbow waveform
    // Get the height of the rightmost bar from the left half to connect smoothly
    const rightmostVolume =
      volumeHistoryRef.current.length > 0
        ? volumeHistoryRef.current[volumeHistoryRef.current.length - 1]
        : 0.2; // Default fallback

    // Initialize or update random waveform
    if (randomWaveformRef.current.length === 0) {
      // Initialize with the connecting height
      randomWaveformRef.current = [rightmostVolume];
    }

    // Generate smooth random values
    const lastValue =
      randomWaveformRef.current[randomWaveformRef.current.length - 1];

    // Create smooth pulsing effect with controlled randomness
    const time = Date.now() * 0.003; // Slow time progression
    const basePulse = Math.sin(time) * 0.3 + 0.5; // Base pulsing between 0.2 and 0.8
    const randomNoise = (Math.random() - 0.5) * 0.1; // Small random variation
    const smoothing = 0.85; // High smoothing for gradual changes

    const newValue =
      lastValue * smoothing + (basePulse + randomNoise) * (1 - smoothing);
    const clampedValue = Math.max(0.02, Math.min(0.9, newValue)); // Reduced minimum height

    randomWaveformRef.current.push(clampedValue);

    if (randomWaveformRef.current.length > halfBars) {
      randomWaveformRef.current.shift();
    }

    // Create gradient influence from audio connection
    // The first bar connects to audio, subsequent bars gradually become more independent
    for (let i = 0; i < randomWaveformRef.current.length; i++) {
      const distanceFromConnection = i; // Distance from the connecting point
      const maxDistance = halfBars - 1;

      // Calculate influence factor: 1.0 at connection point, decreasing to 0.2 at the end
      const influenceFactor = Math.max(
        0.2,
        1.0 - (distanceFromConnection / maxDistance) * 0.8
      );

      if (i === 0) {
        // First bar always matches the audio connection
        randomWaveformRef.current[i] = rightmostVolume;
      } else {
        // Blend the current random value with the audio-influenced value
        const audioInfluencedValue = rightmostVolume * influenceFactor;
        const currentValue = randomWaveformRef.current[i];

        // Gradual transition: more audio influence near connection, less further away
        randomWaveformRef.current[i] =
          currentValue * (1 - influenceFactor * 0.3) +
          audioInfluencedValue * (influenceFactor * 0.3);
      }
    }

    // Draw right half with rainbow colors
    for (let i = 0; i < randomWaveformRef.current.length; i++) {
      const volume = randomWaveformRef.current[i];
      const barHeight = Math.min(volume * height * 2, height * 2 - 50);

      const x = halfWidth + i * actualBarWidth + actualBarWidth / 4;
      const y = (height - barHeight) / 2;

      // Rainbow color: cycle through hue based on position and time
      const hue = ((i / halfBars) * 300 + Date.now() * 0.05) % 360; // Cycle through rainbow
      const saturation = 80; // Slightly more vibrant colors
      const lightness = 60; // Slightly darker for better contrast

      ctx.globalAlpha = 1;
      ctx.beginPath();
      ctx.roundRect(x, y, actualBarWidth / 2, barHeight, 10);
      ctx.fillStyle = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
      ctx.fill();

      // Reduced glow effect for sharper appearance
      ctx.shadowColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
      ctx.shadowBlur = 1; // Reduced from 3 to 1
      ctx.strokeStyle = `hsl(${hue}, ${saturation}%, ${lightness + 10}%)`;
      ctx.lineWidth = 0.5; // Thinner stroke for sharper appearance
      ctx.stroke();
      ctx.shadowBlur = 0; // Reset shadow
    }

    ctx.fillStyle = 'white';

    // VERTICAL PLAYBAR - Draw in center with circular handle and drop shadow
    const centerX = width / 2;
    const playbarHeight = height - 5; // 90% of canvas height
    const playbarY = (height - playbarHeight) / 2; // Center vertically
    ctx.beginPath();
    ctx.roundRect(centerX - 1, playbarY, 2, playbarHeight, 10);
    ctx.fill();
    // Draw the circular handle (white)
    ctx.beginPath();
    ctx.roundRect(centerX - 3, playbarY, 6, 6, 10);
    ctx.fill();

    // Reset alpha and restore context
    ctx.globalAlpha = 1;
    ctx.restore();
  });

  return (
    <canvas
      ref={canvasRef}
      className={className}
      style={{
        display: 'block',
      }}
    />
  );
};

AudioVisualizer.displayName = 'AudioVisualizer';

export default AudioVisualizer;
