import { useCurrentVoiceChannelId } from '@/features/server/channels/hooks';
import { playSound } from '@/features/server/sounds/actions';
import { SoundType } from '@/features/server/types';
import { logVoice } from '@/helpers/browser-logger';
import { getResWidthHeight } from '@/helpers/get-res-with-height';
import { getTRPCClient } from '@/lib/trpc';
import { StreamKind } from '@pulse/shared';
import { Device } from 'mediasoup-client';
import type { RtpCapabilities } from 'mediasoup-client/types';
import {
  createContext,
  memo,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState
} from 'react';
import { useDevices } from '../devices-provider/hooks/use-devices';
import type { TDeviceSettings } from '@/types';
import { FloatingPinnedCard } from './floating-pinned-card';
import { useLocalStreams } from './hooks/use-local-streams';
import { useRemoteStreams } from './hooks/use-remote-streams';
import {
  useTransportStats,
  type TransportStatsData
} from './hooks/use-transport-stats';
import { useTransports } from './hooks/use-transports';
import { useVoiceControls } from './hooks/use-voice-controls';
import { useVoiceEvents } from './hooks/use-voice-events';
import { VolumeControlProvider } from './volume-control-context';
import { DeepFilterNet3Core } from 'deepfilternet3-noise-filter';
import { NoiseSuppressorWorklet_Name } from '@timephy/rnnoise-wasm';
import NoiseSuppressorWorklet from '@timephy/rnnoise-wasm/NoiseSuppressorWorklet?worker&url';
import KeyboardNoiseGateWorklet from '@/worklets/keyboard-noise-gate-worklet?worker&url';

const DEEPFILTER_CDN_ORIGIN = 'https://cdn.mezon.ai';
const DEEPFILTER_CDN_PATH_PREFIX =
  '/AI/models/datas/noise_suppression/deepfilternet3/';
const DEEPFILTER_LOCAL_ASSETS_PREFIX = '/deepfilter-assets/';
const DEEPFILTER_LOCAL_CDN_URL = `${window.location.origin}${DEEPFILTER_LOCAL_ASSETS_PREFIX}AI/models/datas/noise_suppression/deepfilternet3`;

const rewriteDeepFilterFetchUrl = (url: string): string => {
  try {
    const parsed = new URL(url);
    if (
      parsed.origin === DEEPFILTER_CDN_ORIGIN &&
      parsed.pathname.startsWith(DEEPFILTER_CDN_PATH_PREFIX)
    ) {
      return `${DEEPFILTER_LOCAL_ASSETS_PREFIX}${parsed.pathname.slice(1)}${parsed.search}`;
    }
  } catch {
    // ignore parse errors and keep original URL
  }
  return url;
};

const withDeepFilterFetchProxy = async <T,>(fn: () => Promise<T>): Promise<T> => {
  const originalFetch = globalThis.fetch.bind(globalThis);
  const proxiedFetch: typeof fetch = (input, init) => {
    if (typeof input === 'string') {
      return originalFetch(rewriteDeepFilterFetchUrl(input), init);
    }
    if (input instanceof URL) {
      return originalFetch(rewriteDeepFilterFetchUrl(input.toString()), init);
    }
    if (input instanceof Request) {
      return originalFetch(
        new Request(rewriteDeepFilterFetchUrl(input.url), input),
        init
      );
    }
    return originalFetch(input, init);
  };

  globalThis.fetch = proxiedFetch;
  try {
    return await fn();
  } finally {
    globalThis.fetch = originalFetch;
  }
};

const sensitivityToThreshold = (sensitivity: number): number => {
  const clamped = Math.min(100, Math.max(0, sensitivity));
  // Higher sensitivity => lower threshold (easier voice pickup).
  return 0.05 - (clamped / 100) * 0.04;
};

const sensitivityToDeepFilterLevel = (sensitivity: number): number => {
  const clamped = Math.min(100, Math.max(0, sensitivity));
  // Higher sensitivity keeps more quiet speech (less suppression).
  return Math.round(Math.min(95, Math.max(10, 100 - clamped)));
};

const getConfiguredMicChain = (devices: TDeviceSettings): string => {
  const chain: string[] = [];
  if (devices.noiseSuppressionDeepFilterNet) chain.push('DeepFilterNet');
  if (devices.noiseSuppressionRnnoise) chain.push('RNNoise');
  if (devices.keyboardSuppression) chain.push('Keyboard Gate');
  if (devices.noiseSuppressionEnhanced) chain.push('Enhanced (browser)');
  if (
    !devices.noiseSuppressionDeepFilterNet &&
    !devices.noiseSuppressionRnnoise &&
    (devices.echoCancellation || devices.noiseSuppression || devices.autoGainControl)
  ) {
    chain.push('Browser DSP');
  }
  return chain.length ? chain.join(' + ') : 'none';
};

const buildMicConstraints = (devices: TDeviceSettings): MediaTrackConstraints => {
  const enhanced = !!devices.noiseSuppressionEnhanced;
  const rnnoise = !!devices.noiseSuppressionRnnoise;
  const deepFilterNet = !!devices.noiseSuppressionDeepFilterNet;
  const keyboardSuppression = !!devices.keyboardSuppression;
  const processed = enhanced || rnnoise || deepFilterNet || keyboardSuppression;
  const hasWasmSuppression = rnnoise || deepFilterNet;

  const constraints: MediaTrackConstraints = {
    deviceId: devices.microphoneId
      ? { exact: devices.microphoneId }
      : undefined,
    autoGainControl: hasWasmSuppression ? false : devices.autoGainControl,
    echoCancellation: hasWasmSuppression ? false : devices.echoCancellation,
    noiseSuppression: hasWasmSuppression
      ? false
      : enhanced
        ? true
        : devices.noiseSuppression,
    sampleRate: 48000,
    channelCount: processed ? 1 : 2
  };

  if (enhanced) {
    const legacy = constraints as unknown as Record<string, unknown>;
    legacy.googHighpassFilter = true;
    legacy.googNoiseSuppression = true;
    legacy.googEchoCancellation = true;
    legacy.googAutoGainControl = true;
    legacy.googTypingNoiseDetection = true;
  }

  return constraints;
};

type AudioVideoRefs = {
  videoRef: React.RefObject<HTMLVideoElement | null>;
  audioRef: React.RefObject<HTMLAudioElement | null>;
  screenShareRef: React.RefObject<HTMLVideoElement | null>;
  screenShareAudioRef: React.RefObject<HTMLAudioElement | null>;
  externalAudioRef: React.RefObject<HTMLAudioElement | null>;
  externalVideoRef: React.RefObject<HTMLVideoElement | null>;
};

export type { AudioVideoRefs };

enum ConnectionStatus {
  DISCONNECTED = 'disconnected',
  CONNECTING = 'connecting',
  CONNECTED = 'connected',
  FAILED = 'failed'
}

export type TVoiceProvider = {
  loading: boolean;
  connectionStatus: ConnectionStatus;
  transportStats: TransportStatsData;
  sharingSystemAudio: boolean;
  realOutputSinkId: string | undefined;
  audioVideoRefsMap: Map<number, AudioVideoRefs>;
  getOrCreateRefs: (remoteId: number) => AudioVideoRefs;
  init: (
    routerRtpCapabilities: RtpCapabilities,
    channelId: number
  ) => Promise<void>;
  micProcessingStatus: {
    active: boolean;
    chain: string;
    note: string;
  };
} & Pick<
  ReturnType<typeof useLocalStreams>,
  'localAudioStream' | 'localVideoStream' | 'localScreenShareStream'
> &
  Pick<
    ReturnType<typeof useRemoteStreams>,
    'remoteUserStreams' | 'externalStreams'
  > &
  ReturnType<typeof useVoiceControls>;

const VoiceProviderContext = createContext<TVoiceProvider>({
  loading: false,
  connectionStatus: ConnectionStatus.DISCONNECTED,
  transportStats: {
    producer: null,
    consumer: null,
    totalBytesReceived: 0,
    totalBytesSent: 0,
    isMonitoring: false,
    currentBitrateReceived: 0,
    currentBitrateSent: 0,
    averageBitrateReceived: 0,
    averageBitrateSent: 0
  },
  audioVideoRefsMap: new Map(),
  getOrCreateRefs: () => ({
    videoRef: { current: null },
    audioRef: { current: null },
    screenShareRef: { current: null },
    screenShareAudioRef: { current: null },
    externalAudioRef: { current: null },
    externalVideoRef: { current: null }
  }),
  init: () => Promise.resolve(),
  micProcessingStatus: {
    active: false,
    chain: 'none',
    note: 'Runtime status unavailable on this screen'
  },
  toggleMic: () => Promise.resolve(),
  toggleSound: () => Promise.resolve(),
  toggleWebcam: () => Promise.resolve(),
  toggleScreenShare: () => Promise.resolve(),
  updateSavedMicTrack: () => {},
  ownVoiceState: {
    micMuted: false,
    soundMuted: false,
    webcamEnabled: false,
    sharingScreen: false
  },
  sharingSystemAudio: false,
  realOutputSinkId: undefined,
  localAudioStream: undefined,
  localVideoStream: undefined,
  localScreenShareStream: undefined,

  remoteUserStreams: {},
  externalStreams: {}
});

type TVoiceProviderProps = {
  children: React.ReactNode;
};

const VoiceProvider = memo(({ children }: TVoiceProviderProps) => {
  const [loading, setLoading] = useState(false);
  const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>(
    ConnectionStatus.DISCONNECTED
  );
  const [sharingSystemAudio, setSharingSystemAudio] = useState(false);
  const [realOutputSinkId, setRealOutputSinkId] = useState<string | undefined>(undefined);
  const routerRtpCapabilities = useRef<RtpCapabilities | null>(null);
  const audioVideoRefsMap = useRef<Map<number, AudioVideoRefs>>(new Map());
  const { devices } = useDevices();

  const [micProcessingStatus, setMicProcessingStatus] = useState<{
    active: boolean;
    chain: string;
    note: string;
  }>({
    active: false,
    chain: getConfiguredMicChain(devices),
    note: 'Configured; starts when mic stream starts'
  });

  useEffect(() => {
    try {
      const payload = {
        ...micProcessingStatus,
        updatedAt: Date.now()
      };
      localStorage.setItem('pulse.micProcessingStatus', JSON.stringify(payload));
      window.dispatchEvent(
        new CustomEvent('pulse:mic-processing-status', { detail: payload })
      );
    } catch {
      // ignore storage/event failures
    }
  }, [micProcessingStatus]);

  const getOrCreateRefs = useCallback((remoteId: number): AudioVideoRefs => {
    if (!audioVideoRefsMap.current.has(remoteId)) {
      audioVideoRefsMap.current.set(remoteId, {
        videoRef: { current: null },
        audioRef: { current: null },
        screenShareRef: { current: null },
        screenShareAudioRef: { current: null },
        externalAudioRef: { current: null },
        externalVideoRef: { current: null }
      });
    }

    return audioVideoRefsMap.current.get(remoteId)!;
  }, []);

  const {
    addExternalStreamTrack,
    removeExternalStreamTrack,
    removeExternalStream,
    clearExternalStreams,
    addRemoteUserStream,
    removeRemoteUserStream,
    clearRemoteUserStreamsForUser,
    clearRemoteUserStreams,
    externalStreams,
    remoteUserStreams
  } = useRemoteStreams();

  const {
    localAudioProducer,
    localVideoProducer,
    localAudioStream,
    localVideoStream,
    localScreenShareStream,
    localScreenShareProducer,
    localScreenShareAudioProducer,
    setLocalAudioStream,
    setLocalVideoStream,
    setLocalScreenShare,
    clearLocalStreams
  } = useLocalStreams();

  const {
    producerTransport,
    consumerTransport,
    createProducerTransport,
    createConsumerTransport,
    consume,
    consumeExistingProducers,
    cleanupTransports
  } = useTransports({
    addExternalStreamTrack,
    removeExternalStreamTrack,
    addRemoteUserStream,
    removeRemoteUserStream
  });

  const {
    stats: transportStats,
    startMonitoring,
    stopMonitoring,
    resetStats
  } = useTransportStats();

  type AudioProcessingState = {
    ctx: AudioContext;
    source: MediaStreamAudioSourceNode;
    deepFilterNode?: AudioWorkletNode;
    deepFilterCore?: DeepFilterNet3Core;
    rnnoiseNode?: AudioWorkletNode;
    gateNode?: AudioWorkletNode;
    dest: MediaStreamAudioDestinationNode;
    input: MediaStream;
  };

  const audioProcessingRef = useRef<AudioProcessingState | null>(null);
  const rawMicStreamRef = useRef<MediaStream | null>(null);

  const cleanupAudioProcessing = useCallback(() => {
    if (!audioProcessingRef.current) return;
    try {
      audioProcessingRef.current.gateNode?.disconnect();
      audioProcessingRef.current.rnnoiseNode?.disconnect();
      audioProcessingRef.current.deepFilterNode?.disconnect();
      audioProcessingRef.current.source.disconnect();
      audioProcessingRef.current.dest.disconnect();
      audioProcessingRef.current.deepFilterCore?.destroy?.();
      audioProcessingRef.current.ctx.close();
    } catch {
      // ignore
    }
    audioProcessingRef.current = null;
    setMicProcessingStatus({
      active: false,
      chain: getConfiguredMicChain(devices),
      note: 'Configured; starts when mic stream starts'
    });
  }, [devices]);

  const stopRawMic = useCallback(() => {
    rawMicStreamRef.current?.getTracks().forEach((t) => t.stop());
    rawMicStreamRef.current = null;
  }, []);

  const applyAudioProcessing = useCallback(async (input: MediaStream): Promise<MediaStream> => {
    const useDeepFilterNet = !!devices.noiseSuppressionDeepFilterNet;
    const useRnnoise = !!devices.noiseSuppressionRnnoise;
    const useKeyboardSuppression = !!devices.keyboardSuppression;

    if (!useDeepFilterNet && !useRnnoise && !useKeyboardSuppression) {
      cleanupAudioProcessing();
      return input;
    }

    if (typeof AudioWorkletNode === 'undefined') {
      logVoice('AudioWorklet unavailable; skipping client-side audio processing');
      setMicProcessingStatus({
        active: false,
        chain: 'none',
        note: 'AudioWorklet unavailable; using raw mic'
      });
      return input;
    }

    cleanupAudioProcessing();

    const ctx = new AudioContext({ sampleRate: 48000 });
    const source = ctx.createMediaStreamSource(input);
    const dest = ctx.createMediaStreamDestination();

    let current: AudioNode = source;
    let deepFilterNode: AudioWorkletNode | undefined;
    let deepFilterCore: DeepFilterNet3Core | undefined;
    let rnnoiseNode: AudioWorkletNode | undefined;
    let gateNode: AudioWorkletNode | undefined;

    const cleanupLocalProcessing = async () => {
      try {
        gateNode?.disconnect();
        rnnoiseNode?.disconnect();
        deepFilterNode?.disconnect();
        source.disconnect();
        dest.disconnect();
        deepFilterCore?.destroy?.();
      } catch {
        // ignore cleanup failures
      }

      try {
        if (ctx.state !== 'closed') {
          await ctx.close();
        }
      } catch {
        // ignore context close failures
      }
    };

    try {
      if (useDeepFilterNet) {
        const deepFilterSuppression = sensitivityToDeepFilterLevel(
          devices.voiceSensitivity ?? 70
        );
        await withDeepFilterFetchProxy(async () => {
          deepFilterCore = new DeepFilterNet3Core({
            sampleRate: 48000,
            noiseReductionLevel: deepFilterSuppression,
            assetConfig: {
              cdnUrl: DEEPFILTER_LOCAL_CDN_URL
            }
          });
          await deepFilterCore.initialize();
          deepFilterNode = await deepFilterCore.createAudioWorkletNode(ctx);
        });
        if (!deepFilterNode) {
          throw new Error('DeepFilterNet worklet node was not created');
        }
        // Apply again after node creation; pre-node setSuppressionLevel is a no-op.
        deepFilterCore?.setSuppressionLevel(deepFilterSuppression);
        current.connect(deepFilterNode);
        current = deepFilterNode;
      }

      if (useRnnoise) {
        await ctx.audioWorklet.addModule(NoiseSuppressorWorklet);
        rnnoiseNode = new AudioWorkletNode(ctx, NoiseSuppressorWorklet_Name);
        current.connect(rnnoiseNode);
        current = rnnoiseNode;
      }

      if (useKeyboardSuppression) {
        await ctx.audioWorklet.addModule(KeyboardNoiseGateWorklet);
        gateNode = new AudioWorkletNode(ctx, 'keyboard-noise-gate-processor', {
          parameterData: {
            threshold: sensitivityToThreshold(devices.voiceSensitivity ?? 70),
            floor: 0.04,
            attack: 0.65,
            release: 0.985
          }
        });
        current.connect(gateNode);
        current = gateNode;
      }

      current.connect(dest);

      audioProcessingRef.current = {
        ctx,
        source,
        deepFilterNode,
        deepFilterCore,
        rnnoiseNode,
        gateNode,
        dest,
        input
      };

      const chain = [
        useDeepFilterNet ? 'DeepFilterNet' : '',
        useRnnoise ? 'RNNoise' : '',
        useKeyboardSuppression ? 'Keyboard Gate' : ''
      ]
        .filter(Boolean)
        .join(' + ');

      setMicProcessingStatus({
        active: true,
        chain: chain || 'none',
        note: 'Client-side processing active'
      });

      return dest.stream;
    } catch (err) {
      await cleanupLocalProcessing();
      throw err;
    }
  }, [devices.noiseSuppressionDeepFilterNet, devices.noiseSuppressionRnnoise, devices.keyboardSuppression, devices.voiceSensitivity, cleanupAudioProcessing]);

  const acquireMicStream = useCallback(async (): Promise<{ stream: MediaStream; track: MediaStreamTrack; raw: MediaStream }> => {
    const raw = await navigator.mediaDevices.getUserMedia({
      audio: buildMicConstraints(devices),
      video: false
    });

    let stream = raw;
    if (devices.noiseSuppressionDeepFilterNet || devices.noiseSuppressionRnnoise || devices.keyboardSuppression) {
      try {
        stream = await applyAudioProcessing(raw);
      } catch (err) {
        const errorMessage = err instanceof Error ? err.message : String(err);
        console.error('[VOICE-PROVIDER] audio processing init failed', err);
        logVoice('Audio processing failed, falling back to raw mic', { error: err, errorMessage });
        setMicProcessingStatus({
          active: false,
          chain: getConfiguredMicChain(devices),
          note: 'Processing failed: ' + errorMessage
        });
        stream = raw;
      }
    } else {
      cleanupAudioProcessing();
      setMicProcessingStatus({
        active: false,
        chain: 'browser-only',
        note: 'Using browser constraints only'
      });
    }

    const track = stream.getAudioTracks()[0];
    return { stream, track, raw };
  }, [devices, applyAudioProcessing, cleanupAudioProcessing]);

  const startMicStream = useCallback(async () => {
    try {
      logVoice('Starting microphone stream');

      const { stream, track, raw } = await acquireMicStream();

      logVoice('Microphone stream obtained', { stream });

      setLocalAudioStream(stream);

      stopRawMic();
      rawMicStreamRef.current = raw;

      if (track) {
        logVoice('Obtained audio track', { audioTrack: track });

        localAudioProducer.current = await producerTransport.current?.produce({
          track,
          appData: { kind: StreamKind.AUDIO }
        });

        logVoice('Microphone audio producer created', {
          producer: localAudioProducer.current
        });

        localAudioProducer.current?.on('@close', async () => {
          logVoice('Audio producer closed');

          const trpc = getTRPCClient();

          try {
            await trpc.voice.closeProducer.mutate({
              kind: StreamKind.AUDIO
            });
          } catch (error) {
            logVoice('Error closing audio producer', { error });
          }
        });

        track.onended = () => {
          logVoice('Audio track ended, cleaning up microphone');

          stream.getAudioTracks().forEach((streamTrack) => {
            streamTrack.stop();
          });
          localAudioProducer.current?.close();

          stopRawMic();
          cleanupAudioProcessing();
          setLocalAudioStream(undefined);
        };
      } else {
        throw new Error('Failed to obtain audio track from microphone');
      }
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      console.error('[VOICE-PROVIDER] startMicStream failed', error);
      logVoice('Error starting microphone stream', { error, errorMessage });
      setMicProcessingStatus({
        active: false,
        chain: getConfiguredMicChain(devices),
        note: 'Mic start failed: ' + errorMessage
      });
    }
  }, [
    producerTransport,
    setLocalAudioStream,
    localAudioProducer,
    localAudioStream,
    acquireMicStream,
    stopRawMic,
    cleanupAudioProcessing,
    devices
  ]);

  const startWebcamStream = useCallback(async () => {
    try {
      logVoice('Starting webcam stream');

      const stream = await navigator.mediaDevices.getUserMedia({
        audio: false,
        video: {
          deviceId: { ideal: devices?.webcamId },
          frameRate: devices.webcamFramerate,
          ...getResWidthHeight(devices?.webcamResolution)
        }
      });

      logVoice('Webcam stream obtained', { stream });

      setLocalVideoStream(stream);

      const videoTrack = stream.getVideoTracks()[0];

      if (videoTrack) {
        logVoice('Obtained video track', { videoTrack });

        localVideoProducer.current = await producerTransport.current?.produce({
          track: videoTrack,
          appData: { kind: StreamKind.VIDEO }
        });

        logVoice('Webcam video producer created', {
          producer: localVideoProducer.current
        });

        localVideoProducer.current?.on('@close', async () => {
          logVoice('Video producer closed');

          const trpc = getTRPCClient();

          try {
            await trpc.voice.closeProducer.mutate({
              kind: StreamKind.VIDEO
            });
          } catch (error) {
            logVoice('Error closing video producer', { error });
          }
        });

        videoTrack.onended = () => {
          logVoice('Video track ended, cleaning up webcam');

          localVideoStream?.getVideoTracks().forEach((track) => {
            track.stop();
          });
          localVideoProducer.current?.close();

          setLocalVideoStream(undefined);
        };
      } else {
        throw new Error('Failed to obtain video track from webcam');
      }
    } catch (error) {
      logVoice('Error starting webcam stream', { error });
      throw error;
    }
  }, [
    setLocalVideoStream,
    localVideoProducer,
    producerTransport,
    localVideoStream,
    devices.webcamId,
    devices.webcamFramerate,
    devices.webcamResolution
  ]);

  const stopWebcamStream = useCallback(() => {
    logVoice('Stopping webcam stream');

    localVideoStream?.getVideoTracks().forEach((track) => {
      logVoice('Stopping video track', { track });

      track.stop();
      localVideoStream.removeTrack(track);
    });

    localVideoProducer.current?.close();
    localVideoProducer.current = undefined;

    setLocalVideoStream(undefined);
  }, [localVideoStream, setLocalVideoStream, localVideoProducer]);

  const stopScreenShareStream = useCallback(async () => {
    logVoice('Stopping screen share stream');

    localScreenShareStream?.getTracks().forEach((track) => {
      logVoice('Stopping screen share track', { track });

      track.stop();
      localScreenShareStream.removeTrack(track);
    });

    localScreenShareProducer.current?.close();
    localScreenShareProducer.current = undefined;

    localScreenShareAudioProducer.current?.close();
    localScreenShareAudioProducer.current = undefined;

    // Stop macOS system audio capture if active
    window.pulseDesktop?.audioCapture?.stop();

    // Restore the microphone to original settings (echo cancellation was
    // forced ON during system audio capture to prevent acoustic bleed)
    if (sharingSystemAudio && localAudioProducer.current && !localAudioProducer.current.closed) {
      try {
        logVoice('macOS: Restoring mic to original settings');
        const { stream: newMicStream, raw: newRaw } = await acquireMicStream();

        const newMicTrack = newMicStream.getAudioTracks()[0];
        if (newMicTrack) {
          await localAudioProducer.current.replaceTrack({ track: newMicTrack });
          localAudioStream?.getAudioTracks().forEach((t) => t.stop());
          setLocalAudioStream(newMicStream);
          stopRawMic();
          rawMicStreamRef.current = newRaw;
          logVoice('macOS: Mic restored to original settings');
        }
      } catch (err) {
        logVoice('macOS: Failed to restore mic settings', { error: err });
      }
    }

    setLocalScreenShare(undefined);
    setSharingSystemAudio(false);
    setRealOutputSinkId(undefined);
  }, [localScreenShareStream, setLocalScreenShare, localScreenShareProducer, localScreenShareAudioProducer, sharingSystemAudio, localAudioProducer, localAudioStream, setLocalAudioStream, acquireMicStream,
    stopRawMic]);

  const startScreenShareStream = useCallback(async () => {
    try {
      logVoice('Starting screen share stream');

      const stream = await navigator.mediaDevices.getDisplayMedia({
        video: {
          ...getResWidthHeight(devices?.screenResolution),
          frameRate: devices?.screenFramerate
        },
        audio: {
          autoGainControl: false,
          echoCancellation: false,
          noiseSuppression: true,
          channelCount: 2,
          sampleRate: 48000
        },
        // Prevent sharing the app's own tab (major source of audio echo)
        selfBrowserSurface: 'exclude',
        preferCurrentTab: false
      } as DisplayMediaStreamOptions);

      logVoice('Screen share stream obtained', { stream });
      setLocalScreenShare(stream);

      const videoTrack = stream.getVideoTracks()[0];

      if (videoTrack) {
        // Detect if sharing screen/window (system audio) vs tab (isolated audio)
        const displaySurface = videoTrack.getSettings().displaySurface;
        const hasAudio = stream.getAudioTracks().length > 0;
        const isSystemAudio = hasAudio && displaySurface !== 'browser';

        logVoice('Screen share surface type', { displaySurface, hasAudio, isSystemAudio });
        setSharingSystemAudio(isSystemAudio);

        localScreenShareProducer.current =
          await producerTransport.current?.produce({
            track: videoTrack,
            appData: { kind: StreamKind.SCREEN }
          });

        localScreenShareProducer.current?.on('@close', async () => {
          logVoice('Screen share producer closed');

          const trpc = getTRPCClient();

          try {
            await trpc.voice.closeProducer.mutate({
              kind: StreamKind.SCREEN
            });
          } catch (error) {
            logVoice('Error closing screen share producer', { error });
          }
        });

        let audioTrack = stream.getAudioTracks()[0];

        // macOS Electron: use the virtual audio device for system audio capture.
        // Always prefer our HAL plugin over whatever getDisplayMedia returned,
        // because the system picker's audio track (from ScreenCaptureKit) may be
        // silent without the "Screen & System Audio Recording" permission, while
        // our virtual device only needs microphone permission.
        if (window.pulseDesktop?.audioCapture) {
          try {
            const available = await window.pulseDesktop.audioCapture.isAvailable();
            if (available) {
              logVoice('macOS: Starting system audio capture via virtual device');

              // Remove any audio track from getDisplayMedia — we'll replace it
              // with our virtual device capture which is more reliable
              if (audioTrack) {
                logVoice('macOS: Removing system picker audio track in favor of virtual device');
                audioTrack.stop();
                stream.removeTrack(audioTrack);
                audioTrack = undefined as unknown as MediaStreamTrack;
              }

              const captureResult = await window.pulseDesktop.audioCapture.start();

              if (captureResult) {
                // Find the Pulse Audio virtual input device
                const mediaDevices = await navigator.mediaDevices.enumerateDevices();

                logVoice('macOS: Available audio input devices', {
                  devices: mediaDevices
                    .filter((d) => d.kind === 'audioinput')
                    .map((d) => ({ id: d.deviceId, label: d.label }))
                });

                const pulseInput = mediaDevices.find(
                  (d) => d.kind === 'audioinput' && d.label.includes('Pulse Audio')
                );

                if (pulseInput) {
                  logVoice('macOS: Capturing from Pulse Audio device', { deviceId: pulseInput.deviceId });
                  const audioStream = await navigator.mediaDevices.getUserMedia({
                    audio: {
                      deviceId: { exact: pulseInput.deviceId },
                      autoGainControl: false,
                      echoCancellation: false,
                      noiseSuppression: false,
                      channelCount: 2,
                      sampleRate: 48000
                    }
                  });

                  audioTrack = audioStream.getAudioTracks()[0];
                  if (audioTrack) {
                    stream.addTrack(audioTrack);
                    setSharingSystemAudio(true);

                    // Find the real output device by name so we can route voice
                    // chat audio directly to it (bypassing the aggregate device).
                    // This prevents remote users' voices from being re-captured.
                    const realOutput = mediaDevices.find(
                      (d) => d.kind === 'audiooutput' && d.label.includes(captureResult.realOutputDeviceName)
                    );
                    if (realOutput) {
                      logVoice('macOS: Routing voice to real output', { deviceId: realOutput.deviceId, label: realOutput.label });
                      setRealOutputSinkId(realOutput.deviceId);
                    }

                    // Re-acquire the microphone with echo cancellation + noise
                    // suppression forced ON. The aggregate device routes system
                    // audio to the real speakers, and the built-in mic picks it
                    // up acoustically. Without this, the remote user hears the
                    // system audio bleeding through the mic stream.
                    if (localAudioProducer.current && !localAudioProducer.current.closed) {
                      try {
                        // Find the real microphone (exclude the Pulse Audio virtual device)
                        const realMic = mediaDevices.find(
                          (d) => d.kind === 'audioinput' &&
                            !d.label.includes('Pulse Audio') &&
                            (devices.microphoneId ? d.deviceId === devices.microphoneId : true)
                        );

                        if (realMic) {
                          logVoice('macOS: Re-acquiring mic with echo cancellation', { deviceId: realMic.deviceId, label: realMic.label });
                          const newMicStream = await navigator.mediaDevices.getUserMedia({
                            audio: {
                              deviceId: { exact: realMic.deviceId },
                              autoGainControl: true,
                              echoCancellation: true,
                              noiseSuppression: true,
                              sampleRate: 48000,
                              channelCount: 2
                            },
                            video: false
                          });

                          const newMicTrack = newMicStream.getAudioTracks()[0];
                          if (newMicTrack) {
                            await localAudioProducer.current.replaceTrack({ track: newMicTrack });
                            // Stop old tracks and update stream
                            localAudioStream?.getAudioTracks().forEach((t) => t.stop());
                            setLocalAudioStream(newMicStream);
                            logVoice('macOS: Mic re-acquired with echo cancellation enabled');
                          }
                        }
                      } catch (micErr) {
                        logVoice('macOS: Failed to re-acquire mic with echo cancellation', { error: micErr });
                      }
                    }
                  }
                } else {
                  logVoice('macOS: Pulse Audio input device not found, available inputs listed above');
                  window.pulseDesktop.audioCapture.stop();
                }
              }
            } else {
              logVoice('macOS: Audio driver not available, using system audio track if present');
            }
          } catch (err) {
            logVoice('macOS: System audio capture failed', { error: err });
            window.pulseDesktop?.audioCapture?.stop();
          }
        }

        if (audioTrack) {
          logVoice('Obtained screen share audio track', { audioTrack });

          const audioBitrate = (devices.screenAudioBitrate ?? 128) * 1000;

          localScreenShareAudioProducer.current =
            await producerTransport.current?.produce({
              track: audioTrack,
              appData: { kind: StreamKind.SCREEN_AUDIO },
              encodings: [{ maxBitrate: audioBitrate, dtx: false }],
              codecOptions: {
                opusStereo: true,
                opusDtx: false,
                opusFec: true,
                opusMaxPlaybackRate: 48000
              }
            });

          localScreenShareAudioProducer.current?.on('@close', async () => {
            logVoice('Screen share audio producer closed');

            const trpc = getTRPCClient();

            try {
              await trpc.voice.closeProducer.mutate({
                kind: StreamKind.SCREEN_AUDIO
              });
            } catch (error) {
              logVoice('Error closing screen share audio producer', { error });
            }
          });
        }

        videoTrack.onended = () => {
          logVoice('Screen share track ended, cleaning up screen share');

          localScreenShareStream?.getTracks().forEach((track) => {
            track.stop();
          });
          localScreenShareProducer.current?.close();
          localScreenShareAudioProducer.current?.close();

          // Stop macOS system audio capture if active
          window.pulseDesktop?.audioCapture?.stop();

          setLocalScreenShare(undefined);
          setRealOutputSinkId(undefined);
        };

        return videoTrack;
      } else {
        throw new Error('No video track obtained for screen share');
      }
    } catch (error) {
      logVoice('Error starting screen share stream', { error });
      throw error;
    }
  }, [
    setLocalScreenShare,
    localScreenShareProducer,
    localScreenShareAudioProducer,
    producerTransport,
    localScreenShareStream,
    localAudioProducer,
    localAudioStream,
    setLocalAudioStream,
    devices.screenResolution,
    devices.screenFramerate,
    devices.screenAudioBitrate,
    devices.microphoneId
  ]);

  // Hot-swap microphone track on the existing producer when device settings change
  const reapplyMicSettings = useCallback(async (micMuted: boolean, savedMicTrackUpdater: (track: MediaStreamTrack | null) => void) => {
    if (!localAudioProducer.current || localAudioProducer.current.closed) return;

    try {
      logVoice('Reapplying mic settings mid-call');

      const { stream: newStream, track: newTrack, raw: newRaw } =
        await acquireMicStream();
      if (!newTrack) return;

      // Stop old tracks
      localAudioStream?.getAudioTracks().forEach((t) => t.stop());

      if (micMuted) {
        // Mic is muted — update the saved track so unmute uses the new device
        savedMicTrackUpdater(newTrack);
      } else {
        await localAudioProducer.current!.replaceTrack({ track: newTrack });
      }

      setLocalAudioStream(newStream);
      stopRawMic();
      rawMicStreamRef.current = newRaw;
      logVoice('Mic settings reapplied successfully');
    } catch (error) {
      logVoice('Error reapplying mic settings', { error });
    }
  }, [
    localAudioProducer,
    localAudioStream,
    setLocalAudioStream,
    acquireMicStream,
    stopRawMic
  ]);

  // Hot-swap webcam track on the existing producer when device settings change
  const reapplyWebcamSettings = useCallback(async (webcamEnabled: boolean) => {
    if (!webcamEnabled) return;
    if (!localVideoProducer.current || localVideoProducer.current.closed) return;

    try {
      logVoice('Reapplying webcam settings mid-call');

      const newStream = await navigator.mediaDevices.getUserMedia({
        audio: false,
        video: {
          deviceId: { ideal: devices.webcamId },
          frameRate: devices.webcamFramerate,
          ...getResWidthHeight(devices.webcamResolution)
        }
      });

      const newTrack = newStream.getVideoTracks()[0];
      if (!newTrack) return;

      // Stop old tracks
      localVideoStream?.getVideoTracks().forEach((t) => t.stop());

      await localVideoProducer.current!.replaceTrack({ track: newTrack });
      setLocalVideoStream(newStream);
      logVoice('Webcam settings reapplied successfully');
    } catch (error) {
      logVoice('Error reapplying webcam settings', { error });
    }
  }, [
    localVideoProducer,
    localVideoStream,
    setLocalVideoStream,
    devices.webcamId,
    devices.webcamFramerate,
    devices.webcamResolution
  ]);

  const cleanup = useCallback(() => {
    logVoice('Running voice provider cleanup');

    stopMonitoring();
    resetStats();
    clearLocalStreams();
    clearRemoteUserStreams();
    clearExternalStreams();
    cleanupTransports();

    setConnectionStatus(ConnectionStatus.DISCONNECTED);
    stopRawMic();
    cleanupAudioProcessing();
  }, [
    stopMonitoring,
    resetStats,
    clearLocalStreams,
    clearRemoteUserStreams,
    clearExternalStreams,
    cleanupTransports,
    stopRawMic,
    cleanupAudioProcessing
  ]);

  const init = useCallback(
    async (
      incomingRouterRtpCapabilities: RtpCapabilities,
      channelId: number
    ) => {
      logVoice('Initializing voice provider', {
        incomingRouterRtpCapabilities,
        channelId
      });

      cleanup();

      try {
        setLoading(true);
        setConnectionStatus(ConnectionStatus.CONNECTING);

        routerRtpCapabilities.current = incomingRouterRtpCapabilities;

        const device = new Device();

        await device.load({
          routerRtpCapabilities: incomingRouterRtpCapabilities
        });

        await createProducerTransport(device);
        await createConsumerTransport(device);
        await consumeExistingProducers(incomingRouterRtpCapabilities);
        await startMicStream();

        startMonitoring(producerTransport.current, consumerTransport.current);
        setConnectionStatus(ConnectionStatus.CONNECTED);
        setLoading(false);
        playSound(SoundType.OWN_USER_JOINED_VOICE_CHANNEL);
      } catch (error) {
        logVoice('Error initializing voice provider', { error });

        setConnectionStatus(ConnectionStatus.FAILED);
        setLoading(false);

        throw error;
      }
    },
    [
      cleanup,
      createProducerTransport,
      createConsumerTransport,
      consumeExistingProducers,
      startMicStream,
      startMonitoring,
      producerTransport,
      consumerTransport
    ]
  );

  const {
    toggleMic,
    toggleSound,
    toggleWebcam,
    toggleScreenShare,
    updateSavedMicTrack,
    ownVoiceState
  } = useVoiceControls({
    startMicStream,
    localAudioStream,
    localAudioProducer,
    startWebcamStream,
    stopWebcamStream,
    startScreenShareStream,
    stopScreenShareStream
  });

  useVoiceEvents({
    consume,
    removeRemoteUserStream,
    removeExternalStreamTrack,
    removeExternalStream,
    clearRemoteUserStreamsForUser,
    rtpCapabilities: routerRtpCapabilities.current!
  });

  useEffect(() => {
    return () => {
      logVoice('Voice provider unmounting, cleaning up resources');
      cleanup();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Clean up streams when leaving voice (channelId -> undefined)
  const currentVoiceChannelId = useCurrentVoiceChannelId();
  const prevChannelIdRef = useRef(currentVoiceChannelId);

  useEffect(() => {
    if (localAudioStream) return;

    setMicProcessingStatus({
      active: false,
      chain: getConfiguredMicChain(devices),
      note: 'Configured; starts when mic stream starts'
    });
  }, [devices, localAudioStream]);

  useEffect(() => {
    if (prevChannelIdRef.current && !currentVoiceChannelId) {
      logVoice('Voice channel left, cleaning up streams');
      cleanup();
    }
    prevChannelIdRef.current = currentVoiceChannelId;
  }, [currentVoiceChannelId, cleanup]);

  // Live-apply device setting changes while in a call
  const prevDevicesRef = useRef<TDeviceSettings | null>(null);

  useEffect(() => {
    if (connectionStatus !== ConnectionStatus.CONNECTED) {
      prevDevicesRef.current = null;
      return;
    }

    // Skip first run after connecting — devices were already used during init
    if (!prevDevicesRef.current) {
      prevDevicesRef.current = devices;
      return;
    }

    const prev = prevDevicesRef.current;
    prevDevicesRef.current = devices;

    const micChanged =
      prev.microphoneId !== devices.microphoneId ||
      prev.echoCancellation !== devices.echoCancellation ||
      prev.noiseSuppression !== devices.noiseSuppression ||
      prev.autoGainControl !== devices.autoGainControl ||
      prev.noiseSuppressionEnhanced !== devices.noiseSuppressionEnhanced ||
      prev.noiseSuppressionDeepFilterNet !== devices.noiseSuppressionDeepFilterNet ||
      prev.noiseSuppressionRnnoise !== devices.noiseSuppressionRnnoise ||
      prev.keyboardSuppression !== devices.keyboardSuppression ||
      prev.voiceSensitivity !== devices.voiceSensitivity;

    const webcamChanged =
      prev.webcamId !== devices.webcamId ||
      prev.webcamFramerate !== devices.webcamFramerate ||
      prev.webcamResolution !== devices.webcamResolution;

    if (micChanged) {
      reapplyMicSettings(ownVoiceState.micMuted, updateSavedMicTrack);
    }
    if (webcamChanged) {
      reapplyWebcamSettings(ownVoiceState.webcamEnabled);
    }
  }, [devices, connectionStatus, reapplyMicSettings, reapplyWebcamSettings, ownVoiceState.micMuted, ownVoiceState.webcamEnabled, updateSavedMicTrack]);

  const contextValue = useMemo<TVoiceProvider>(
    () => ({
      loading,
      connectionStatus,
      transportStats,
      sharingSystemAudio,
      realOutputSinkId,
      micProcessingStatus,
      audioVideoRefsMap: audioVideoRefsMap.current,
      getOrCreateRefs,
      init,

      toggleMic,
      toggleSound,
      toggleWebcam,
      toggleScreenShare,
      updateSavedMicTrack,
      ownVoiceState,

      localAudioStream,
      localVideoStream,
      localScreenShareStream,

      remoteUserStreams,
      externalStreams
    }),
    [
      loading,
      connectionStatus,
      transportStats,
      sharingSystemAudio,
      realOutputSinkId,
      micProcessingStatus,
      getOrCreateRefs,
      init,

      toggleMic,
      toggleSound,
      toggleWebcam,
      toggleScreenShare,
      updateSavedMicTrack,
      ownVoiceState,

      localAudioStream,
      localVideoStream,
      localScreenShareStream,
      remoteUserStreams,
      externalStreams
    ]
  );

  return (
    <VoiceProviderContext.Provider value={contextValue}>
      <VolumeControlProvider>
        <div className="relative">
          <FloatingPinnedCard
            remoteUserStreams={remoteUserStreams}
            externalStreams={externalStreams}
            localScreenShareStream={localScreenShareStream}
            localVideoStream={localVideoStream}
          />
          {children}
        </div>
      </VolumeControlProvider>
    </VoiceProviderContext.Provider>
  );
});

export { VoiceProvider, VoiceProviderContext };
