/*** This text should be at the top of all code files ***
 * qr-scanner-app.js – used for local QR scanning in the browser
 * Made by: Morten Pilekær
 * Rev. 0.4.0 Owner: Morten Pilekær
 * Location: /qr-app-lite/js/qr-scanner-app.js
 *
 * Primær motor: lokal jsQR-library.
 * Fallback/alternativ: browserens BarcodeDetector, hvis tilgængelig.
 * Kører uden CDN og uden internetadgang.
 ***/
(() => {
  'use strict';

  const config = window.QR_APP_CONFIG || {};
  const HISTORY_KEY = 'qrAppLite.scanHistory.v1';
  const HISTORY_MAX_ITEMS = Number.parseInt(config.scanHistoryMaxItems || '20', 10) || 20;
  const IS_PUBLIC_DEMO = config.publicDemo === true;

  const els = {
    alert: document.getElementById('scannerAlert'),
    cameraSelect: document.getElementById('cameraSelect'),
    startBtn: document.getElementById('startBtn'),
    stopBtn: document.getElementById('stopBtn'),
    refreshBtn: document.getElementById('refreshBtn'),
    status: document.getElementById('scannerStatus'),
    apiStatus: document.getElementById('apiStatus'),
    badge: document.getElementById('scanBadge'),
    video: document.getElementById('scannerVideo'),
    canvas: document.getElementById('scannerCanvas'),
    result: document.getElementById('scanResult'),
    copyBtn: document.getElementById('copyBtn'),
    openUrlBtn: document.getElementById('openUrlBtn'),
    clearBtn: document.getElementById('clearBtn'),
    historyCount: document.getElementById('historyCount'),
    historyEmpty: document.getElementById('historyEmpty'),
    scanHistory: document.getElementById('scanHistory'),
    clearHistoryBtn: document.getElementById('clearHistoryBtn'),
  };

  let detector = null;
  let scannerEngine = '';
  let stream = null;
  let scanTimer = null;
  let lastValue = '';
  let isScanning = false;
  let hasRequestedCameraPermission = false;

  function escapeHtml(value) {
    return String(value)
      .replaceAll('&', '&amp;')
      .replaceAll('<', '&lt;')
      .replaceAll('>', '&gt;')
      .replaceAll('"', '&quot;')
      .replaceAll("'", '&#039;');
  }

  function setAlert(type, html) {
    els.alert.className = `alert alert-${type}`;
    els.alert.innerHTML = html;
  }

  function setStatus(text) {
    els.status.textContent = text;
  }

  function setBadge(text, bootstrapClass) {
    els.badge.textContent = text;
    els.badge.className = `badge ${bootstrapClass}`;
  }

  function isProbablyUrl(value) {
    try {
      const url = new URL(value.trim());
      return url.protocol === 'http:' || url.protocol === 'https:';
    } catch (_err) {
      return false;
    }
  }

  function setOpenUrl(value) {
    const trimmed = value.trim();
    if (isProbablyUrl(trimmed)) {
      els.openUrlBtn.href = trimmed;
      els.openUrlBtn.classList.remove('disabled');
      els.openUrlBtn.removeAttribute('aria-disabled');
    } else {
      els.openUrlBtn.href = '#';
      els.openUrlBtn.classList.add('disabled');
      els.openUrlBtn.setAttribute('aria-disabled', 'true');
    }
  }

  function setResult(value, addToHistory = true) {
    lastValue = value;
    els.result.value = value;
    els.copyBtn.disabled = value.trim() === '';
    els.clearBtn.disabled = value.trim() === '';
    setOpenUrl(value);

    if (!IS_PUBLIC_DEMO && addToHistory && value.trim() !== '') {
      addHistoryItem(value.trim());
    }
  }

  function loadHistory() {
    if (IS_PUBLIC_DEMO) {
      return [];
    }

    try {
      const parsed = JSON.parse(window.localStorage.getItem(HISTORY_KEY) || '[]');
      return Array.isArray(parsed) ? parsed.filter((item) => item && typeof item.value === 'string') : [];
    } catch (_err) {
      return [];
    }
  }

  function saveHistory(items) {
    if (IS_PUBLIC_DEMO) {
      return;
    }

    window.localStorage.setItem(HISTORY_KEY, JSON.stringify(items.slice(0, HISTORY_MAX_ITEMS)));
  }

  function formatTimestamp(isoString) {
    try {
      return new Intl.DateTimeFormat('da-DK', {
        dateStyle: 'short',
        timeStyle: 'medium',
      }).format(new Date(isoString));
    } catch (_err) {
      return isoString;
    }
  }

  function renderHistory() {
    if (IS_PUBLIC_DEMO) {
      return;
    }

    const items = loadHistory();
    els.historyCount.textContent = String(items.length);
    els.clearHistoryBtn.disabled = items.length === 0;
    els.historyEmpty.classList.toggle('d-none', items.length > 0);
    els.scanHistory.innerHTML = '';

    if (items.length === 0) {
      return;
    }

    items.forEach((item, index) => {
      const value = item.value || '';
      const itemEl = document.createElement('div');
      itemEl.className = 'list-group-item scan-history-item';

      const safeValue = escapeHtml(value);
      const isUrl = isProbablyUrl(value);
      const openButton = isUrl
        ? `<a class="btn btn-sm btn-outline-primary" href="${safeValue}" target="_blank" rel="noopener">Åbn URL</a>`
        : '';

      itemEl.innerHTML = `
        <div class="d-flex flex-column flex-md-row justify-content-between gap-2">
          <div class="me-md-3 flex-grow-1">
            <div class="small text-secondary mb-1">${escapeHtml(formatTimestamp(item.createdAt || ''))}</div>
            <code class="scan-history-value text-break">${safeValue}</code>
          </div>
          <div class="d-flex flex-wrap gap-2 align-self-md-start">
            <button type="button" class="btn btn-sm btn-outline-success" data-history-action="copy" data-index="${index}">Kopiér</button>
            <button type="button" class="btn btn-sm btn-outline-secondary" data-history-action="use" data-index="${index}">Vis</button>
            ${openButton}
          </div>
        </div>
      `;

      els.scanHistory.appendChild(itemEl);
    });
  }

  function addHistoryItem(value) {
    if (IS_PUBLIC_DEMO) {
      return;
    }

    const cleanValue = value.trim();
    if (cleanValue === '') {
      return;
    }

    const current = loadHistory().filter((item) => item.value !== cleanValue);
    current.unshift({
      value: cleanValue,
      createdAt: new Date().toISOString(),
    });

    saveHistory(current);
    renderHistory();
  }

  async function copyText(value) {
    if (value.trim() === '') {
      return false;
    }

    try {
      await navigator.clipboard.writeText(value);
      return true;
    } catch (_err) {
      return false;
    }
  }

  async function setupScannerEngine() {
    if (!('mediaDevices' in navigator) || !('getUserMedia' in navigator.mediaDevices)) {
      els.apiStatus.textContent = 'Kamera-API mangler';
      setAlert('danger', 'Browseren understøtter ikke <code>getUserMedia</code>. Brug en nyere Chrome/Edge-browser på <code>localhost</code>.');
      els.startBtn.disabled = true;
      els.refreshBtn.disabled = true;
      return false;
    }

    if (typeof window.jsQR === 'function') {
      scannerEngine = 'jsQR';
      els.apiStatus.textContent = 'OK – lokal jsQR';
      setAlert('info', 'Scanner bruger lokal <code>jsQR</code>-library. Vælg kamera og tryk <strong>Start scanner</strong>.');
      return true;
    }

    if ('BarcodeDetector' in window) {
      try {
        const supportedFormats = await window.BarcodeDetector.getSupportedFormats();
        if (supportedFormats.includes('qr_code')) {
          detector = new window.BarcodeDetector({ formats: ['qr_code'] });
          scannerEngine = 'BarcodeDetector';
          els.apiStatus.textContent = 'OK – BarcodeDetector';
          setAlert('info', 'Scanner bruger browserens <code>BarcodeDetector</code>. Vælg kamera og tryk <strong>Start scanner</strong>.');
          return true;
        }
      } catch (_err) {
        try {
          detector = new window.BarcodeDetector({ formats: ['qr_code'] });
          scannerEngine = 'BarcodeDetector';
          els.apiStatus.textContent = 'OK – BarcodeDetector';
          setAlert('info', 'Scanner bruger browserens <code>BarcodeDetector</code>. Vælg kamera og tryk <strong>Start scanner</strong>.');
          return true;
        } catch (_err2) {
          // Fortsæt til fejlbesked nedenfor.
        }
      }
    }

    els.apiStatus.textContent = 'Ingen QR-motor';
    setAlert('danger', 'Der blev ikke fundet en QR-scanner-motor. Kontrollér at <code>js/jsQR.js</code> findes og indlæses før <code>js/qr-scanner-app.js</code>.');
    els.startBtn.disabled = true;
    els.refreshBtn.disabled = true;
    return false;
  }

  async function requestTemporaryCameraAccess() {
    const tmpStream = await navigator.mediaDevices.getUserMedia({
      video: true,
      audio: false,
    });

    hasRequestedCameraPermission = true;
    tmpStream.getTracks().forEach((track) => track.stop());
  }

  async function loadCameras() {
    if (!('mediaDevices' in navigator) || !('enumerateDevices' in navigator.mediaDevices)) {
      els.cameraSelect.innerHTML = '<option value="">Kamera-enumerering understøttes ikke</option>';
      return;
    }

    els.cameraSelect.innerHTML = '<option value="">Søger efter kameraer...</option>';

    try {
      if (!hasRequestedCameraPermission) {
        await requestTemporaryCameraAccess();
      }

      const devices = await navigator.mediaDevices.enumerateDevices();
      const cameras = devices.filter((device) => device.kind === 'videoinput');

      els.cameraSelect.innerHTML = '';

      if (cameras.length === 0) {
        els.cameraSelect.innerHTML = '<option value="">Ingen kameraer fundet</option>';
        setAlert('warning', 'Der blev ikke fundet et webcam. Kontrollér USB-webcam, Windows kameraindstillinger og browserens kamera-tilladelse.');
        return;
      }

      cameras.forEach((camera, index) => {
        const option = document.createElement('option');
        option.value = camera.deviceId;
        option.textContent = camera.label || `Kamera ${index + 1}`;
        els.cameraSelect.appendChild(option);
      });

      setAlert('info', `Fandt ${cameras.length} kamera(er). Vælg kamera og tryk <strong>Start scanner</strong>.`);
    } catch (err) {
      els.cameraSelect.innerHTML = '<option value="">Kameraadgang fejlede</option>';
      setAlert('danger', `Kunne ikke hente kameraer: ${escapeHtml(err.name || 'Fejl')} – ${escapeHtml(err.message || String(err))}. Kontrollér kameraadgang i Chrome: Site settings → Camera.`);
    }
  }

  function buildVideoConstraints() {
    const selectedDeviceId = els.cameraSelect.value;

    if (selectedDeviceId) {
      return {
        audio: false,
        video: {
          deviceId: { exact: selectedDeviceId },
          width: { ideal: 1280 },
          height: { ideal: 720 },
        },
      };
    }

    return {
      audio: false,
      video: {
        width: { ideal: 1280 },
        height: { ideal: 720 },
      },
    };
  }

  async function startScanner() {
    if (!scannerEngine) {
      return;
    }

    await stopScanner(false);

    try {
      stream = await navigator.mediaDevices.getUserMedia(buildVideoConstraints());
      hasRequestedCameraPermission = true;
      els.video.srcObject = stream;
      await els.video.play();

      if (els.video.videoWidth === 0 || els.video.videoHeight === 0) {
        await new Promise((resolve) => {
          els.video.onloadedmetadata = resolve;
        });
      }

      isScanning = true;
      els.startBtn.disabled = true;
      els.stopBtn.disabled = false;
      els.refreshBtn.disabled = true;
      els.cameraSelect.disabled = true;

      setStatus('Scanner aktiv');
      setBadge('Scanner', 'text-bg-success');
      setAlert('success', 'Scanner er startet. Hold QR-koden roligt foran kameraet.');

      scanLoop();
    } catch (err) {
      setAlert('danger', `Kunne ikke starte kamera: ${escapeHtml(err.name || 'Fejl')} – ${escapeHtml(err.message || String(err))}`);
      setStatus('Fejl ved start');
      setBadge('Fejl', 'text-bg-danger');
    }
  }

  async function stopScanner(showMessage = true) {
    isScanning = false;

    if (scanTimer) {
      window.clearTimeout(scanTimer);
      scanTimer = null;
    }

    if (stream) {
      stream.getTracks().forEach((track) => track.stop());
      stream = null;
    }

    els.video.pause();
    els.video.srcObject = null;

    els.startBtn.disabled = scannerEngine === '';
    els.stopBtn.disabled = true;
    els.refreshBtn.disabled = scannerEngine === '';
    els.cameraSelect.disabled = false;

    setStatus('Stoppet');
    setBadge('Standby', 'text-bg-secondary');

    if (showMessage) {
      setAlert('info', 'Scanner er stoppet.');
    }
  }

  async function detectWithBarcodeDetector() {
    const barcodes = await detector.detect(els.video);
    const qr = barcodes.find((barcode) => barcode.format === 'qr_code');
    return qr && qr.rawValue ? qr.rawValue : '';
  }

  function detectWithJsQr() {
    const width = els.video.videoWidth;
    const height = els.video.videoHeight;

    if (!width || !height) {
      return '';
    }

    const ctx = els.canvas.getContext('2d', { willReadFrequently: true });
    els.canvas.width = width;
    els.canvas.height = height;
    ctx.drawImage(els.video, 0, 0, width, height);

    const imageData = ctx.getImageData(0, 0, width, height);
    const code = window.jsQR(imageData.data, width, height, {
      inversionAttempts: 'attemptBoth',
    });

    return code && code.data ? code.data : '';
  }

  async function scanLoop() {
    if (!isScanning || !scannerEngine) {
      return;
    }

    try {
      if (els.video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
        let value = '';

        if (scannerEngine === 'jsQR') {
          value = detectWithJsQr();
        } else if (scannerEngine === 'BarcodeDetector' && detector) {
          value = await detectWithBarcodeDetector();
        }

        if (value && value !== lastValue) {
          setResult(value, !IS_PUBLIC_DEMO);
          setStatus('QR-kode læst');
          setBadge('Fundet', 'text-bg-primary');
          setAlert(
            'success',
            IS_PUBLIC_DEMO
              ? 'QR-kode fundet. Resultatet gemmes ikke; kopiér det nederst på siden, hvis du vil beholde det.'
              : 'QR-kode fundet. Resultatet er vist nederst på siden og gemt i lokal historik.'
          );
        }
      }
    } catch (_err) {
      // Enkeltframes kan fejle. Scanner fortsætter.
    }

    scanTimer = window.setTimeout(scanLoop, scannerEngine === 'jsQR' ? 180 : 250);
  }

  async function copyResult() {
    const value = els.result.value.trim();
    if (value === '') {
      return;
    }

    const ok = await copyText(value);
    if (ok) {
      setAlert('success', 'Resultatet er kopieret til clipboard.');
    } else {
      els.result.focus();
      els.result.select();
      setAlert('warning', 'Automatisk kopiering fejlede. Resultatet er markeret, så du kan kopiere manuelt med Ctrl+C.');
    }
  }

  function clearResult() {
    setResult('', false);
    setStatus(isScanning ? 'Scanner aktiv' : 'Stoppet');
    setBadge(isScanning ? 'Scanner' : 'Standby', isScanning ? 'text-bg-success' : 'text-bg-secondary');
  }

  async function handleHistoryClick(event) {
    const button = event.target.closest('[data-history-action]');
    if (!button) {
      return;
    }

    const index = Number.parseInt(button.getAttribute('data-index') || '-1', 10);
    const action = button.getAttribute('data-history-action');
    const item = loadHistory()[index];

    if (!item || typeof item.value !== 'string') {
      return;
    }

    if (action === 'copy') {
      const ok = await copyText(item.value);
      setAlert(ok ? 'success' : 'warning', ok ? 'Historikpost kopieret til clipboard.' : 'Kunne ikke kopiere historikpost automatisk.');
    }

    if (action === 'use') {
      setResult(item.value, false);
      setAlert('info', 'Historikpost er vist i resultatfeltet.');
    }
  }

  function clearHistory() {
    window.localStorage.removeItem(HISTORY_KEY);
    renderHistory();
    setAlert('info', 'Lokal scanner-historik er ryddet.');
  }

  async function init() {
    els.startBtn.disabled = true;
    els.stopBtn.disabled = true;
    els.refreshBtn.disabled = true;

    els.startBtn.addEventListener('click', startScanner);
    els.stopBtn.addEventListener('click', () => stopScanner(true));
    els.refreshBtn.addEventListener('click', loadCameras);
    els.copyBtn.addEventListener('click', copyResult);
    els.clearBtn.addEventListener('click', clearResult);
    if (!IS_PUBLIC_DEMO) {
      els.scanHistory.addEventListener('click', handleHistoryClick);
      els.clearHistoryBtn.addEventListener('click', clearHistory);
    }

    renderHistory();

    const engineReady = await setupScannerEngine();
    if (engineReady) {
      els.startBtn.disabled = false;
      els.refreshBtn.disabled = false;
      await loadCameras();
    }

    if ('mediaDevices' in navigator && 'addEventListener' in navigator.mediaDevices) {
      navigator.mediaDevices.addEventListener('devicechange', () => {
        if (!isScanning) {
          loadCameras();
        }
      });
    }

    window.addEventListener('beforeunload', () => {
      if (stream) {
        stream.getTracks().forEach((track) => track.stop());
      }
    });
  }

  init();
})();
