/**
 * Scanner service — dual-engine QR detection.
 *
 * Engine 1 (preferred):  BarcodeDetector — native browser API, hardware-
 *   accelerated, built into Chrome 83+ / all Android Chrome. Zero deps.
 *   Typical decode latency: 20-60 ms per frame.
 *
 * Engine 2 (fallback):   @zxing/browser BrowserQRCodeReader — pure-JS ZXing,
 *   QR-only (fast), already installed, works everywhere else.
 *
 * Camera stream stays alive between scans so the next detection is
 * near-instant (no 2-3 s camera-restart delay).
 */
import { BrowserQRCodeReader } from '@zxing/browser';

type Controls = { stop: () => void };

let rafId:  number | null  = null;   // for BarcodeDetector loop
let stream: MediaStream | null = null; // for BarcodeDetector camera
let zxing:  Controls | null = null;  // for ZXing decode loop
let paused = false;

const VIDEO_CONSTRAINTS = {
  facingMode: { ideal: 'environment' },
  width:  { ideal: 1280, max: 1920 },
  height: { ideal: 720,  max: 1080 },
};

// ─── Public API ─────────────────────────────────────────────────────────────

export async function startScanner(
  videoEl: HTMLVideoElement,
  onResult: (text: string) => void,
): Promise<void> {
  stopCurrentScanner();
  paused = false;

  if (typeof (window as any).BarcodeDetector !== 'undefined') {
    await startNative(videoEl, onResult);
  } else {
    await startZxing(videoEl, onResult);
  }
}

/** Allow the next scan result through without restarting the camera. */
export function resumeScanner(): void {
  paused = false;
}

/** Stop all engines and release the camera. */
export function stopCurrentScanner(): void {
  paused = false;

  if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null; }

  try { zxing?.stop(); } catch {}
  zxing = null;

  stream?.getTracks().forEach(t => t.stop());
  stream = null;
}

export async function listCameras(): Promise<{ id: string; label: string }[]> {
  try {
    const devices = await navigator.mediaDevices.enumerateDevices();
    return devices
      .filter(d => d.kind === 'videoinput')
      .map(d => ({ id: d.deviceId, label: d.label || `Camera ${d.deviceId.slice(0, 6)}` }));
  } catch {
    return [];
  }
}

// ─── Engine 1: Native BarcodeDetector ───────────────────────────────────────

async function startNative(
  videoEl: HTMLVideoElement,
  onResult: (text: string) => void,
): Promise<void> {
  stream = await navigator.mediaDevices.getUserMedia({ video: VIDEO_CONSTRAINTS });
  videoEl.srcObject = stream;

  // Wait until the video is ready to paint frames
  await new Promise<void>((resolve, reject) => {
    if (videoEl.readyState >= HTMLMediaElement.HAVE_ENOUGH_DATA) { resolve(); return; }
    videoEl.onloadeddata = () => videoEl.play().then(resolve).catch(reject);
    videoEl.onerror = () => reject(new Error('Video element error'));
  });

  const detector = new (window as any).BarcodeDetector({ formats: ['qr_code'] });

  const loop = async () => {
    if (!paused && videoEl.readyState >= HTMLMediaElement.HAVE_ENOUGH_DATA) {
      try {
        const codes: any[] = await detector.detect(videoEl);
        if (codes.length > 0 && !paused) {
          paused = true;
          onResult(codes[0].rawValue as string);
        }
      } catch { /* no code in this frame — continue */ }
    }
    rafId = requestAnimationFrame(loop);
  };
  rafId = requestAnimationFrame(loop);
}

// ─── Engine 2: ZXing BrowserQRCodeReader (fallback) ─────────────────────────

async function startZxing(
  videoEl: HTMLVideoElement,
  onResult: (text: string) => void,
): Promise<void> {
  const reader = new BrowserQRCodeReader(undefined, {
    delayBetweenScanAttempts: 50,
  });

  zxing = await reader.decodeFromConstraints(
    { video: VIDEO_CONSTRAINTS },
    videoEl,
    (result) => {
      if (result && !paused) {
        paused = true;
        onResult(result.getText());
      }
    },
  );
}
