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

interface AudioAnalyzerContextType {
  analyzerNode: AnalyserNode | null;
  audioContext: AudioContext | null;
  connectAudioElement: (element: HTMLAudioElement) => void;
  disconnectAudioElement: (element: HTMLAudioElement) => void;
  connectOscillator: (oscillator: OscillatorNode, gainNode: GainNode) => void;
  isReady: boolean;
}

const AudioAnalyzerContext = createContext<AudioAnalyzerContextType | undefined>(undefined);

export const AudioAnalyzerProvider = ({ children }: { children: ReactNode }) => {
  const audioContextRef = useRef<AudioContext | null>(null);
  const analyzerRef = useRef<AnalyserNode | null>(null);
  const sourceMapRef = useRef<Map<HTMLAudioElement, MediaElementAudioSourceNode>>(new Map());
  const [isReady, setIsReady] = useState(false);
  const [, forceUpdate] = useState(0);

  // Initialize audio context - call immediately but handle suspended state
  const initAudioContext = useCallback(() => {
    if (audioContextRef.current) return audioContextRef.current;

    try {
      const ctx = new (window.AudioContext || (window as any).webkitAudioContext)();
      audioContextRef.current = ctx;

      // Create analyzer node
      const analyzer = ctx.createAnalyser();
      analyzer.fftSize = 512;
      analyzer.smoothingTimeConstant = 0.8;
      analyzer.connect(ctx.destination);
      analyzerRef.current = analyzer;

      setIsReady(true);
      forceUpdate(n => n + 1); // Force re-render to update context value
      return ctx;
    } catch (e) {
      console.error('Failed to create AudioContext:', e);
      return null;
    }
  }, []);

  // Initialize immediately on mount
  useEffect(() => {
    initAudioContext();
  }, [initAudioContext]);

  // Connect an audio element to the analyzer
  const connectAudioElement = useCallback((element: HTMLAudioElement) => {
    const ctx = initAudioContext();
    if (!ctx || !analyzerRef.current) return;

    // Already connected?
    if (sourceMapRef.current.has(element)) return;

    try {
      // Resume context if suspended
      if (ctx.state === 'suspended') {
        ctx.resume();
      }

      const source = ctx.createMediaElementSource(element);
      source.connect(analyzerRef.current);
      sourceMapRef.current.set(element, source);
      console.log('Connected audio element to analyzer');
    } catch (e) {
      // Element might already be connected to a different context
      console.log('Could not connect audio element:', e);
    }
  }, [initAudioContext]);

  // Disconnect an audio element
  const disconnectAudioElement = useCallback((element: HTMLAudioElement) => {
    const source = sourceMapRef.current.get(element);
    if (source) {
      try {
        source.disconnect();
      } catch (e) {
        // Ignore
      }
      sourceMapRef.current.delete(element);
    }
  }, []);

  // Connect oscillator (for sound effects) to analyzer
  const connectOscillator = useCallback((oscillator: OscillatorNode, gainNode: GainNode) => {
    if (!analyzerRef.current) return;
    
    // Route through analyzer instead of direct to destination
    gainNode.disconnect();
    gainNode.connect(analyzerRef.current);
  }, []);

  // Cleanup on unmount
  useEffect(() => {
    return () => {
      sourceMapRef.current.forEach((source) => {
        try {
          source.disconnect();
        } catch (e) {
          // Ignore
        }
      });
      sourceMapRef.current.clear();
      
      if (audioContextRef.current) {
        audioContextRef.current.close();
        audioContextRef.current = null;
      }
    };
  }, []);

  return (
    <AudioAnalyzerContext.Provider
      value={{
        analyzerNode: analyzerRef.current,
        audioContext: audioContextRef.current,
        connectAudioElement,
        disconnectAudioElement,
        connectOscillator,
        isReady,
      }}
    >
      {children}
    </AudioAnalyzerContext.Provider>
  );
};

export const useAudioAnalyzer = () => {
  const context = useContext(AudioAnalyzerContext);
  if (context === undefined) {
    throw new Error('useAudioAnalyzer must be used within an AudioAnalyzerProvider');
  }
  return context;
};
