const AudioContextCtor =
  typeof window !== "undefined"
    ? window.AudioContext ||
      (window as unknown as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext
    : undefined;

interface AudioMeter {
  connect(stream: MediaStream): void;
  getLevel(): number;
  destroy(): void;
}

export function createAudioMeter(): AudioMeter {
  let audioContext: AudioContext | null = null;
  let analyser: AnalyserNode | null = null;
  let sourceNode: MediaStreamAudioSourceNode | null = null;
  let waveform: Uint8Array<ArrayBuffer> | null = null;
  let connectedStream: MediaStream | null = null;
  let running = false;
  let currentLevel = 0;

  const tick = () => {
    if (!running) return;
    if (analyser && waveform) {
      analyser.getByteTimeDomainData(waveform);
      let sum = 0;
      for (let i = 0; i < waveform.length; i++) {
        const v = (waveform[i] - 128) / 128;
        sum += v * v;
      }
      currentLevel = Math.min(1, Math.sqrt(sum / waveform.length) * 4.8);
    }
    requestAnimationFrame(tick);
  };

  const ensureContext = async () => {
    if (!AudioContextCtor) return;
    if (!audioContext) {
      audioContext = new AudioContextCtor();
    }
    if (audioContext.state === "suspended") {
      try {
        await audioContext.resume();
      } catch (_) {
        /* ignore */
      }
    }
    if (!analyser) {
      analyser = audioContext.createAnalyser();
      analyser.fftSize = 512;
      analyser.smoothingTimeConstant = 0.84;
      waveform = new Uint8Array(analyser.fftSize);
    }
  };

  return {
    async connect(stream: MediaStream) {
      await ensureContext();
      if (!audioContext || !analyser) return;
      if (connectedStream === stream) return;
      if (sourceNode) {
        try {
          sourceNode.disconnect();
        } catch (_) {
          /* ignore */
        }
        sourceNode = null;
      }
      try {
        sourceNode = audioContext.createMediaStreamSource(stream);
        sourceNode.connect(analyser);
        connectedStream = stream;
      } catch (_) {
        sourceNode = null;
        connectedStream = null;
      }
      if (!running) {
        running = true;
        requestAnimationFrame(tick);
      }
    },
    getLevel() {
      return currentLevel;
    },
    destroy() {
      running = false;
      if (sourceNode) {
        try {
          sourceNode.disconnect();
        } catch (_) {
          /* ignore */
        }
      }
      audioContext?.close().catch(() => {});
      audioContext = null;
      analyser = null;
      sourceNode = null;
      waveform = null;
      connectedStream = null;
      currentLevel = 0;
    },
  };
}
