{% extends "admin_base.html" %}
{% block title %}QR Scanner | Nexora Admin{% endblock %}
{% block content %}
<div class="admin-head">
  <p class="eyebrow">QR Verification</p>
  <h1>QR Ticket Scanner</h1>
  <p>Scan a player ticket QR code. The ticket opens in a popup where you can <strong>Approve</strong> (allow entry) or
    <strong>Reject</strong> (deny entry).
  </p>
</div>

<section class="admin-panel qr-admin-panel">

  <!-- mode switch -->
  <div class="qr-mode-switch">
    {% if can('scan_qr_codes') %}<button class="qr-mode-btn active" type="button" data-qr-mode="camera"
      data-qr-mode-btn>
      <i class="fas fa-camera"></i>
      <span>Camera QR</span>
    </button>{% endif %}
    {% if can('manually_verify_tickets') %}<button
      class="qr-mode-btn {% if not can('scan_qr_codes') %}active{% endif %}" type="button" data-qr-mode="input"
      data-qr-mode-btn>
      <i class="fas fa-keyboard"></i>
      <span>Manual Input</span>
    </button>{% endif %}
  </div>

  <div class="qr-station">

    <!-- -- MAIN: LIVE CAMERA -- -->
    {% if can('scan_qr_codes') %}<div class="qr-stage" data-qr-panel="camera">
      <div class="qr-viewfinder" data-viewfinder>
        <div id="qr-reader"></div>
        <span class="qr-reticle" aria-hidden="true"></span>
        <span class="qr-scanline" aria-hidden="true"></span>

        <span class="qr-cam-chip" data-cam-chip data-state="idle">
          <i class="fas fa-circle"></i><span data-cam-chip-text>Camera Ready</span>
        </span>

        <div class="qr-cam-idle" data-cam-idle>
          <i class="fas fa-video"></i>
          <strong>Camera Ready</strong>
          <small>Grant camera access to start live QR scanning.</small>
          <button class="btn primary" type="button" data-start-scan>
            <i class="fas fa-camera"></i> Allow Camera
          </button>
        </div>

        <div class="qr-cam-error" data-cam-error hidden>
          <i class="fas fa-triangle-exclamation"></i>
          <p data-cam-error-msg></p>
          <button class="btn outline small" type="button" data-cam-retry>Try Again</button>
        </div>
      </div>

      <div class="qr-stage-bar">
        <span class="qr-live-indicator" data-live-indicator><i class="fas fa-circle"></i> Scanner idle</span>
        <button class="btn outline small" type="button" data-stop-scan><i class="fas fa-stop"></i> Stop Camera</button>
      </div>
    </div>{% endif %}

    <!-- -- SIDE: CONTROLS -- -->
    <aside class="qr-side">
      <div class="qr-permission-strip">
        <div class="qr-perm-item" data-perm-camera>
          <span class="qr-perm-icon"><i class="fas fa-camera"></i></span>
          <div class="qr-perm-text">
            <strong>Camera</strong>
            <small data-perm-camera-state>Access needed for live scan</small>
          </div>
          <button class="btn outline small" type="button" data-allow-camera>Allow</button>
        </div>
        <div class="qr-perm-item" data-perm-sound>
          <span class="qr-perm-icon"><i class="fas fa-volume-high"></i></span>
          <div class="qr-perm-text">
            <strong>Scan Sound</strong>
            <small data-perm-sound-state>Tap allow to enable beep</small>
          </div>
          <button class="btn outline small" type="button" data-allow-sound>Allow</button>
        </div>
      </div>

      <div class="qr-side-card qr-status-card">
        <p class="qr-side-label"><i class="fas fa-signal"></i> Scanner Status</p>
        <span class="qr-status-pill" data-scan-status data-state="waiting">WAITING</span>
        <small class="qr-status-note" data-scan-status-note>Waiting for camera permission.</small>
      </div>
    </aside>
  </div>

  <!-- -- MANUAL INPUT (full width, dark) -- -->
  {% if can('manually_verify_tickets') %}<section class="qr-side-card qr-manuel-dark qr-manual-panel"
    data-qr-panel="input" style="{% if can('scan_qr_codes') %}display:none;{% endif %}">
    <p class="qr-side-label"><i class="fas fa-keyboard"></i> Manual Input</p>
    <div class="manual-scan">
      <label>Ticket Number / Serial / QR Token
        <input data-manual-token placeholder="e.g. NX-0007 or NX-TICKET-0007" autocomplete="off"
          autocapitalize="characters">
      </label>
      <button class="btn primary" type="button" data-manual-verify><i class="fas fa-magnifying-glass"></i>
        Check Ticket</button>
      <small class="manual-scan-hint">Works with the ticket number printed on the PDF, the serial, or a scanned QR
        token.</small>
    </div>
  </section>{% endif %}

  <!-- -- LAST RESULT -- -->
  <aside class="qr-result-card" data-result-card>
    <span class="badge neutral">Waiting</span>
    <h2>Scan a ticket</h2>
    <p>The last scan result stays here for reference.</p>
    <div class="qr-result-details" data-result-details></div>
  </aside>
</section>
<!-- TICKET OVERLAY -->
<div class="qr-overlay" data-qr-overlay aria-hidden="true" role="dialog" aria-modal="true">
  <div class="qr-overlay-card" data-qr-overlay-card>
    <button class="qr-overlay-close" type="button" data-qr-close aria-label="Close ticket">
      <i class="fas fa-xmark"></i>
    </button>

    <div class="qr-overlay-head" data-qr-head>
      <span class="qr-overlay-badge" data-qr-badge>Result</span>
      <h2 data-qr-title>Ticket</h2>
      <p data-qr-sub></p>
    </div>

    <div class="qr-overlay-body" data-qr-body></div>

    <div class="qr-decision-fields" data-qr-decision-fields>
      <label>Check-in Gate / Location
        <input data-qr-gate placeholder="e.g. Main Gate" value="Main Gate" maxlength="80">
      </label>
      <label>Reject Reason
        <select data-qr-reason>
          <option value="">Select a reason</option>
          <option>Unpaid ticket</option>
          <option>Invalid ticket</option>
          <option>Duplicate entry</option>
          <option>Cancelled registration</option>
          <option>Wrong event/game</option>
          <option>Suspicious ticket</option>
          <option>Other</option>
        </select>
      </label>
      <label class="qr-override-field" data-qr-override-wrap>
        <input type="checkbox" data-qr-override> Admin override for an already-used ticket
      </label>
      <label data-qr-override-reason-wrap style="display:none;">Override reason
        <input data-qr-override-reason placeholder="Required for admin override" maxlength="160">
      </label>
    </div>
    <div class="qr-overlay-actions" data-qr-actions>
      <button class="qr-btn confirm-payment" type="button" data-qr-confirm-payment
        {% if not can('confirm_payments') %}style="display:none" {% endif %}>
        <i class="fas fa-circle-check"></i> Confirm Payment
      </button>
      <button class="qr-btn approve" type="button" data-qr-approve {% if not can('approve_entry') %}style="display:none"
        {% endif %}>
        <i class="fas fa-check"></i> Approve Entry
      </button>
      <button class="qr-btn reject" type="button" data-qr-reject {% if not can('reject_entry') %}style="display:none" {%
        endif %}>
        <i class="fas fa-xmark"></i> Reject Entry
      </button>
      <button class="qr-btn team-approve" type="button" data-qr-approve-team {% if not can('approve_entry')
        %}style="display:none" {% endif %}>
        <i class="fas fa-users"></i> Check In Entire Team
      </button>
      <button class="qr-btn scan-next" type="button" data-qr-scan-next>
        <i class="fas fa-qrcode"></i> Scan Next Ticket
      </button>
    </div>
    <p class="qr-overlay-note" data-qr-note></p>
  </div>
</div>

<script src="https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js"></script>
<script>
  (function () {
    "use strict";

    const readerId = "qr-reader";
    const csrfToken = {{ csrf_token() | tojson }};
    const verifyUrl = {{ url_for('verify_qr_ticket') | tojson
  }};
  const decisionUrl = {{ url_for('qr_entry_decision') | tojson }};
  const confirmPaymentUrl = {{ url_for('qr_confirm_payment') | tojson }};
  const initialToken = {{ (request.args.get('token') or '') | tojson }};
  const canApprove = {{ can('approve_entry') | tojson }};
  const canReject = {{ can('reject_entry') | tojson }};
  const canOverride = {{ can('override_duplicate_entry') | tojson }};
  const canConfirmPayment = {{ can('confirm_payments') | tojson }};
  const canViewRecord = {{ can('view_scanned_record') | tojson }};

  const resultCard = document.querySelector("[data-result-card]");
  const manualInput = document.querySelector("[data-manual-token]");
  const startButton = document.querySelector("[data-start-scan]");
  const stopButton = document.querySelector("[data-stop-scan]");
  const manualButton = document.querySelector("[data-manual-verify]");

  const overlay = document.querySelector("[data-qr-overlay]");
  const overlayCard = document.querySelector("[data-qr-overlay-card]");
  const overlayHead = document.querySelector("[data-qr-head]");
  const overlayBadge = document.querySelector("[data-qr-badge]");
  const overlayTitle = document.querySelector("[data-qr-title]");
  const overlaySub = document.querySelector("[data-qr-sub]");
  const overlayBody = document.querySelector("[data-qr-body]");
  const overlayActions = document.querySelector("[data-qr-actions]");
  const overlayNote = document.querySelector("[data-qr-note]");
  const decisionFields = document.querySelector("[data-qr-decision-fields]");
  const gateInput = document.querySelector("[data-qr-gate]");
  const reasonInput = document.querySelector("[data-qr-reason]");
  const overrideInput = document.querySelector("[data-qr-override]");
  const overrideWrap = document.querySelector("[data-qr-override-wrap]");
  const overrideReasonWrap = document.querySelector("[data-qr-override-reason-wrap]");
  const overrideReasonInput = document.querySelector("[data-qr-override-reason]");
  const closeBtn = document.querySelector("[data-qr-close]");
  const approveBtn = document.querySelector("[data-qr-approve]");
  const rejectBtn = document.querySelector("[data-qr-reject]");
  const approveTeamBtn = document.querySelector("[data-qr-approve-team]");
  const scanNextBtn = document.querySelector("[data-qr-scan-next]");
  const confirmPaymentBtn = document.querySelector("[data-qr-confirm-payment]");

  const allowCameraBtn = document.querySelector("[data-allow-camera]");
  const allowSoundBtn = document.querySelector("[data-allow-sound]");
  const cameraBox = document.querySelector("[data-perm-camera]");
  const soundBox = document.querySelector("[data-perm-sound]");
  const cameraState = document.querySelector("[data-perm-camera-state]");
  const soundState = document.querySelector("[data-perm-sound-state]");

  let scanner = null;
  let busy = false;
  let currentToken = "";
  let resumeScannerAfterClose = false;
  let audioCtx = null;
  let soundReady = false;

  /* ── SCANNER UI STATE HELPERS (visual only — no logic changes) ── */
  const viewfinder = document.querySelector("[data-viewfinder]");
  const camChip = document.querySelector("[data-cam-chip]");
  const camChipText = document.querySelector("[data-cam-chip-text]");
  const camIdle = document.querySelector("[data-cam-idle]");
  const camError = document.querySelector("[data-cam-error]");
  const camErrorMsg = document.querySelector("[data-cam-error-msg]");
  const liveIndicator = document.querySelector("[data-live-indicator]");
  const scanStatusPill = document.querySelector("[data-scan-status]");
  const scanStatusNote = document.querySelector("[data-scan-status-note]");
  const retryBtn = document.querySelector("[data-cam-retry]");

  function setCamChip(state, text) {
    if (!camChip) return;
    camChip.dataset.state = state;
    if (camChipText && text) camChipText.textContent = text;
  }

  function setViewfinderActive(active) {
    if (!viewfinder) return;
    viewfinder.classList.toggle("is-active", !!active);
    viewfinder.classList.toggle("is-idle", !active);
    if (camIdle) camIdle.style.display = active ? "none" : "";
    if (camError) camError.hidden = true;
  }

  function showCamError(message) {
    setViewfinderActive(false);
    if (!camError) return;
    camErrorMsg.textContent = message || "Camera could not be started.";
    camError.hidden = false;
  }

  function showCamErrorHTML(html) {
    setViewfinderActive(false);
    if (!camError) return;
    camErrorMsg.innerHTML = html;
    camError.hidden = false;
  }

  /* Plain-HTTP LAN: browsers hard-block camera access and hide
     navigator.mediaDevices. No script can bypass it — so give the
     operator exact working options instead of a dead end. */
  function httpsUrlHere() {
    /* Dual-mode server: HTTP lives on 5000, camera-enabled HTTPS mirror on 5443 */
    var host = location.host;
    if (location.protocol === "http:") {
      host = host.replace(/:5000$/i, "") + ":5443";
    }
    return "https://" + host + location.pathname + location.search;
  }

  function showInsecureHelp() {
    var url = httpsUrlHere();
    setScanStatus("CAMERA_ERROR", "HTTPS required for camera on LAN.");
    cameraState.textContent = "Needs HTTPS";
    showCamErrorHTML(
      '<div class="qr-insecure-help">' +
      '<strong><i class="fas fa-lock"></i> Camera needs a secure connection</strong>' +
      '<p>Browsers block the camera on plain-HTTP LAN addresses like <code>' + esc(location.host) + '</code>.</p>' +
      '<a class="qr-https-btn" href="' + esc(url) + '"><i class="fas fa-bolt"></i> Switch to secure camera mode</a>' +
      '<ol>' +
      '<li><strong>Recommended:</strong> tap the button above, then accept the certificate warning once (Advanced → Proceed). The scanner opens with the live camera on this same device.</li>' +
      '<li>On the server PC you can also scan at <code>http://localhost:5000/admin/qr-scanner</code> — localhost is always allowed.</li>' +
      '</ol>' +
      '<small>Manual Input below keeps working either way.</small>' +
      '</div>'
    );
  }

  function setScanStatus(state, note) {
    if (scanStatusPill) {
      scanStatusPill.dataset.state = state;
      scanStatusPill.textContent = state.replace(/_/g, " ");
    }
    if (scanStatusNote && note) scanStatusNote.textContent = note;
  }

  function setLive(on, label) {
    if (!liveIndicator) return;
    liveIndicator.classList.toggle("on", !!on);
    liveIndicator.innerHTML =
      '<i class="fas fa-circle"></i> ' + (label || (on ? "Scanning for QR…" : "Scanner idle"));
  }

  /* ── SOUND ──
     Browsers block audio until a real user gesture, so we unlock on tap. */
  function unlockSound() {
    try {
      const Ctx = window.AudioContext || window.webkitAudioContext;
      if (!Ctx) {
        soundState.textContent = "Not supported on this browser";
        return;
      }
      audioCtx = audioCtx || new Ctx();
      if (audioCtx.state === "suspended") audioCtx.resume();
      soundReady = true;
      soundState.textContent = "Enabled";
      soundBox.classList.add("granted");
      beep("ready");
    } catch (err) {
      soundState.textContent = "Could not enable sound";
    }
  }

  function beep(kind) {
    if (!soundReady || !audioCtx) return;
    if (audioCtx.state === "suspended") audioCtx.resume();

    var tones, vol;
    if (kind === "approve") {
      tones = [[523, 0], [659, 0.1], [784, 0.2]];
      vol = 0.35;
    } else if (kind === "reject") {
      tones = [[440, 0], [330, 0.12], [262, 0.24]];
      vol = 0.30;
    } else if (kind === "error") {
      tones = [[330, 0], [330, 0.15], [330, 0.30]];
      vol = 0.25;
    } else {
      tones = [[880, 0], [1100, 0.08]];
      vol = 0.35;
    }

    tones.forEach(function (pair) {
      var osc = audioCtx.createOscillator();
      var gain = audioCtx.createGain();
      osc.type = "sine";
      osc.frequency.value = pair[0];
      var t0 = audioCtx.currentTime + pair[1];
      gain.gain.setValueAtTime(0.0001, t0);
      gain.gain.exponentialRampToValueAtTime(vol, t0 + 0.015);
      gain.gain.exponentialRampToValueAtTime(0.0001, t0 + 0.18);
      osc.connect(gain).connect(audioCtx.destination);
      osc.start(t0);
      osc.stop(t0 + 0.22);
    });

    if (navigator.vibrate) {
      navigator.vibrate(kind === "reject" || kind === "error" ? [90, 60, 90] : 60);
    }
  }

  /* ── OVERLAY ── */
  function esc(value) {
    return String(value === undefined || value === null ? "" : value)
      .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;").replace(/'/g, "&#39;");
  }

  function line(label, value) {
    return value ? '<div class="qr-line"><span>' + esc(label) + '</span><strong>' + esc(value) + '</strong></div>' : "";
  }

  function openOverlay() {
    resumeScannerAfterClose = !!(scanner && scanner.isScanning);
    stopScanner();
    overlay.classList.add("open");
    overlay.setAttribute("aria-hidden", "false");
    document.body.classList.add("qr-overlay-lock");
    history.pushState({ qrOverlay: true }, "");
  }

  function closeOverlay(skipHistory) {
    if (!overlay.classList.contains("open")) return;
    overlay.classList.remove("open");
    overlay.setAttribute("aria-hidden", "true");
    document.body.classList.remove("qr-overlay-lock");
    currentToken = "";
    busy = false;
    if (!skipHistory && history.state && history.state.qrOverlay) {
      history.back();
    }
    if (resumeScannerAfterClose) {
      resumeScannerAfterClose = false;
      setTimeout(startScanner, 260);
    }
  }

  window.addEventListener("popstate", function (e) {
    if (overlay.classList.contains("open")) {
      closeOverlay(true);
    }
  });

  function paintOverlay(data) {
    const status = data.status || "invalid";
    const payRaw = data.payment_status_raw || (data.payment_status || "").toLowerCase();
    const entRaw = data.entry_status_raw || (data.entry_status || "").toLowerCase();
    overlayCard.dataset.state = status;
    overlayHead.dataset.state = status;

    overlayBadge.textContent =
      status === "approved" ? "APPROVED" :
        status === "rejected" ? "REJECTED" :
          status === "already" ? "ALREADY CHECKED IN" :
            status === "payment_required" ? "UNPAID" :
              status === "paid_ready" ? "PAID — ENTRY READY" :
                status === "camera" ? "CAMERA UNAVAILABLE" :
                  status === "found" ? "TICKET FOUND" : "INVALID";

    overlayTitle.textContent = data.title || data.ticket_number || "Invalid Ticket";
    overlaySub.textContent = data.message || "";

    if (!data.ok) {
      overlayBody.innerHTML =
        '<div class="qr-invalid-box"><i class="fas fa-triangle-exclamation"></i><p>' +
        esc(data.message || "This QR code does not match any Nexora ticket.") + '</p></div>';
      overlayActions.style.display = "none";
      decisionFields.style.display = "none";
      confirmPaymentBtn.style.display = "none";
      overlayNote.textContent = "Ask the player to show the original ticket email.";
      return;
    }

    var paymentBadge = "";
    if (payRaw === "paid") {
      paymentBadge = '<span class="qr-pay-badge paid"><i class="fas fa-circle-check"></i> PAID</span>';
    } else if (payRaw === "under_verification") {
      paymentBadge = '<span class="qr-pay-badge verifying"><i class="fas fa-clock"></i> UNDER VERIFICATION</span>';
    } else {
      paymentBadge = '<span class="qr-pay-badge unpaid"><i class="fas fa-circle-xmark"></i> UNPAID</span>';
    }

    overlayBody.innerHTML =
      '<div class="qr-person">' +
      '<span class="qr-avatar">' + esc((data.name || "?").charAt(0).toUpperCase()) + '</span>' +
      '<div><strong>' + esc(data.name) + '</strong>' +
      '<small>' + esc(data.ref) + ' &middot; ' + esc(data.entry_type) + '</small></div></div>' +
      '<div class="qr-pay-row">' + paymentBadge +
      '<span class="qr-status-chip ' + entRaw + '">' + esc(data.ticket_status || data.entry_status || "Pending") + '</span>' +
      '</div>' +
      (data.amount_paid || data.payment_due
        ? '<div class="qr-amount-row" style="padding:10px 0 4px;display:flex;gap:14px;font-size:13px;">' +
          (data.amount_paid ? '<span style="color:#16a34a;font-weight:700;"><i class="fas fa-circle-check"></i> Paid: ' + esc(data.amount_paid) + '</span>' : '') +
          (data.payment_due && data.payment_due !== "Rs. 0" && data.payment_due !== "Pending"
            ? '<span style="color:#dc2626;font-weight:700;"><i class="fas fa-clock"></i> Due: ' + esc(data.payment_due) + '</span>'
            : (data.payment_due === "Pending" ? '<span style="color:#d97706;font-weight:700;"><i class="fas fa-clock"></i> Payment: Pending</span>' : '')) +
          '</div>' : '') +
      (canViewRecord
        ? '<div class="qr-line-grid">' +
          line("Email", data.email) +
          line("Contact", data.phone) +
          line("CNIC", data.cnic) +
          line("Institution", data.institution) +
          line("Game", data.game) +
          line("Platform", data.platform) +
          line("Team", data.team_name) +
          line("Team ID", data.team_id) +
          line("Team Members", (data.team_members || []).join(", ")) +
          line("Group Leader", data.group_leader) +
          line("Total Members", data.total_members) +
          line("Team Checked In", data.team_checked_in) +
          line("Team Remaining", data.team_remaining) +
          line("Team Rejected", data.team_rejected) +
          line("Check-in", data.checkin_status) +
          line("Checked In At", data.entry_marked_at) +
          line("Gate", data.checkin_gate) +
          line("Approved By", data.checked_in_by) +
          line("Reject Reason", data.rejection_reason) +
          line("Applied", data.applied_at) +
          '</div>'
        : '<div class="qr-line-grid">' +
          line("Game", data.game) +
          line("Entry", data.entry_type) +
          '<div class="qr-record-restricted"><i class="fas fa-lock"></i> Full record details require View Record permission.</div>' +
          '</div>'
      ) +
      (data.proof_url && canViewRecord
        ? '<a class="qr-proof-link" href="' + esc(data.proof_url) + '" target="_blank"><i class="fas fa-image"></i> Open payment proof</a>'
        : "");

    var decided = status === "approved" || status === "rejected";
    decisionFields.style.display = decided ? "none" : "grid";
    overlayActions.style.display = decided ? "none" : "grid";

    /* Button visibility per state */
    if (status === "payment_required") {
      confirmPaymentBtn.style.display = canConfirmPayment ? "" : "none";
      approveBtn.style.display = "none";
      rejectBtn.style.display = canReject ? "" : "none";
      approveTeamBtn.style.display = "none";
    } else if (status === "paid_ready") {
      confirmPaymentBtn.style.display = "none";
      approveBtn.style.display = canApprove ? "" : "none";
      rejectBtn.style.display = canReject ? "" : "none";
      approveTeamBtn.style.display = canApprove && (data.entry_type || "").toLowerCase() === "group" ? "" : "none";
    } else if (status === "found") {
      confirmPaymentBtn.style.display = "none";
      approveBtn.style.display = canApprove ? "" : "none";
      rejectBtn.style.display = canReject ? "" : "none";
      approveTeamBtn.style.display = canApprove && (data.entry_type || "").toLowerCase() === "group" ? "" : "none";
    } else if (status === "already") {
      decisionFields.style.display = "grid";
      confirmPaymentBtn.style.display = "none";
      approveBtn.style.display = canApprove ? "" : "none";
      rejectBtn.style.display = "none";
      approveTeamBtn.style.display = "none";
      overrideWrap.style.display = canOverride ? "flex" : "none";
    } else {
      confirmPaymentBtn.style.display = "none";
      approveBtn.style.display = "none";
      rejectBtn.style.display = "none";
      approveTeamBtn.style.display = "none";
    }

    if (status !== "already") {
      overrideWrap.style.display = "none";
      overrideInput.checked = false;
      overrideReasonWrap.style.display = "none";
    }
    scanNextBtn.style.display = decided ? "" : "none";

    /* Status-specific notes */
    if (status === "approved") {
      overlayNote.textContent = "Entry allowed. Let the player in.";
    } else if (status === "rejected") {
      overlayNote.textContent = "Entry denied. This ticket has been rejected.";
    } else if (status === "already") {
      overlayNote.textContent = "This ticket has already been used. Only an authorized admin override can approve it again.";
    } else if (status === "payment_required") {
      overlayNote.textContent = "Payment not confirmed for this ticket. Confirm payment first, then approve entry.";
    } else if (status === "paid_ready") {
      overlayNote.textContent = "Payment verified. Press Approve to allow entry, or Reject to deny it.";
    } else {
      overlayNote.textContent = "Choose an action for this ticket.";
    }
  }

  function paintSideCard(data) {
    const cls = data.status === "approved" ? "paid"
      : (data.status === "rejected" || data.status === "invalid") ? "unpaid" : "neutral";
    var details = canViewRecord
      ? line("Game", data.game) + line("Entry", data.entry_type) + line("Payment", data.payment_status) + line("Amount Paid", data.amount_paid) + line("Amount Due", data.payment_due) + line("Decision", (data.entry_status || "").toUpperCase())
      : line("Game", data.game) + line("Entry", data.entry_type) + line("Payment", data.payment_status) + line("Amount Paid", data.amount_paid) + line("Amount Due", data.payment_due) + '<div class="qr-record-restricted"><i class="fas fa-lock"></i> Record details restricted.</div>';
    resultCard.innerHTML =
      '<span class="badge ' + cls + '">' + esc(data.message || "Result") + '</span>' +
      '<h2>' + esc(data.title || data.ticket_number || "Invalid Ticket") + '</h2>' +
      '<p>' + esc(data.name || data.message || "QR token did not match any ticket.") + '</p>' +
      '<div class="qr-result-details">' + details + '</div>';
  }

  /* ── NETWORK ── */
  function verifyToken(token) {
    token = (token || "").trim();
    if (!token || busy) return;
    busy = true;
    setScanStatus("VERIFYING", "Checking ticket with the server…");

    fetch(verifyUrl, {
      method: "POST",
      headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
      body: JSON.stringify({ token: token })
    })
      .then(function (response) {
        return response.json().catch(function () { return {}; });
      })
      .then(function (data) {
        if (data.ok) {
          currentToken = data.token || token;
          beep("ready");
        } else {
          currentToken = "";
          beep("error");
        }
        paintOverlay(data);
        paintSideCard(data);
        openOverlay();
        /* visual status only */
        var st = data.status || "invalid";
        if (st === "approved") setScanStatus("VERIFIED", (data.name || "Ticket") + " — entry approved.");
        else if (st === "rejected") setScanStatus("REJECTED", (data.name || "Ticket") + " — entry rejected.");
        else if (st === "already") setScanStatus("QR_DETECTED", "Already-used ticket — override needed.");
        else if (st === "payment_required") setScanStatus("UNPAID", (data.name || "Ticket") + " — payment not confirmed.");
        else if (st === "paid_ready") setScanStatus("PAID", "Payment verified — ready for entry decision.");
        else if (data.ok) setScanStatus("QR_DETECTED", "Ticket found. Awaiting decision.");
        else setScanStatus("SCANNING", "Unrecognised code. Keep scanning…");
      })
      .catch(function () {
        beep("error");
        paintOverlay({ ok: false, status: "invalid", message: "Network error while checking the ticket." });
        openOverlay();
      })
      .finally(function () {
        setTimeout(function () { busy = false; }, 800);
      });
  }

  function sendDecision(decision) {
    if (!currentToken) return;
    if (!soundReady) unlockSound();
    approveBtn.disabled = true;
    rejectBtn.disabled = true;
    approveTeamBtn.disabled = true;

    const isOverride = !!overrideInput.checked;
    const reason = reasonInput.value;
    if (decision === "rejected" && !reason) {
      overlayNote.textContent = "Select a rejection reason before rejecting this ticket.";
      approveBtn.disabled = false;
      rejectBtn.disabled = false;
      approveTeamBtn.disabled = false;
      return;
    }
    if (isOverride && !overrideReasonInput.value.trim()) {
      overlayNote.textContent = "Enter an override reason before approving an already-used ticket.";
      approveBtn.disabled = false;
      rejectBtn.disabled = false;
      approveTeamBtn.disabled = false;
      return;
    }
    fetch(decisionUrl, {
      method: "POST",
      headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
      body: JSON.stringify({
        token: currentToken,
        decision: decision,
        reason: reason,
        gate: gateInput.value.trim(),
        override: isOverride,
        override_reason: overrideReasonInput.value.trim()
      })
    })
      .then(function (response) {
        return response.json().catch(function () { return {}; });
      })
      .then(function (data) {
        beep(decision.indexOf("approved") === 0 ? "approve" : "reject");
        paintOverlay(data);
        paintSideCard(data);
        setTimeout(function () {
          if (overlay.classList.contains("open") && currentToken === data.token) {
            closeOverlay();
          }
        }, 1500);
      })
      .catch(function () {
        beep("error");
        overlayNote.textContent = "Could not save the decision. Please try again.";
      })
      .finally(function () {
        approveBtn.disabled = false;
        rejectBtn.disabled = false;
      });
  }

  function confirmPayment() {
    if (!currentToken) return;
    if (!soundReady) unlockSound();
    confirmPaymentBtn.disabled = true;
    confirmPaymentBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Confirming…';

    fetch(confirmPaymentUrl, {
      method: "POST",
      headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
      body: JSON.stringify({ token: currentToken })
    })
      .then(function (response) {
        return response.json().catch(function () { return {}; });
      })
      .then(function (data) {
        if (data.ok) {
          beep("approve");
          paintOverlay(data);
          paintSideCard(data);
          setScanStatus("VERIFIED", "Payment confirmed. Ticket ready for entry decision.");
        } else {
          beep("error");
          overlayNote.textContent = data.message || "Could not confirm payment. Please try again.";
        }
      })
      .catch(function () {
        beep("error");
        overlayNote.textContent = "Network error while confirming payment.";
      })
      .finally(function () {
        confirmPaymentBtn.disabled = false;
        confirmPaymentBtn.innerHTML = '<i class="fas fa-circle-check"></i> Confirm Payment';
      });
  }

  /* ── CAMERA ── */
  function secureOk() {
    return window.location.protocol === "https:" ||
      window.location.hostname === "localhost" ||
      window.location.hostname === "127.0.0.1";
  }

  function requestCamera() {
    setScanStatus("WAITING", "Requesting camera permission…");
    cameraState.textContent = "Requesting…";

    /* Insecure context (plain HTTP on a LAN IP): browsers hide
       navigator.mediaDevices entirely — surface actionable options. */
    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
      if (!window.isSecureContext) { showInsecureHelp(); return; }
      showCamError("Camera not supported on this device/browser.");
      cameraState.textContent = "Not supported";
      setScanStatus("CAMERA_ERROR", "getUserMedia unavailable.");
      return;
    }

    navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } })
      .then(function (stream) {
        /* Permission granted: keep momentum — hand off straight to
           the live scanner instead of stopping the stream. */
        stream.getTracks().forEach(function (track) { track.stop(); });
        cameraBox.classList.add("granted");
        cameraState.textContent = "Allowed ✓";
        allowCameraBtn.textContent = "Allowed ✓";
        setScanStatus("CAMERA_ACTIVE", "Starting live preview…");
        startScanner();
      })
      .catch(function (err) {
        var name = err && err.name ? err.name : "";
        if (name === "NotAllowedError" || name === "SecurityError") {
          cameraState.textContent = "Denied — enable in site settings";
          setScanStatus("CAMERA_ERROR", "Permission denied by browser.");
          showCamError("Camera access is required to scan QR tickets. Allow the camera in your browser site settings, then press Try Again.");
        } else if (name === "NotFoundError" || name === "OverconstrainedError") {
          cameraState.textContent = "No camera found";
          setScanStatus("CAMERA_ERROR", "No camera detected.");
          showCamError("No camera was found on this device. Use Manual Input below.");
        } else if (name === "NotReadableError") {
          cameraState.textContent = "Camera busy";
          setScanStatus("CAMERA_ERROR", "Camera is in use elsewhere.");
          showCamError("The camera is already being used by another application. Close it, then Try Again.");
        } else {
          cameraState.textContent = "Camera error";
          setScanStatus("CAMERA_ERROR", "Unexpected camera error.");
          showCamError("Could not start the camera. Please Try Again.");
        }
      });
  }

  function startScanner() {
    hideCamError();
    if (!window.Html5Qrcode) {
      setScanStatus("CAMERA_ERROR", "Scanner library did not load.");
      showCamError("The scanner library failed to load. Check your connection and Try Again.");
      return;
    }
    if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
      if (!window.isSecureContext) { showInsecureHelp(); return; }
      setScanStatus("CAMERA_ERROR", "Camera unsupported.");
      showCamError("Camera not supported on this device/browser.");
      return;
    }

    setScanStatus("CAMERA_READY", "Requesting camera…");
    Html5Qrcode.getCameras().then(function (devices) {
      if (!devices || !devices.length) {
        setScanStatus("CAMERA_ERROR", "No camera detected.");
        showCamError("No camera was found on this device. Use Manual Input below.");
        return;
      }

      /* prefer rear / environment camera */
      var cameraId = devices[devices.length - 1].id;
      for (var i = 0; i < devices.length; i++) {
        var label = (devices[i].label || "").toLowerCase();
        if (label.indexOf("back") >= 0 || label.indexOf("rear") >= 0 || label.indexOf("environment") >= 0) {
          cameraId = devices[i].id;
          break;
        }
      }

      cameraBox.classList.add("granted");
      cameraState.textContent = "Allowed ✓";

      /* We recreate the instance to clear its internal cache for duplicate scans */
      if (scanner) {
        try { scanner.clear(); } catch (e) { }
      }
      scanner = new Html5Qrcode(readerId);

      var box = Math.min(300, Math.max(180, Math.floor(Math.min(window.innerWidth * 0.62, 420))));
      setScanStatus("SCANNING", "Looking for a QR code…");
      setLive(true);
      setCamChip("live", "Camera Active — Scan QR Code");
      setViewfinderActive(true);
      cameraState.textContent = "Active ✓";

      return scanner.start(
        { facingMode: "environment" },
        { fps: 10, qrbox: { width: box, height: box } },
        function onSuccess(decodedText) {
          setScanStatus("QR_DETECTED", "QR code captured. Verifying…");
          verifyToken(decodedText);
        }
      ).catch(function () {
        /* facingMode constraint failed on this device — retry with explicit id */
        return scanner.start(
          cameraId,
          { fps: 10, qrbox: { width: box, height: box } },
          function onSuccess2(decodedText2) {
            setScanStatus("QR_DETECTED", "QR code captured. Verifying…");
            verifyToken(decodedText2);
          }
        );
      });
    }).catch(function (err) {
      var name = err && err.name ? err.name : "";
      setLive(false);
      if (name === "NotAllowedError" || name === "SecurityError") {
        cameraState.textContent = "Denied — enable in site settings";
        setScanStatus("CAMERA_ERROR", "Permission denied by browser.");
        showCamError("Camera access is required to scan QR tickets. Allow the camera in your browser site settings, then press Try Again.");
      } else if (name === "NotReadableError") {
        cameraState.textContent = "Camera busy";
        setScanStatus("CAMERA_ERROR", "Camera is in use elsewhere.");
        showCamError("The camera is already being used by another application. Close it, then Try Again.");
      } else {
        cameraState.textContent = "Camera error";
        setScanStatus("CAMERA_ERROR", "Could not start camera.");
        showCamError("Could not start the camera. Please Try Again.");
      }
    });
  }

  function hideCamError() {
    if (camError) camError.hidden = true;
  }

  function stopScanner() {
    setLive(false);
    setCamChip("idle", "Camera Ready");
    if (scanner && scanner.isScanning) {
      return scanner.stop().then(function () {
        setScanStatus("CAMERA_READY", "Camera stopped. Press Start / Allow to resume.");
        if (viewfinder) viewfinder.classList.add("is-idle");
        if (viewfinder) viewfinder.classList.remove("is-active");
        if (camIdle) camIdle.style.display = "";
      }).catch(function () { });
    }
    return Promise.resolve();
  }

  /* ── MODE SWITCH (Camera / Manual Input) ── */
  var modeBtns = document.querySelectorAll("[data-qr-mode-btn]");
  var panels = document.querySelectorAll("[data-qr-panel]");
  function switchToManualInput() {
    var manualModeButton = document.querySelector('[data-qr-mode="input"]');
    if (!manualModeButton) return;
    manualModeButton.click();
    setTimeout(function () { if (manualInput) manualInput.focus(); }, 0);
  }
  modeBtns.forEach(function (btn) {
    btn.addEventListener("click", function () {
      var mode = btn.dataset.qrMode;
      modeBtns.forEach(function (b) { b.classList.remove("active"); });
      btn.classList.add("active");
      panels.forEach(function (p) {
        p.style.display = p.dataset.qrPanel === mode ? "" : "none";
      });
      if (mode === "camera") {
        if (!scanner || !scanner.isScanning) startScanner();
      } else {
        stopScanner();
      }
    });
  });

  /* ── WIRING ── */
  startButton.addEventListener("click", function () {
    if (!soundReady) unlockSound();
    startScanner();
  });

  /* Auto-unlock sound on ANY tap so the beep works without a separate click */
  (function autoSoundUnlock() {
    function tryUnlock() {
      if (!soundReady) unlockSound();
      if (soundReady) {
        document.removeEventListener("click", tryUnlock);
      }
    }
    document.addEventListener("click", tryUnlock);
  })();
  stopButton.addEventListener("click", stopScanner);
  manualButton.addEventListener("click", function () {
    if (!soundReady) unlockSound();
    verifyToken(manualInput.value);
  });
  manualInput.addEventListener("keydown", function (event) {
    if (event.key === "Enter") {
      event.preventDefault();
      verifyToken(manualInput.value);
    }
  });

  allowCameraBtn.addEventListener("click", requestCamera);
  allowSoundBtn.addEventListener("click", unlockSound);
  if (retryBtn) retryBtn.addEventListener("click", function () {
    hideCamError();
    startScanner();
  });

  closeBtn.addEventListener("click", closeOverlay);
  overlay.addEventListener("click", function (event) {
    if (event.target === overlay) closeOverlay();
  });
  document.addEventListener("keydown", function (event) {
    if (event.key === "Escape" && overlay.classList.contains("open")) closeOverlay();
  });
  approveBtn.addEventListener("click", function () { sendDecision("approved"); });
  rejectBtn.addEventListener("click", function () { sendDecision("rejected"); });
  approveTeamBtn.addEventListener("click", function () { sendDecision("approved_team"); });
  confirmPaymentBtn.addEventListener("click", confirmPayment);
  scanNextBtn.addEventListener("click", closeOverlay);
  overrideInput.addEventListener("change", function () {
    overrideReasonWrap.style.display = overrideInput.checked ? "" : "none";
  });

  if (navigator.permissions && navigator.permissions.query) {
    navigator.permissions.query({ name: "camera" })
      .then(function (status) {
        if (status.state === "granted") {
          cameraBox.classList.add("granted");
          cameraState.textContent = "Allowed ✓";
          allowCameraBtn.textContent = "Allowed ✓";
          /* Permission already granted previously — start the live
             preview automatically, no extra click needed. */
          setScanStatus("CAMERA_READY", "Permission remembered. Starting camera…");
          startScanner();
        } else if (status.state === "denied") {
          cameraState.textContent = "Blocked in site settings";
          setScanStatus("CAMERA_ERROR", "Camera blocked in browser settings.");
          showCamError("Camera access is blocked for this site. Enable it in your browser site settings, then press Try Again.");
        }
      })
      .catch(function () { });
  }

  if (initialToken) {
    manualInput.value = initialToken;
    verifyToken(initialToken);
  }
  }) ();
</script>
{% endblock %}