// DSAudioMeter.jsx — Meridian audio activity indicator
//
// Three visual variants for showing live audio:
//   pulse    — single scaling circle, simplest, default
//   bars     — row of N vertical bars tracking amplitude history
//   waveform — scrolling polyline of amplitude history
//
// Props:
//   amplitude   number 0..1, streaming
//   variant     'pulse' | 'bars' | 'waveform'    default 'pulse'
//   size        'default' | 'mini'               default 'default'
//   paused      bool                              default false
//   silent      bool                              default false  (dim overlay)
//   color       hex string                        default Tokens.Color.axis
//
// Reduced-motion: static mic icon at active color.
// Paused: history frozen, color drops to inkMuted.
// Silent: 50% opacity overlay on the whole component.

function DSAudioMeter({
  amplitude = 0,
  variant = 'pulse',
  size = 'default',
  paused = false,
  silent = false,
  color = M.axis,
}) {
  const isMini = size === 'mini';
  const box = isMini ? 28 : 96;
  const barCount = isMini ? 14 : 28;

  // Respect prefers-reduced-motion
  const [reduceMotion, setReduceMotion] = React.useState(false);
  React.useEffect(() => {
    const mql = window.matchMedia('(prefers-reduced-motion: reduce)');
    setReduceMotion(mql.matches);
    const onChange = e => setReduceMotion(e.matches);
    mql.addEventListener?.('change', onChange);
    return () => mql.removeEventListener?.('change', onChange);
  }, []);

  // History buffer — refilled on each render with the latest amplitude.
  // useRef so we don't trigger re-renders for every sample.
  const histRef = React.useRef(new Array(barCount).fill(0));
  // Force re-render on rAF tick so the visuals update with the latest sample,
  // but we cap to 30 fps to keep CPU calm.
  const [, tick] = React.useReducer(x => x + 1, 0);
  React.useEffect(() => {
    if (paused || reduceMotion) return;
    let raf, last = 0;
    const loop = (t) => {
      raf = requestAnimationFrame(loop);
      if (t - last < 33) return; // ~30fps cap
      last = t;
      // Shift in latest amplitude
      const h = histRef.current;
      h.shift();
      h.push(Math.max(0, Math.min(1, amplitude)));
      tick();
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, [amplitude, paused, reduceMotion]);

  // When the buffer length changes (size change), resize it
  React.useEffect(() => {
    const h = histRef.current;
    if (h.length !== barCount) {
      histRef.current = h.length > barCount
        ? h.slice(-barCount)
        : new Array(barCount - h.length).fill(0).concat(h);
    }
  }, [barCount]);

  const activeColor = paused ? M.inkMuted : color;

  // ── Reduced-motion fallback: static mic icon ───────────────────
  if (reduceMotion) {
    return (
      <Wrapper box={box} silent={silent}>
        <MicGlyph size={isMini ? 14 : 36} color={activeColor} />
      </Wrapper>
    );
  }

  // ── PULSE ──────────────────────────────────────────────────────
  if (variant === 'pulse') {
    // Two concentric circles: solid core + halo that scales with amplitude.
    const a = paused ? 0 : amplitude;
    const haloScale = 1 + a * (isMini ? 0.6 : 1.0);
    const coreScale = 1 + a * 0.15;
    return (
      <Wrapper box={box} silent={silent}>
        <svg width={box} height={box} viewBox={`0 0 ${box} ${box}`} style={{ display: 'block' }}>
          {/* Halo */}
          <circle
            cx={box/2} cy={box/2}
            r={box * 0.30}
            fill={activeColor}
            opacity={paused ? 0.10 : 0.18}
            style={{
              transformOrigin: `${box/2}px ${box/2}px`,
              transform: `scale(${haloScale})`,
              transition: 'transform 80ms cubic-bezier(.2,.8,.2,1)',
            }}
          />
          {/* Core */}
          <circle
            cx={box/2} cy={box/2}
            r={box * 0.18}
            fill={activeColor}
            style={{
              transformOrigin: `${box/2}px ${box/2}px`,
              transform: `scale(${coreScale})`,
              transition: 'transform 80ms cubic-bezier(.2,.8,.2,1)',
            }}
          />
          {/* Mic glyph centered */}
          <g transform={`translate(${box/2 - (isMini?5:10)},${box/2 - (isMini?6:12)})`}>
            <MicGlyphPath size={isMini ? 10 : 20} color="#fff" />
          </g>
        </svg>
      </Wrapper>
    );
  }

  // ── BARS ───────────────────────────────────────────────────────
  if (variant === 'bars') {
    const hist = histRef.current;
    const barW = (box - (barCount - 1) * 1) / barCount;
    const gap = 1;
    return (
      <Wrapper box={box} silent={silent}>
        <svg width={box} height={box} viewBox={`0 0 ${box} ${box}`} style={{ display: 'block' }}>
          {hist.map((v, i) => {
            // Floor minimum so the bars never disappear entirely
            const a = paused ? Math.max(0.06, v * 0.5) : Math.max(0.06, v);
            const h = a * box * 0.85;
            return (
              <rect
                key={i}
                x={i * (barW + gap)}
                y={(box - h) / 2}
                width={barW}
                height={h}
                rx={Math.min(barW/2, 1.5)}
                fill={activeColor}
                opacity={paused ? 0.5 : 1}
              />
            );
          })}
        </svg>
      </Wrapper>
    );
  }

  // ── WAVEFORM ───────────────────────────────────────────────────
  // Center-mirrored line: amplitude maps to vertical excursion top + bottom.
  const hist = histRef.current;
  const n = hist.length;
  const mid = box / 2;
  const amp = box * 0.40; // max excursion from midline
  const pts = hist.map((v, i) => {
    const x = (i / (n - 1)) * box;
    // Add a small alternating sign so the line wobbles, not just spikes.
    const sign = (i % 2 === 0 ? 1 : -1);
    const y = mid - sign * v * amp;
    return `${x.toFixed(2)},${y.toFixed(2)}`;
  }).join(' ');
  const ptsMirror = hist.map((v, i) => {
    const x = (i / (n - 1)) * box;
    const sign = (i % 2 === 0 ? 1 : -1);
    const y = mid + sign * v * amp;
    return `${x.toFixed(2)},${y.toFixed(2)}`;
  }).join(' ');

  return (
    <Wrapper box={box} silent={silent}>
      <svg width={box} height={box} viewBox={`0 0 ${box} ${box}`} style={{ display: 'block' }}>
        {/* Midline reference */}
        <line x1="0" y1={mid} x2={box} y2={mid}
              stroke={activeColor} strokeOpacity="0.18" strokeWidth="1" />
        <polyline points={pts}
                  fill="none" stroke={activeColor}
                  strokeWidth={isMini ? 1 : 1.5}
                  strokeOpacity={paused ? 0.5 : 1}
                  strokeLinecap="round" strokeLinejoin="round" />
        <polyline points={ptsMirror}
                  fill="none" stroke={activeColor}
                  strokeWidth={isMini ? 1 : 1.5}
                  strokeOpacity={paused ? 0.25 : 0.55}
                  strokeLinecap="round" strokeLinejoin="round" />
      </svg>
    </Wrapper>
  );
}

// ── Wrapper: handles silent overlay + sizing ─────────────────────
function Wrapper({ box, silent, children }) {
  return (
    <div style={{
      position: 'relative', width: box, height: box, flexShrink: 0,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      opacity: silent ? 0.5 : 1,
      transition: 'opacity 220ms cubic-bezier(.2,.8,.2,1)',
    }}>
      {children}
    </div>
  );
}

// ── Mic glyph ────────────────────────────────────────────────────
function MicGlyph({ size, color }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
      <MicGlyphPath size={size} color={color} />
    </svg>
  );
}
function MicGlyphPath({ size, color }) {
  // Single-color, stroke 2.2 to match Meridian icon vocabulary
  return (
    <g stroke={color} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" fill="none">
      <path d="M12 2a3 3 0 0 0-3 3v6a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z" />
      <path d="M19 11a7 7 0 0 1-14 0" />
      <path d="M12 18v3" />
    </g>
  );
}

window.DSAudioMeter = DSAudioMeter;
