import { useEffect, useRef } from "react";

interface AudioLevelBarsProps {
  sourceNode: AudioNode;
}

const BAR_COUNT = 3;
const MIN_HEIGHT_PX = 6;
const MAX_HEIGHT_PX = 48;

// dB scale
const NOISE_FLOOR_DB = -50;
const DB_RANGE = -NOISE_FLOOR_DB; // 50dB dynamic range

// Asymmetric smoothing time constants
const ATTACK_MS = 30;
const RELEASE_MS = 300;

// Bar activation thresholds on the 0..1 normalized dB scale
const BAR_THRESHOLDS = [0.0, 0.15, 0.35];

/**
 * 3-bar VU meter that visualizes audio levels from any AudioNode source.
 * Works with both live MediaStream sources and MediaElement sources.
 *
 * Uses direct DOM manipulation with exponential smoothing on a dB scale
 * for smooth, jitter-free animation independent of frame rate.
 */
export function AudioLevelBars({ sourceNode }: AudioLevelBarsProps) {
  const barRefs = useRef<(HTMLDivElement | null)[]>([]);

  useEffect(() => {
    const ctx = sourceNode.context as AudioContext;
    const analyser = ctx.createAnalyser();
    analyser.fftSize = 256;
    sourceNode.connect(analyser);

    // Connect to destination via silent gain node — without this,
    // Chromium suspends processing on disconnected audio graphs.
    const silentGain = ctx.createGain();
    silentGain.gain.value = 0;
    analyser.connect(silentGain);
    silentGain.connect(ctx.destination);

    const dataArray = new Uint8Array(analyser.frequencyBinCount);

    let smoothedLevel = 0;
    let lastTime = performance.now();
    let rafId = 0;

    function tick() {
      const now = performance.now();
      const dt = now - lastTime;
      lastTime = now;

      analyser.getByteTimeDomainData(dataArray);

      // Compute RMS of waveform (128 = silence baseline)
      let sumSquares = 0;
      for (let i = 0; i < dataArray.length; i++) {
        const normalized = (dataArray[i] - 128) / 128;
        sumSquares += normalized * normalized;
      }
      const rms = Math.sqrt(sumSquares / dataArray.length);

      // Convert to dB, clamp to noise floor, normalize to 0..1
      const db = rms > 0 ? 20 * Math.log10(rms) : NOISE_FLOOR_DB;
      const normalizedDb = Math.max(0, (db - NOISE_FLOOR_DB) / DB_RANGE);

      // Asymmetric exponential smoothing (frame-rate independent)
      const timeConstant =
        normalizedDb > smoothedLevel ? ATTACK_MS : RELEASE_MS;
      const alpha = 1 - Math.exp(-dt / timeConstant);
      smoothedLevel += alpha * (normalizedDb - smoothedLevel);

      // Update bar heights via direct DOM writes
      for (let i = 0; i < BAR_COUNT; i++) {
        const el = barRefs.current[i];
        if (!el) continue;

        const threshold = BAR_THRESHOLDS[i];
        const barLevel =
          smoothedLevel <= threshold
            ? 0
            : Math.min(1, (smoothedLevel - threshold) / (1 - threshold));
        const height = MIN_HEIGHT_PX + barLevel * (MAX_HEIGHT_PX - MIN_HEIGHT_PX);
        el.style.height = `${height}px`;
      }

      rafId = requestAnimationFrame(tick);
    }

    rafId = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(rafId);
      try {
        sourceNode.disconnect(analyser);
        analyser.disconnect(silentGain);
        silentGain.disconnect(ctx.destination);
      } catch {
        // Nodes may already be disconnected
      }
    };
  }, [sourceNode]);

  return (
    <div className="flex items-end gap-1.5">
      {Array.from({ length: BAR_COUNT }, (_, i) => (
        <div
          key={i}
          ref={(el) => {
            barRefs.current[i] = el;
          }}
          className="w-1.5 rounded-full bg-green-400"
          style={{ height: `${MIN_HEIGHT_PX}px` }}
        />
      ))}
    </div>
  );
}
