/* ═══════════════════════════════════════════════════════════
   Rewards System — Mobile JS
   ROOT CAUSE FIX: qrbox used SCREEN pixel caps on VIDEO native pixels.
   html5-qrcode passes video.videoWidth/videoHeight (e.g. 1280×720)
   to the qrbox function — NOT rendered screen pixels.
   A cap of 320px on a 1280px video = only 90px visible on screen.
   User cannot align a card into a 90px target. Nothing ever decoded.
   Fix: pure percentages. 80% width × 60% height of native video.
═══════════════════════════════════════════════════════════ */

function isSecureContext() {
    return window.location.protocol === 'https:' ||
           window.location.hostname === 'localhost' ||
           window.location.hostname === '127.0.0.1' ||
           window.location.hostname === '[::1]';
}

function buildHttpsWarning() {
    return '<strong>Camera requires HTTPS.</strong><br>'
         + 'Open this page via your Tailscale HTTPS hostname, '
         + 'or use the <b>Manual Entry</b> field below.';
}

var MobileScanner = {
    instance:    null,
    scanning:    false,
    stopping:    false,
    scannedOnce: false,

    start: function (containerId, onSuccess, onError) {
        var self = this;

        if (!isSecureContext()) {
            if (onError) onError(buildHttpsWarning());
            return;
        }

        if (self.stopping) {
            setTimeout(function () { self.start(containerId, onSuccess, onError); }, 400);
            return;
        }
        if (self.scanning) return;

        self.scannedOnce = false;
        self.instance = new Html5Qrcode(containerId, { verbose: false });

        // CRITICAL FIX: qrbox receives VIDEO NATIVE pixels (e.g. 1280x720),
        // not screen pixels. Use percentages only — no pixel caps.
        // 80% × 60% = large easy target on any camera resolution.
        var scanConfig = {
            fps:   15,
            qrbox: function (videoW, videoH) {
                return {
                    width:  Math.round(videoW * 0.8),
                    height: Math.round(videoH * 0.6)
                };
            },
            showTorchButtonIfSupported: true
        };

        var cameraCfg = { facingMode: { ideal: 'environment' } };
        self._tryStart(cameraCfg, scanConfig, containerId, onSuccess, onError, true);
    },

    _tryStart: function (cameraCfg, scanConfig, containerId, onSuccess, onError, allowFallback) {
        var self = this;
        self.instance.start(
            cameraCfg,
            scanConfig,
            function (decodedText) {
                if (self.scannedOnce) return;
                self.scannedOnce = true;
                try { if (navigator.vibrate) navigator.vibrate([60, 30, 60]); } catch (e) {}

                // Fire onSuccess FIRST so input fills immediately,
                // then stop scanner after a short delay
                var code = (typeof decodedText === 'string') ? decodedText.trim() : String(decodedText);
                onSuccess(code);
                setTimeout(function () { self.stop(); }, 150);
            },
            function () { /* per-frame misses — normal */ }
        ).then(function () {
            self.scanning = true;
        }).catch(function (err) {
            self.scanning = false;
            if (allowFallback && cameraCfg && cameraCfg.facingMode) {
                try { self.instance.clear(); } catch (e) {}
                self._fallbackToEnumerated(scanConfig, containerId, onSuccess, onError);
                return;
            }
            if (onError) onError(self._humanise(err));
        });
    },

    _fallbackToEnumerated: function (scanConfig, containerId, onSuccess, onError) {
        var self = this;
        if (typeof Html5Qrcode.getCameras !== 'function') {
            if (onError) onError('Camera enumeration not supported. Use Manual Entry below.');
            return;
        }
        Html5Qrcode.getCameras().then(function (cameras) {
            if (!cameras || cameras.length === 0) {
                if (onError) onError('No camera found on this device.');
                return;
            }
            var chosen = cameras[0];
            for (var i = 0; i < cameras.length; i++) {
                var lbl = (cameras[i].label || '').toLowerCase();
                if (lbl.indexOf('back') !== -1 || lbl.indexOf('rear') !== -1 || lbl.indexOf('environment') !== -1) {
                    chosen = cameras[i];
                    break;
                }
            }
            try { self.instance.clear(); } catch (e) {}
            self.instance = new Html5Qrcode(containerId, { verbose: false });
            self._tryStart(chosen.id, scanConfig, containerId, onSuccess, onError, false);
        }).catch(function (err) {
            if (onError) onError('Could not list cameras: ' + (err.message || err));
        });
    },

    stop: function () {
        var self = this;
        if (!self.instance) {
            self.scanning = self.stopping = false;
            return Promise.resolve();
        }
        self.stopping = true;
        self.scanning = false;
        return self.instance.stop().then(function () {
            try { self.instance.clear(); } catch (e) {}
            self.instance = null;
            self.stopping = false;
        }).catch(function () {
            try { if (self.instance) self.instance.clear(); } catch (e) {}
            self.instance = null;
            self.stopping = false;
        });
    },

    reset: function () {
        this.scannedOnce = false;
    },

    _humanise: function (err) {
        var raw = (err && err.message) ? err.message : String(err || '');
        if (!raw) return 'Unknown camera error.';
        if (/Permission denied|NotAllowedError/i.test(raw))
            return '<strong>Camera permission denied.</strong><br>'
                 + 'Tap the lock icon in the address bar → <b>Allow</b> camera, then try again.';
        if (/NotFoundError|DevicesNotFound|Requested device not found/i.test(raw))
            return 'No camera found on this device.';
        if (/NotReadableError|TrackStartError/i.test(raw))
            return 'Camera is in use by another app. Close other camera apps and try again.';
        if (/OverconstrainedError|constraint/i.test(raw))
            return 'Switching to alternate camera…';
        if (/NotSupportedError/i.test(raw))
            return 'Camera not supported. Please use <b>Chrome</b>.';
        if (/InsecureContext|getUserMedia/i.test(raw))
            return buildHttpsWarning();
        return '<strong>Camera error:</strong> ' + raw;
    }
};

/* ── AJAX Helpers ─────────────────────────────────────── */
function mAjaxGet(url, data, cb, errCb) {
    $.ajax({ url: url, type: 'GET', data: data, dataType: 'json',
        success: cb,
        error: function (xhr) {
            var msg = 'Server error';
            try { msg = JSON.parse(xhr.responseText).error || msg; } catch (e) {}
            if (errCb) errCb(msg); else console.error(msg);
        }
    });
}

function mAjaxPost(url, data, cb, errCb) {
    $.ajax({ url: url, type: 'POST', data: data, dataType: 'json',
        success: cb,
        error: function (xhr) {
            var msg = 'Server error';
            try { msg = JSON.parse(xhr.responseText).error || msg; } catch (e) {}
            if (errCb) errCb(msg); else console.error(msg);
        }
    });
}

/* ── Alert Helper ─────────────────────────────────────── */
var _alertIcons = {
    success: 'check-circle-fill',
    danger:  'x-circle-fill',
    info:    'info-circle-fill',
    warning: 'exclamation-triangle-fill'
};

function mShowAlert(elId, type, html) {
    var el = typeof elId === 'string' ? document.getElementById(elId) : elId;
    if (!el) return;
    el.className     = 'm-alert m-alert-' + type;
    el.innerHTML     = '<i class="bi bi-' + (_alertIcons[type] || 'info-circle-fill') + '"></i><span>' + html + '</span>';
    el.style.display = 'flex';
}

function mHideAlert(elId) {
    var el = typeof elId === 'string' ? document.getElementById(elId) : elId;
    if (el) el.style.display = 'none';
}

/* ── Success Overlay ──────────────────────────────────── */
function mShowSuccess(opts) {
    var el = document.getElementById('m-success-overlay');
    if (!el) {
        el = document.createElement('div');
        el.id = 'm-success-overlay';
        el.className = 'm-success-overlay';
        document.body.appendChild(el);
    }
    var lines = (opts.lines || []).map(function (l) {
        return '<div class="mb-1"><span class="fw-bold">' + l.label + '</span> ' + l.value + '</div>';
    }).join('');
    el.innerHTML =
        '<div class="m-success-card">' +
        '<div class="m-success-icon ' + (opts.iconClass || 'bg-success-subtle text-success') + '">' +
        '<i class="bi bi-' + (opts.icon || 'check-circle-fill') + '"></i></div>' +
        '<h5 class="fw-bold mb-2">' + (opts.title || 'Done!') + '</h5>' +
        '<div class="text-muted small mb-3">' + lines + '</div>' +
        '<button class="m-btn m-btn-primary" id="m-success-close-btn">' +
        '<i class="bi bi-arrow-repeat me-2"></i>' + (opts.btnText || 'Next') + '</button></div>';
    el.style.display = 'flex';
    document.getElementById('m-success-close-btn').addEventListener('click', function () {
        el.style.display = 'none';
        if (opts.onClose) opts.onClose();
    });
}

/* ── Spinner Button Helper ────────────────────────────── */
function mBtnLoading(btn, loading) {
    if (loading) {
        btn._origHTML = btn.innerHTML;
        btn.disabled  = true;
        btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Processing…';
    } else {
        btn.disabled  = false;
        btn.innerHTML = btn._origHTML || 'Submit';
    }
}
