// cameraScanner.js — camera-based barcode scanning via BarcodeDetector.
// BarcodeDetector is not natively supported on Windows; a WASM polyfill is loaded
// on first use from jsDelivr (which correctly serves the .wasm binary alongside the JS).

let _detector = null;
let _stream   = null;
let _interval = null;
let _running  = false;
let _onScan   = null;
let _onStop   = null;

const INTERVAL_MS = 175; // WASM detection takes 50-120ms; 175ms avoids main-thread congestion
const FORMATS     = ['qr_code', 'code_128', 'code_39', 'ean_13', 'ean_8', 'upc_a', 'data_matrix', 'pdf417'];

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

export function init(onScan, onStop) {
  _onScan = onScan;
  _onStop = onStop;
  document.getElementById('btn-camera-close')?.addEventListener('click', stop);
}

export function isActive() {
  return _running;
}

export async function start() {
  if (_running) return;
  _showOverlay();
  _setStatus('Initializing…');
  try {
    await _initDetector();
    _stream = await _acquireCamera();
    const video = document.getElementById('camera-video');
    video.srcObject = _stream;
    await video.play();
    _setStatus('Scanning…');
    _running = true;
    _startLoop(video);
  } catch (err) {
    _handleError(err);
  }
}

export async function stop() {
  _running = false;
  clearInterval(_interval);
  _interval = null;
  _releaseCamera();
  _hideOverlay();
  _onStop?.();
}

// ── Detector init (lazy, singleton) ──────────────────────────────────────────

async function _initDetector() {
  if (_detector) return;

  _setStatus('Loading barcode scanner…');

  // Try native BarcodeDetector first (works on Android/macOS, NOT Windows)
  if ('BarcodeDetector' in window) {
    try {
      const supported = await BarcodeDetector.getSupportedFormats();
      if (supported.length > 0) {
        _detector = new BarcodeDetector({ formats: FORMATS.filter(f => supported.includes(f)) });
        return;
      }
    } catch {
      // Fall through to polyfill
    }
  }

  // WASM polyfill via jsDelivr — jsDelivr correctly serves the .wasm binary
  // alongside the JS module (esm.sh does not host .wasm files)
  const { BarcodeDetector: Poly } = await import(
    'https://cdn.jsdelivr.net/npm/barcode-detector@3/+esm'
  );
  _detector = new Poly({ formats: FORMATS });
}

// ── Camera acquisition ────────────────────────────────────────────────────────

async function _acquireCamera() {
  const devices = await navigator.mediaDevices.enumerateDevices();
  const inputs  = devices.filter(d => d.kind === 'videoinput');

  if (!inputs.length) {
    const err = new Error('No camera found on this device.');
    err.name = 'NoCameraError';
    throw err;
  }

  // Surface Pro: enumerateDevices labels are only populated after first permission grant.
  // On first run, labels are empty strings → facingMode fallback is used.
  // On subsequent runs, labels like "Microsoft Camera Rear" are available.
  const rear = inputs.find(d => /rear|back/i.test(d.label));

  const constraints = rear
    ? { video: { deviceId: { exact: rear.deviceId }, width: { ideal: 1280 }, height: { ideal: 720 } } }
    : { video: { facingMode: { ideal: 'environment' }, width: { ideal: 1280 }, height: { ideal: 720 } } };

  return navigator.mediaDevices.getUserMedia(constraints);
}

// ── Scan loop ─────────────────────────────────────────────────────────────────

function _startLoop(video) {
  _interval = setInterval(async () => {
    if (!_running || video.readyState < video.HAVE_ENOUGH_DATA) return;
    try {
      const hits = await _detector.detect(video);
      if (hits.length) {
        const value = hits[0].rawValue;
        // Flash detected state briefly so the user sees the scanner found something
        const overlay = document.getElementById('camera-overlay');
        overlay?.classList.add('detected');
        _setStatus('Barcode found!');
        await new Promise(r => setTimeout(r, 350));
        await stop();        // Close overlay then fire scan (matches USB wedge UX)
        _onScan?.(value);
      }
    } catch {
      // Silently skip frames where detect() throws (video not ready, stream ended, etc.)
    }
  }, INTERVAL_MS);
}

// ── Error handling ────────────────────────────────────────────────────────────

function _handleError(err) {
  _running = false;
  clearInterval(_interval);
  _interval = null;
  _releaseCamera();

  const msg =
    err.name === 'NotAllowedError'  ? 'Camera permission denied. Allow camera access and try again.' :
    err.name === 'NoCameraError'    ? 'No camera found on this device.' :
    err.name === 'NotFoundError'    ? 'Camera not found. Is it connected?' :
    err.name === 'NotReadableError' ? 'Camera is in use by another application.' :
    `Scanner error: ${err.message}`;

  _setStatus(msg, true);
  // Keep overlay visible on error so the user can read the message; X button still works
}

// ── Helpers ───────────────────────────────────────────────────────────────────

function _releaseCamera() {
  _stream?.getTracks().forEach(t => t.stop());
  _stream = null;
  const video = document.getElementById('camera-video');
  if (video) video.srcObject = null;
}

function _showOverlay() {
  document.getElementById('camera-overlay').hidden = false;
}

function _hideOverlay() {
  const el = document.getElementById('camera-overlay');
  el.classList.remove('detected');
  el.hidden = true;
}

function _setStatus(msg, isError = false) {
  const el = document.getElementById('camera-status');
  if (!el) return;
  el.textContent = msg;
  el.classList.toggle('error', isError);
}
