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

// ============================================================
// Camera tester — live preview from any connected camera.
// Switch devices, read the negotiated resolution and measured
// frame rate, mirror the image, and grab a still frame. The
// stream stays local; snapshots live only in this page.
// ============================================================

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

function CameraTesterTool() {
  const [running, setRunning] = useState(false);
  const [error, setError] = useState(null);
  const [devices, setDevices] = useState([]);
  const [deviceId, setDeviceId] = useState('');
  const [mirror, setMirror] = useState(true);
  const [res, setRes] = useState(null);     // { w, h }
  const [fps, setFps] = useState(0);
  const [shot, setShot] = useState(null);    // data URL

  const videoRef = useRef(null);
  const streamRef = useRef(null);
  const rafRef = useRef(0);
  const fpsState = useRef({ last: 0, frames: 0, t0: 0 });

  const stop = useCallback(() => {
    cancelAnimationFrame(rafRef.current);
    if (streamRef.current) { streamRef.current.getTracks().forEach(t => t.stop()); streamRef.current = null; }
    if (videoRef.current) videoRef.current.srcObject = null;
    setRunning(false);
    setFps(0);
  }, []);

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

  // Measure real frame rate using requestVideoFrameCallback when available,
  // else fall back to a rAF estimate.
  const measureFps = useCallback(() => {
    const v = videoRef.current;
    if (!v) return;
    const s = fpsState.current;
    if (typeof v.requestVideoFrameCallback === 'function') {
      const onFrame = (now) => {
        s.frames++;
        if (!s.t0) s.t0 = now;
        if (now - s.t0 >= 1000) {
          setFps(Math.round((s.frames * 1000) / (now - s.t0)));
          s.frames = 0; s.t0 = now;
        }
        if (streamRef.current) v.requestVideoFrameCallback(onFrame);
      };
      v.requestVideoFrameCallback(onFrame);
    } else {
      const tick = () => {
        const now = performance.now();
        s.frames++;
        if (!s.t0) s.t0 = now;
        if (now - s.t0 >= 1000) {
          setFps(Math.round((s.frames * 1000) / (now - s.t0)));
          s.frames = 0; s.t0 = now;
        }
        rafRef.current = requestAnimationFrame(tick);
      };
      rafRef.current = requestAnimationFrame(tick);
    }
  }, []);

  const start = useCallback(async (id) => {
    setError(null);
    try {
      const constraints = {
        video: id ? { deviceId: { exact: id } } : { facingMode: 'user' },
        audio: false,
      };
      const stream = await navigator.mediaDevices.getUserMedia(constraints);
      streamRef.current = stream;
      const v = videoRef.current;
      v.srcObject = stream;
      await v.play().catch(() => {});

      const all = await navigator.mediaDevices.enumerateDevices();
      const cams = all.filter(d => d.kind === 'videoinput');
      setDevices(cams);
      const track = stream.getVideoTracks()[0];
      const st = track ? track.getSettings() : {};
      setDeviceId(st.deviceId || id || (cams[0] && cams[0].deviceId) || '');

      const updateRes = () => setRes({ w: v.videoWidth, h: v.videoHeight });
      updateRes();
      v.onloadedmetadata = updateRes;

      fpsState.current = { last: 0, frames: 0, t0: 0 };
      setRunning(true);
      measureFps();
    } catch (err) {
      setError(camErrorCopy(err));
      stop();
    }
  }, [measureFps, stop]);

  const switchDevice = (id) => {
    setDeviceId(id);
    if (running) {
      cancelAnimationFrame(rafRef.current);
      if (streamRef.current) { streamRef.current.getTracks().forEach(t => t.stop()); streamRef.current = null; }
      start(id);
    }
  };

  const snapshot = () => {
    const v = videoRef.current;
    if (!v || !v.videoWidth) return;
    const canvas = document.createElement('canvas');
    canvas.width = v.videoWidth; canvas.height = v.videoHeight;
    const ctx = canvas.getContext('2d');
    if (mirror) { ctx.translate(canvas.width, 0); ctx.scale(-1, 1); }
    ctx.drawImage(v, 0, 0);
    setShot(canvas.toDataURL('image/png'));
  };

  return (
    <div className="hw-tool">
      <div className="hw-head">
        <div>
          <h1>Camera tester</h1>
          <p>Confirm your webcam works, check the picture, and read the live resolution and frame
             rate. Switch between cameras and grab a still. The video feed never leaves this device.</p>
        </div>
        <div className="hw-head-actions">
          <span className={`hw-live ${running ? 'is-on' : ''}`}>
            <span className="hw-live-dot" />{running ? 'Live' : 'Idle'}
          </span>
          {running
            ? <button className="lh-btn ghost" onClick={stop}><Icon name="videocam_off" />Stop</button>
            : <button className="lh-btn primary" onClick={() => start(deviceId)}><Icon name="videocam" />Start camera</button>}
        </div>
      </div>

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

      <div className="hw-panel">
        <div className="cmt-stage">
          <video ref={videoRef} className={`cmt-video ${mirror ? 'is-mirrored' : ''}`} playsInline muted />
          {running && (
            <div className="cmt-badge">
              {res && <span className="cmt-pill">{res.w}×{res.h}</span>}
              <span className="cmt-pill">{fps || '–'} fps</span>
            </div>
          )}
          {!running && !error && (
            <div style={{ textAlign: 'center', color: 'var(--text-muted)', padding: 24 }}>
              <Icon name="videocam" style={{ fontSize: 40, opacity: 0.5 }} />
              <div className="hw-banner-title" style={{ marginTop: 8 }}>Camera is off</div>
              <div className="hw-banner-sub">Press “Start camera” and allow access.</div>
            </div>
          )}
        </div>

        <div className="hw-toolbar" style={{ marginTop: 16, marginBottom: 0 }}>
          {devices.length > 0 && (
            <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 || `Camera ${i + 1}`}
                </option>
              ))}
            </select>
          )}
          <button className="lh-btn ghost" onClick={() => setMirror(m => !m)} disabled={!running}>
            <Icon name="flip" />{mirror ? 'Mirrored' : 'Not mirrored'}
          </button>
          <div className="hw-spacer" />
          <button className="lh-btn" onClick={snapshot} disabled={!running}>
            <Icon name="photo_camera" />Snapshot
          </button>
        </div>
      </div>

      {(res || fps) && running && (
        <div className="hw-panel">
          <div className="hw-stats">
            <div className="hw-stat">
              <div className="hw-stat-value">{res ? `${res.w}×${res.h}` : '—'}</div>
              <div className="hw-stat-label">Resolution</div>
            </div>
            <div className="hw-stat">
              <div className="hw-stat-value">{fps || '—'}</div>
              <div className="hw-stat-label">Frames / sec</div>
            </div>
            <div className="hw-stat">
              <div className="hw-stat-value">{res ? (res.w >= 1920 ? '1080p+' : res.w >= 1280 ? '720p' : res.w >= 640 ? 'SD' : 'Low') : '—'}</div>
              <div className="hw-stat-label">Quality</div>
            </div>
            <div className="hw-stat">
              <div className="hw-stat-value">{devices.length}</div>
              <div className="hw-stat-label">Cameras found</div>
            </div>
          </div>
        </div>
      )}

      {shot && (
        <div className="hw-panel">
          <div className="cmt-shot-wrap">
            <div className="cmt-shot-label">Snapshot</div>
            <img src={shot} className="cmt-shot" alt="Camera snapshot" />
            <div style={{ marginTop: 12, display: 'flex', gap: 8 }}>
              <a className="lh-btn ghost" href={shot} download="camera-snapshot.png"><Icon name="download" />Download</a>
              <button className="lh-btn ghost" onClick={() => setShot(null)}><Icon name="close" />Clear</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

window.CameraTesterTool = CameraTesterTool;
