// media.jsx — camera/mic acquisition with graceful fallback, exported to window
const { useState: useStateM, useRef: useRefM, useEffect: useEffectM, useCallback } = React;

// Shared singleton stream so camera carries from system-check into the interview
const _mediaState = { stream: null };

function useMediaStream(active) {
  const [status, setStatus] = useStateM('idle'); // idle | requesting | granted | denied | unsupported
  const [stream, setStream] = useStateM(_mediaState.stream);

  const request = useCallback(async () => {
    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { setStatus('unsupported'); return; }
    setStatus('requesting');
    try {
      const s = await navigator.mediaDevices.getUserMedia({
        video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: 'user' },
        audio: true,
      });
      _mediaState.stream = s;
      setStream(s);
      setStatus('granted');
    } catch (e) {
      setStatus('denied');
    }
  }, []);

  useEffectM(() => {
    if (active && status === 'idle') request();
  }, [active, status, request]);

  return { status, stream, request };
}

// attach a stream to a <video> element
function useVideoRef(stream) {
  const ref = useRefM(null);
  useEffectM(() => {
    if (ref.current && stream) {
      ref.current.srcObject = stream;
      ref.current.play && ref.current.play().catch(() => {});
    }
  }, [stream]);
  return ref;
}

// mic level meter from a real stream, or simulated when none.
// `simulate` (default true) governs the "no stream yet" fallback: the pre-check screen
// wants a decorative sine-wave so the meter doesn't look dead while permission is pending,
// but a LIVE indicator during the actual interview (candidate speaking dot, AI voice orb)
// must NEVER show fake motion — that reintroduces the exact "random, not real" look those
// indicators exist to fix. Callers that drive real UI during the call pass simulate=false
// so a missing stream just reads as silence (level 0) instead of invented movement.
function useMicLevel(stream, enabled, simulate = true) {
  const [level, setLevel] = useStateM(0);
  useEffectM(() => {
    if (!enabled) { setLevel(0); return; }
    let raf, ctx, analyser, data, simT = 0, simRaf;
    if (stream && stream.getAudioTracks().length) {
      try {
        ctx = new (window.AudioContext || window.webkitAudioContext)();
        const src = ctx.createMediaStreamSource(stream);
        analyser = ctx.createAnalyser();
        analyser.fftSize = 256;
        src.connect(analyser);
        data = new Uint8Array(analyser.frequencyBinCount);
        const tick = () => {
          analyser.getByteFrequencyData(data);
          let sum = 0; for (let i = 0; i < data.length; i++) sum += data[i];
          setLevel(Math.min(1, (sum / data.length) / 70));
          raf = requestAnimationFrame(tick);
        };
        tick();
      } catch (e) { /* fall through to sim (if allowed) or silence */ }
    }
    if (!analyser) {
      if (!simulate) { setLevel(0); return; }
      const tick = () => {
        simT += 0.08;
        const v = (Math.sin(simT) * 0.5 + 0.5) * (0.4 + Math.random() * 0.5);
        setLevel(Math.max(0, Math.min(1, v)));
        simRaf = setTimeout(() => requestAnimationFrame(tick), 60);
      };
      tick();
    }
    return () => { cancelAnimationFrame(raf); clearTimeout(simRaf); ctx && ctx.close && ctx.close(); };
  }, [stream, enabled, simulate]);
  return level;
}

function detectBrowser() {
  const ua = navigator.userAgent;
  let name = 'Your browser', ok = true;
  if (/edg\//i.test(ua)) name = 'Microsoft Edge';
  else if (/chrome|chromium|crios/i.test(ua)) name = 'Google Chrome';
  else if (/firefox|fxios/i.test(ua)) name = 'Mozilla Firefox';
  else if (/safari/i.test(ua)) name = 'Safari';
  return { name, ok };
}

// Virtual camera/mic drivers are a common way AI-cheat-tool setups (screen-reading
// overlays, remote-controlled browsers) route a fabricated or remote-controlled feed
// into a normal-looking getUserMedia call. Device labels are only readable after
// permission is granted (which every interview requires anyway), so this is a
// same-call, zero-extra-permission check — not proof of cheating, just a proxy signal
// fed into the integrity score alongside everything else.
const _VIRTUAL_DEVICE_KEYWORDS = [
  'obs virtual camera', 'obs-camera', 'snap camera', 'manycam', 'camtwist',
  'droidcam', 'iriun', 'epoccam', 'ndi virtual', 'xsplit vcam', 'ecamm live',
  'voicemeeter', 'vb-audio', 'vb-cable', 'virtual audio cable', 'blackhole',
  'loopback audio', 'soundflower', 'virtual-cam', 'virtualcam',
];

function detectVirtualDevice(stream) {
  if (!stream) return null;
  const tracks = [...stream.getVideoTracks(), ...stream.getAudioTracks()];
  for (const t of tracks) {
    const label = (t.label || '').toLowerCase();
    const hit = _VIRTUAL_DEVICE_KEYWORDS.find((k) => label.includes(k));
    if (hit) return { kind: t.kind, label: t.label, matched: hit };
  }
  return null;
}

Object.assign(window, { useMediaStream, useVideoRef, useMicLevel, detectBrowser, detectVirtualDevice });

window._sharedStreamStop = function () {
  if (_mediaState.stream) {
    _mediaState.stream.getTracks().forEach((t) => t.stop());
    _mediaState.stream = null;
  }
};
