/* global React, Icon, IconBtn, db */
const { useState, useEffect, useRef, useCallback } = React;

// ============================================================
// Microphone tester — live waveform + level meter from the mic.
// Pick an input device, watch the scope react, read the current
// and peak level. Audio never leaves the page; nothing is recorded
// to disk. Requires microphone permission.
// ============================================================

const MCT_SEGMENTS = 28;

function micErrorCopy(err) {
  const n = err && err.name;
  if (n === 'NotAllowedError' || n === 'SecurityError')
    return 'Microphone permission was blocked. Allow mic access for this page in your browser, then start again.';
  if (n === 'NotFoundError' || n === 'OverconstrainedError')
    return 'No microphone was found. Plug one in or pick a different input device.';
  if (n === 'NotReadableError')
    return 'The microphone is in use by another app. Close it and try again.';
  return (err && err.message) || 'Could not access the microphone.';
}

function MicTesterTool() {
  const [running, setRunning] = useState(false);
  const [error, setError] = useState(null);
  const [devices, setDevices] = useState([]);
  const [deviceId, setDeviceId] = useState('');
  const [level, setLevel] = useState(0);   // 0..1 current RMS
  const [peak, setPeak] = useState(0);     // 0..1 peak hold

  const streamRef = useRef(null);
  const ctxRef = useRef(null);
  const analyserRef = useRef(null);
  const rafRef = useRef(0);
  const canvasRef = useRef(null);
  const peakRef = useRef(0);

  const stop = useCallback(() => {
    cancelAnimationFrame(rafRef.current);
    if (streamRef.current) { streamRef.current.getTracks().forEach(t => t.stop()); streamRef.current = null; }
    if (ctxRef.current) { ctxRef.current.close().catch(() => {}); ctxRef.current = null; }
    analyserRef.current = null;
    setRunning(false);
    setLevel(0); setPeak(0); peakRef.current = 0;
  }, []);

  useEffect(() => () => stop(), [stop]);

  const draw = useCallback(() => {
    const analyser = analyserRef.current;
    const canvas = canvasRef.current;
    if (!analyser || !canvas) return;
    const ctx = canvas.getContext('2d');
    const dpr = window.devicePixelRatio || 1;
    const w = canvas.clientWidth, h = canvas.clientHeight;
    if (canvas.width !== w * dpr || canvas.height !== h * dpr) {
      canvas.width = w * dpr; canvas.height = h * dpr;
    }
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

    const N = analyser.fftSize;
    const buf = new Uint8Array(N);
    analyser.getByteTimeDomainData(buf);

    ctx.clearRect(0, 0, w, h);
    // baseline
    const style = getComputedStyle(document.documentElement);
    const border = style.getPropertyValue('--color-border').trim() || '#2d1b40';
    const primary = style.getPropertyValue('--color-primary').trim() || '#7f0df2';
    ctx.strokeStyle = border; ctx.lineWidth = 1;
    ctx.beginPath(); ctx.moveTo(0, h / 2); ctx.lineTo(w, h / 2); ctx.stroke();

    // waveform
    ctx.strokeStyle = primary; ctx.lineWidth = 2; ctx.lineJoin = 'round';
    ctx.beginPath();
    let sumSq = 0, pk = 0;
    for (let i = 0; i < N; i++) {
      const v = (buf[i] - 128) / 128; // -1..1
      sumSq += v * v; pk = Math.max(pk, Math.abs(v));
      const x = (i / (N - 1)) * w;
      const y = h / 2 + v * (h / 2 - 4);
      i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
    }
    ctx.stroke();

    const rms = Math.sqrt(sumSq / N);
    setLevel(rms);
    peakRef.current = Math.max(peakRef.current * 0.96, pk); // decaying peak hold
    setPeak(peakRef.current);

    rafRef.current = requestAnimationFrame(draw);
  }, []);

  const start = useCallback(async (id) => {
    setError(null);
    try {
      const constraints = {
        audio: id ? { deviceId: { exact: id }, echoCancellation: false, noiseSuppression: false }
                   : { echoCancellation: false, noiseSuppression: false },
      };
      const stream = await navigator.mediaDevices.getUserMedia(constraints);
      streamRef.current = stream;

      // Populate device labels (now that we have permission).
      const all = await navigator.mediaDevices.enumerateDevices();
      const ins = all.filter(d => d.kind === 'audioinput');
      setDevices(ins);
      const active = stream.getAudioTracks()[0];
      const settings = active ? active.getSettings() : {};
      setDeviceId(settings.deviceId || id || (ins[0] && ins[0].deviceId) || '');

      const Ctx = window.AudioContext || window.webkitAudioContext;
      const ctx = new Ctx();
      if (ctx.state === 'suspended') await ctx.resume();
      const src = ctx.createMediaStreamSource(stream);
      const analyser = ctx.createAnalyser();
      analyser.fftSize = 2048;
      analyser.smoothingTimeConstant = 0.6;
      src.connect(analyser);
      ctxRef.current = ctx;
      analyserRef.current = analyser;

      setRunning(true);
      cancelAnimationFrame(rafRef.current);
      rafRef.current = requestAnimationFrame(draw);
    } catch (err) {
      setError(micErrorCopy(err));
      stop();
    }
  }, [draw, stop]);

  const switchDevice = (id) => {
    setDeviceId(id);
    if (running) {
      // restart on the new device
      cancelAnimationFrame(rafRef.current);
      if (streamRef.current) { streamRef.current.getTracks().forEach(t => t.stop()); streamRef.current = null; }
      if (ctxRef.current) { ctxRef.current.close().catch(() => {}); ctxRef.current = null; }
      start(id);
    }
  };

  const levelPct = Math.min(100, Math.round(level * 140));
  const peakPct = Math.min(100, Math.round(peak * 140));
  const litSegs = Math.round((levelPct / 100) * MCT_SEGMENTS);

  return (
    <div className="hw-tool">
      <div className="hw-head">
        <div>
          <h1>Microphone tester</h1>
          <p>Speak, tap, or snap — the scope and meter react in real time. Use it to confirm the
             right input is picked up and that levels look healthy. Audio stays on your device.</p>
        </div>
        <div className="hw-head-actions">
          <span className={`hw-live ${running ? 'is-on' : ''}`}>
            <span className="hw-live-dot" />{running ? 'Listening' : 'Idle'}
          </span>
          {running
            ? <button className="lh-btn ghost" onClick={stop}><Icon name="stop" />Stop</button>
            : <button className="lh-btn primary" onClick={() => start(deviceId)}><Icon name="mic" />Start mic</button>}
        </div>
      </div>

      {error && (
        <div className="hw-banner is-error" style={{ marginBottom: 16 }}>
          <Icon name="mic_off" />
          <div>
            <div className="hw-banner-title">Microphone unavailable</div>
            <div className="hw-banner-sub">{error}</div>
          </div>
        </div>
      )}

      <div className="hw-panel">
        {devices.length > 0 && (
          <div className="hw-toolbar">
            <Icon name="settings_voice" style={{ color: 'var(--text-lo)' }} />
            <select className="hw-select" value={deviceId} onChange={e => switchDevice(e.target.value)}>
              {devices.map((d, i) => (
                <option key={d.deviceId || i} value={d.deviceId}>
                  {d.label || `Microphone ${i + 1}`}
                </option>
              ))}
            </select>
          </div>
        )}

        <div className="mct-canvas-wrap">
          <canvas ref={canvasRef} className="mct-canvas" />
          {!running && !error && (
            <div className="hw-banner" style={{ position: 'absolute', inset: 0, border: 0, background: 'transparent', alignItems: 'center', justifyContent: 'center' }}>
              <div style={{ textAlign: 'center' }}>
                <div className="hw-banner-title">No signal yet</div>
                <div className="hw-banner-sub">Press “Start mic” and allow microphone access.</div>
              </div>
            </div>
          )}
        </div>

        <div className="mct-meter" aria-hidden="true">
          {Array.from({ length: MCT_SEGMENTS }).map((_, i) => {
            const on = i < litSegs;
            const ratio = i / MCT_SEGMENTS;
            const color = ratio > 0.85 ? 'var(--color-error)' : ratio > 0.65 ? 'var(--color-warning)' : 'var(--color-success)';
            return (
              <div key={i} className="mct-meter-seg"
                style={{
                  background: on ? color : 'var(--color-border)',
                  opacity: on ? 1 : 0.5,
                  height: `${40 + ratio * 60}%`,
                }} />
            );
          })}
        </div>

        <div className="mct-level-row">
          <div className="mct-level-num">{levelPct}<span style={{ fontSize: 16, color: 'var(--text-muted)' }}>%</span></div>
          <div>
            <div style={{ fontSize: 13, color: 'var(--text-md)', fontWeight: 600 }}>Input level</div>
            <div className="mct-level-cap">Peak hold {peakPct}% · keep speech under the amber zone to avoid clipping</div>
          </div>
        </div>
      </div>
    </div>
  );
}

window.MicTesterTool = MicTesterTool;
