'use client';

/**
 * useHybridAudio - Hybrid audio hook combining HTML5 audio + Web Audio API
 *
 * Uses native HTML5 <audio> for playback (no crackling, native streaming)
 * and Web Audio API only for visualization (AnalyserNode).
 *
 * Audio routing:
 *   source -> destination (single path for playback)
 *   source -> analyser (parallel path for visualization only, no output)
 */

import { useCallback, useEffect, useId, useRef, useState } from 'react';
import { useAudioBus } from './useAudioBus';

// =============================================================================
// TYPES
// =============================================================================

export interface UseHybridAudioOptions {
  src: string;
  autoPlay?: boolean;
  initialVolume?: number;
  loop?: boolean;
  crossOrigin?: 'anonymous' | 'use-credentials';
  /** Set to true to opt out of global audio bus (player won't stop others and won't be stopped) */
  excludeFromBus?: boolean;
  onPlay?: () => void;
  onPause?: () => void;
  onEnded?: () => void;
  onTimeUpdate?: (time: number) => void;
  onError?: (error: Error) => void;
  onReady?: () => void;
}

export interface HybridAudioState {
  isReady: boolean;
  isPlaying: boolean;
  currentTime: number;
  duration: number;
  volume: number;
  isMuted: boolean;
  isLooping: boolean;
  buffered: TimeRanges | null;
  error: Error | null;
}

export interface HybridAudioControls {
  play: () => Promise<void>;
  pause: () => void;
  togglePlay: () => void;
  seek: (time: number) => void;
  seekTo: (progress: number) => void;
  skip: (seconds: number) => void;
  setVolume: (vol: number) => void;
  toggleMute: () => void;
  toggleLoop: () => void;
  setLoop: (enabled: boolean) => void;
  restart: () => void;
}

export interface HybridWebAudioAPI {
  context: AudioContext | null;
  analyser: AnalyserNode | null;
  sourceNode: MediaElementAudioSourceNode | null;
}

export interface UseHybridAudioReturn {
  audioRef: React.RefObject<HTMLAudioElement | null>;
  state: HybridAudioState;
  controls: HybridAudioControls;
  webAudio: HybridWebAudioAPI;
}

// =============================================================================
// HOOK
// =============================================================================

export function useHybridAudio(options: UseHybridAudioOptions): UseHybridAudioReturn {
  const {
    src,
    autoPlay = false,
    initialVolume = 1,
    loop = false,
    crossOrigin = 'anonymous',
    excludeFromBus = false,
    onPlay,
    onPause,
    onEnded,
    onTimeUpdate,
    onError,
    onReady,
  } = options;

  const playerId = useId();

  // Refs
  const audioRef = useRef<HTMLAudioElement | null>(null);
  const audioContextRef = useRef<AudioContext | null>(null);
  const sourceNodeRef = useRef<MediaElementAudioSourceNode | null>(null);
  const analyserRef = useRef<AnalyserNode | null>(null);
  const connectedElementRef = useRef<HTMLMediaElement | null>(null);

  // State
  const [state, setState] = useState<HybridAudioState>({
    isReady: false,
    isPlaying: false,
    currentTime: 0,
    duration: 0,
    volume: initialVolume,
    isMuted: false,
    isLooping: loop,
    buffered: null,
    error: null,
  });

  // Initialize Web Audio for visualization (lazy, on first play)
  const initWebAudio = useCallback(() => {
    const audio = audioRef.current;
    if (!audio) return;

    // Already connected to this element
    if (connectedElementRef.current === audio && audioContextRef.current) {
      return;
    }

    try {
      // Create AudioContext if needed
      if (!audioContextRef.current) {
        const AudioContextClass =
          window.AudioContext ||
          (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;
        audioContextRef.current = new AudioContextClass();
      }

      const ctx = audioContextRef.current;

      // Disconnect old source if any
      if (sourceNodeRef.current) {
        try {
          sourceNodeRef.current.disconnect();
        } catch {
          /* ignore */
        }
      }

      // Create source from audio element
      const source = ctx.createMediaElementSource(audio);
      sourceNodeRef.current = source;

      // Create analyser for visualization
      const analyser = ctx.createAnalyser();
      analyser.fftSize = 256;
      analyser.smoothingTimeConstant = 0.85;
      analyserRef.current = analyser;

      // Audio routing:
      // 1. source -> destination (for playback)
      // 2. source -> analyser (for visualization only - NO output!)
      source.connect(ctx.destination);
      source.connect(analyser);
      // NOTE: analyser does NOT connect to destination - prevents double audio!

      connectedElementRef.current = audio;
    } catch (error) {
      console.warn('[useHybridAudio] Web Audio init failed:', error);
    }
  }, []);

  // Audio bus — stop other players when this one plays
  const { announce: busAnnounce, release: busRelease } = useAudioBus(
    playerId,
    useCallback(() => {
      if (!excludeFromBus) audioRef.current?.pause();
    }, [excludeFromBus])
  );

  // Resume AudioContext on user interaction
  const resumeAudioContext = useCallback(async () => {
    const ctx = audioContextRef.current;
    if (ctx && ctx.state === 'suspended') {
      await ctx.resume();
    }
  }, []);

  // Controls
  const play = useCallback(async () => {
    const audio = audioRef.current;
    if (!audio) return;

    try {
      if (!excludeFromBus) busAnnounce();
      initWebAudio();
      await resumeAudioContext();
      await audio.play();
    } catch (error) {
      console.error('[useHybridAudio] Play failed:', error);
      onError?.(error as Error);
    }
  }, [excludeFromBus, busAnnounce, initWebAudio, resumeAudioContext, onError]);

  const pause = useCallback(() => {
    audioRef.current?.pause();
  }, []);

  const togglePlay = useCallback(() => {
    if (state.isPlaying) {
      pause();
    } else {
      play();
    }
  }, [state.isPlaying, play, pause]);

  const seek = useCallback(
    (time: number) => {
      const audio = audioRef.current;
      if (audio && isFinite(time)) {
        audio.currentTime = Math.max(0, Math.min(time, state.duration || audio.duration || 0));
      }
    },
    [state.duration]
  );

  const seekTo = useCallback(
    (progress: number) => {
      const duration = state.duration || audioRef.current?.duration || 0;
      seek(duration * Math.max(0, Math.min(1, progress)));
    },
    [state.duration, seek]
  );

  const skip = useCallback(
    (seconds: number) => {
      seek(state.currentTime + seconds);
    },
    [state.currentTime, seek]
  );

  const setVolume = useCallback((vol: number) => {
    const audio = audioRef.current;
    if (audio) {
      const clampedVol = Math.max(0, Math.min(1, vol));
      audio.volume = clampedVol;
      setState((prev) => ({ ...prev, volume: clampedVol }));
    }
  }, []);

  const toggleMute = useCallback(() => {
    const audio = audioRef.current;
    if (audio) {
      audio.muted = !audio.muted;
      setState((prev) => ({ ...prev, isMuted: audio.muted }));
    }
  }, []);

  const toggleLoop = useCallback(() => {
    const audio = audioRef.current;
    if (audio) {
      audio.loop = !audio.loop;
      setState((prev) => ({ ...prev, isLooping: audio.loop }));
    }
  }, []);

  const setLoop = useCallback((enabled: boolean) => {
    const audio = audioRef.current;
    if (audio) {
      audio.loop = enabled;
      setState((prev) => ({ ...prev, isLooping: enabled }));
    }
  }, []);

  const restart = useCallback(() => {
    seek(0);
    play();
  }, [seek, play]);

  const controls: HybridAudioControls = {
    play,
    pause,
    togglePlay,
    seek,
    seekTo,
    skip,
    setVolume,
    toggleMute,
    toggleLoop,
    setLoop,
    restart,
  };

  // Create audio element on mount
  useEffect(() => {
    const audio = document.createElement('audio');
    audio.preload = 'metadata';
    audio.crossOrigin = crossOrigin;
    audio.volume = initialVolume;
    audio.loop = loop;
    audioRef.current = audio;

    return () => {
      audio.pause();
      audio.src = '';
      busRelease();
      if (audioContextRef.current) {
        audioContextRef.current.close().catch(() => {});
      }
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Event handlers
  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;

    const handlers = {
      loadedmetadata: () => {
        setState((prev) => ({
          ...prev,
          duration: audio.duration,
          isReady: true,
        }));
        onReady?.();
      },
      canplay: () => {
        setState((prev) => ({ ...prev, isReady: true }));
        if (autoPlay) {
          play();
        }
      },
      play: () => {
        setState((prev) => ({ ...prev, isPlaying: true }));
        onPlay?.();
      },
      pause: () => {
        setState((prev) => ({ ...prev, isPlaying: false }));
        onPause?.();
      },
      ended: () => {
        setState((prev) => ({ ...prev, isPlaying: false }));
        onEnded?.();
      },
      timeupdate: () => {
        setState((prev) => ({ ...prev, currentTime: audio.currentTime }));
        onTimeUpdate?.(audio.currentTime);
      },
      progress: () => {
        setState((prev) => ({ ...prev, buffered: audio.buffered }));
      },
      error: () => {
        const error = new Error(audio.error?.message || 'Audio error');
        setState((prev) => ({ ...prev, error }));
        onError?.(error);
      },
      volumechange: () => {
        setState((prev) => ({
          ...prev,
          volume: audio.volume,
          isMuted: audio.muted,
        }));
      },
    };

    Object.entries(handlers).forEach(([event, handler]) => {
      audio.addEventListener(event, handler);
    });

    return () => {
      Object.entries(handlers).forEach(([event, handler]) => {
        audio.removeEventListener(event, handler);
      });
    };
  }, [autoPlay, onPlay, onPause, onEnded, onTimeUpdate, onError, onReady, play]);

  // Load new source
  useEffect(() => {
    const audio = audioRef.current;
    if (!audio || !src) return;

    setState((prev) => ({
      ...prev,
      isReady: false,
      currentTime: 0,
      duration: 0,
      error: null,
    }));

    audio.src = src;
    audio.load();
  }, [src]);

  return {
    audioRef,
    state,
    controls,
    webAudio: {
      context: audioContextRef.current,
      analyser: analyserRef.current,
      sourceNode: sourceNodeRef.current,
    },
  };
}
