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

import audioContext from '@/lib/audioContext';

interface UseLiveAudioAnalyzerOptions {
  fftSize?: number;
  smoothingTimeConstant?: number;
  enabled?: boolean;
}

export default function useLiveAudioAnalyzer(
  audioElement: HTMLAudioElement | null,
  options: UseLiveAudioAnalyzerOptions = {}
) {
  const {
    fftSize = 2048,
    smoothingTimeConstant = 0.8,
    enabled = true,
  } = options;

  const [isAnalyzing, setIsAnalyzing] = useState(false);
  const analyserNodeRef = useRef<AnalyserNode | null>(null);
  const sourceNodeRef = useRef<MediaElementAudioSourceNode | null>(null);
  const frequencyDataRef = useRef<Uint8Array<ArrayBuffer> | null>(null);
  const timeDataRef = useRef<Uint8Array<ArrayBuffer> | null>(null);
  const connectedElementRef = useRef<HTMLAudioElement | null>(null);

  // Complete cleanup function
  const completeCleanup = useCallback(() => {
    if (analyserNodeRef.current) {
      try {
        analyserNodeRef.current.disconnect();
      } catch (e) {
        // Ignore disconnect errors
      }
      analyserNodeRef.current = null;
    }

    if (sourceNodeRef.current) {
      try {
        sourceNodeRef.current.disconnect();
      } catch (e) {
        // Ignore disconnect errors
      }
      sourceNodeRef.current = null;
    }

    connectedElementRef.current = null;
    frequencyDataRef.current = null;
    timeDataRef.current = null;
    setIsAnalyzing(false);
  }, []);

  // Initialize analyzer
  const initializeAnalyzer = useCallback(() => {
    if (!audioElement || !enabled) return;

    try {
      // Create analyzer node
      const analyser = audioContext.createAnalyser();
      analyser.fftSize = fftSize;
      analyser.smoothingTimeConstant = smoothingTimeConstant;

      // Create media element source if we don't have one for this element
      let source = sourceNodeRef.current;
      if (!source || connectedElementRef.current !== audioElement) {
        // Clean up any existing source first
        if (sourceNodeRef.current) {
          try {
            sourceNodeRef.current.disconnect();
          } catch (e) {
            // Ignore disconnect errors
          }
        }

        try {
          // Create new MediaElementSourceNode
          source = audioContext.createMediaElementSource(audioElement);
          sourceNodeRef.current = source;
          connectedElementRef.current = audioElement;
        } catch (sourceError) {
          // If audio element is already connected, we can't use Web Audio API analysis
          console.warn(
            'Audio element already connected to Web Audio API, skipping waveform analysis:',
            sourceError
          );
          setIsAnalyzing(false);
          return;
        }
      }

      // Always disconnect existing analyzer to avoid multiple connections
      if (analyserNodeRef.current) {
        try {
          analyserNodeRef.current.disconnect();
        } catch (e) {
          // Ignore disconnect errors
        }
      }

      // Connect source to new analyzer, and analyzer to destination
      source.connect(analyser);
      analyser.connect(audioContext.destination);

      analyserNodeRef.current = analyser;

      // Initialize data arrays
      const bufferLength = analyser.frequencyBinCount;
      frequencyDataRef.current = new Uint8Array(new ArrayBuffer(bufferLength));
      timeDataRef.current = new Uint8Array(new ArrayBuffer(bufferLength));

      setIsAnalyzing(true);
    } catch (error) {
      console.error('Failed to initialize audio analyzer:', error);
      setIsAnalyzing(false);
    }
  }, [audioElement, enabled, fftSize, smoothingTimeConstant]);

  // Clean up analyzer
  const cleanupAnalyzer = useCallback(() => {
    if (analyserNodeRef.current) {
      try {
        analyserNodeRef.current.disconnect();
      } catch (e) {
        // Ignore disconnect errors
      }
      analyserNodeRef.current = null;
    }

    // Keep the source node connected for audio playback continuity
    // Only fully clean up when component unmounts or audio element changes

    frequencyDataRef.current = null;
    timeDataRef.current = null;
    setIsAnalyzing(false);
  }, []);

  // Get frequency data (for bar visualizations)
  const getFrequencyData = useCallback((): Uint8Array | null => {
    if (!analyserNodeRef.current || !frequencyDataRef.current) return null;

    analyserNodeRef.current.getByteFrequencyData(frequencyDataRef.current);
    return frequencyDataRef.current;
  }, []);

  // Get time domain data (for waveform visualizations)
  const getTimeData = useCallback((): Uint8Array | null => {
    if (!analyserNodeRef.current || !timeDataRef.current) return null;

    analyserNodeRef.current.getByteTimeDomainData(timeDataRef.current);
    return timeDataRef.current;
  }, []);

  // Get both frequency and time data
  const getAnalysisData = useCallback(() => {
    return {
      frequency: getFrequencyData(),
      time: getTimeData(),
      bufferLength: frequencyDataRef.current?.length || 0,
    };
  }, [getFrequencyData, getTimeData]);

  // Initialize when audio element changes
  useEffect(() => {
    // Clear everything when audio element changes
    if (connectedElementRef.current !== audioElement) {
      completeCleanup();
    }

    if (audioElement && enabled) {
      initializeAnalyzer();
    } else {
      cleanupAnalyzer();
    }

    // Cleanup function for when audio element changes
    return () => {
      if (analyserNodeRef.current) {
        try {
          analyserNodeRef.current.disconnect();
        } catch (e) {
          // Ignore disconnect errors
        }
        analyserNodeRef.current = null;
      }
    };
  }, [
    audioElement,
    enabled,
    initializeAnalyzer,
    cleanupAnalyzer,
    completeCleanup,
  ]);

  // Complete cleanup when component unmounts
  useEffect(() => {
    return completeCleanup;
  }, [completeCleanup]);

  // Resume audio context if needed
  useEffect(() => {
    if (isAnalyzing && audioContext.state === 'suspended') {
      audioContext.resume();
    }
  }, [isAnalyzing]);

  return {
    isAnalyzing,
    getFrequencyData,
    getTimeData,
    getAnalysisData,
    analyzerNode: analyserNodeRef.current,
  };
}
