/*!
 * pp-qr.js — tiny self-contained QR Code generator (byte mode, ECC level M).
 * No dependencies, no network. Renders text as a QR matrix and paints it into a
 * target element. Used by the generated app's 2FA setup so a TOTP secret is
 * encoded entirely in the user's own browser. Geometry follows the reference
 * algorithm by Project Nayuki (public domain). Versions 1–10 cover otpauth:// URIs.
 */
(function (global) {
    'use strict';

    // ── GF(256) for Reed–Solomon ───────────────────────────────────────────
    var EXP = new Array(512), LOG = new Array(256);
    (function () {
        var x = 1;
        for (var i = 0; i < 255; i++) { EXP[i] = x; LOG[x] = i; x <<= 1; if (x & 0x100) x ^= 0x11d; }
        for (var j = 255; j < 512; j++) EXP[j] = EXP[j - 255];
    })();
    function gmul(a, b) { return (a === 0 || b === 0) ? 0 : EXP[LOG[a] + LOG[b]]; }
    function rsDivisor(degree) {
        var result = new Array(degree).fill(0);
        result[degree - 1] = 1;
        var root = 1;
        for (var i = 0; i < degree; i++) {
            for (var j = 0; j < result.length; j++) {
                result[j] = gmul(result[j], root);
                if (j + 1 < result.length) result[j] ^= result[j + 1];
            }
            root = gmul(root, 2);
        }
        return result;
    }
    function rsEncode(data, ecLen) {
        var divisor = rsDivisor(ecLen);
        var result = new Array(ecLen).fill(0);
        for (var d = 0; d < data.length; d++) {
            var factor = data[d] ^ result.shift();
            result.push(0);
            for (var i = 0; i < result.length; i++) result[i] ^= gmul(divisor[i], factor);
        }
        return result;
    }

    // ── Capacity + block structure per version, ECC level M ────────────────
    // [ dataCodewords, ecCodewordsPerBlock, numBlocksG1, dataCwG1, numBlocksG2, dataCwG2 ]
    var VERS = {
        1: [16, 10, 1, 16, 0, 0], 2: [28, 16, 1, 28, 0, 0], 3: [44, 26, 1, 44, 0, 0],
        4: [64, 18, 2, 32, 0, 0], 5: [86, 24, 2, 43, 0, 0], 6: [108, 16, 4, 27, 0, 0],
        7: [124, 18, 4, 31, 0, 0], 8: [154, 22, 2, 38, 2, 39], 9: [182, 22, 3, 36, 2, 37],
        10: [216, 26, 4, 43, 1, 44]
    };
    var ALIGN = {
        1: [], 2: [6, 18], 3: [6, 22], 4: [6, 26], 5: [6, 30], 6: [6, 34],
        7: [6, 22, 38], 8: [6, 24, 42], 9: [6, 26, 46], 10: [6, 28, 50]
    };

    function utf8len(text) { var n = 0; for (var i = 0; i < text.length; i++) { var c = text.charCodeAt(i); n += c < 0x80 ? 1 : c < 0x800 ? 2 : 3; } return n; }
    function chooseVersion(byteLen) {
        for (var v = 1; v <= 10; v++) {
            var cci = v < 10 ? 8 : 16;
            if (4 + cci + byteLen * 8 <= VERS[v][0] * 8) return v;
        }
        throw new Error('QR: data too long');
    }

    // ── Data codeword stream (data + interleaved EC) ───────────────────────
    function buildCodewords(text, version) {
        var bytes = [];
        for (var i = 0; i < text.length; i++) {
            var c = text.charCodeAt(i);
            if (c < 0x80) bytes.push(c);
            else if (c < 0x800) { bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); }
            else { bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); }
        }
        var cci = version < 10 ? 8 : 16, bs = [];
        function put(val, len) { for (var b = len - 1; b >= 0; b--) bs.push((val >> b) & 1); }
        put(0b0100, 4);
        put(bytes.length, cci);
        for (var k = 0; k < bytes.length; k++) put(bytes[k], 8);
        var totalData = VERS[version][0], capBits = totalData * 8;
        for (var t = 0; t < 4 && bs.length < capBits; t++) bs.push(0);
        while (bs.length % 8 !== 0) bs.push(0);
        var dcw = [];
        for (var p = 0; p < bs.length; p += 8) { var v = 0; for (var q = 0; q < 8; q++) v = (v << 1) | bs[p + q]; dcw.push(v); }
        var pad = [0xEC, 0x11], pi = 0;
        while (dcw.length < totalData) dcw.push(pad[pi++ % 2]);

        var ecLen = VERS[version][1];
        var nb1 = VERS[version][2], d1 = VERS[version][3], nb2 = VERS[version][4], d2 = VERS[version][5];
        var blocks = [], off = 0;
        for (var b1 = 0; b1 < nb1; b1++) { blocks.push(dcw.slice(off, off + d1)); off += d1; }
        for (var b2 = 0; b2 < nb2; b2++) { blocks.push(dcw.slice(off, off + d2)); off += d2; }
        var ecBlocks = blocks.map(function (blk) { return rsEncode(blk, ecLen); });

        var result = [], maxD = Math.max(d1, d2 || 0);
        for (var col = 0; col < maxD; col++) for (var bi = 0; bi < blocks.length; bi++) if (col < blocks[bi].length) result.push(blocks[bi][col]);
        for (var ec = 0; ec < ecLen; ec++) for (var bj = 0; bj < ecBlocks.length; bj++) result.push(ecBlocks[bj][ec]);
        return result;
    }

    // ── BCH bits for format & version info ─────────────────────────────────
    function getBit(x, i) { return (x >>> i) & 1; }
    function formatValue(mask) {              // ECC level M formatBits = 0
        var data = (0 << 3) | mask, rem = data;
        for (var i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >>> 9) * 0x537);
        return ((data << 10) | rem) ^ 0x5412;
    }
    function versionValue(version) {
        var rem = version;
        for (var i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >>> 11) * 0x1f25);
        return (version << 12) | rem;
    }

    // ── Build QR ───────────────────────────────────────────────────────────
    function generate(text) {
        var version = chooseVersion(utf8len(text));
        var cw = buildCodewords(text, version);
        var size = version * 4 + 17;
        var mod = [], fn = [], i, j, r, c;
        for (i = 0; i < size; i++) { mod.push(new Array(size).fill(0)); fn.push(new Array(size).fill(false)); }
        function setF(rr, cc, dark) { if (rr < 0 || cc < 0 || rr >= size || cc >= size) return; mod[rr][cc] = dark ? 1 : 0; fn[rr][cc] = true; }

        // Timing (whole row/col 6), then finders overwrite the overlap.
        for (i = 0; i < size; i++) { setF(6, i, i % 2 === 0); setF(i, 6, i % 2 === 0); }
        function finder(cr, cc) {
            for (var dr = -4; dr <= 4; dr++) for (var dc = -4; dc <= 4; dc++) {
                var d = Math.max(Math.abs(dr), Math.abs(dc));
                setF(cr + dr, cc + dc, d !== 2 && d !== 4);
            }
        }
        finder(3, 3); finder(3, size - 4); finder(size - 4, 3);

        // Alignment patterns (all combos except the three finder corners).
        var ap = ALIGN[version], na = ap.length;
        for (var a = 0; a < na; a++) for (var b = 0; b < na; b++) {
            if ((a === 0 && b === 0) || (a === 0 && b === na - 1) || (a === na - 1 && b === 0)) continue;
            var cr = ap[a], cc = ap[b];
            for (var dr = -2; dr <= 2; dr++) for (var dc = -2; dc <= 2; dc++) setF(cr + dr, cc + dc, Math.max(Math.abs(dr), Math.abs(dc)) !== 1);
        }

        // Reserve format (both copies) + dark module + version info as function.
        var fmt1 = [[0, 8], [1, 8], [2, 8], [3, 8], [4, 8], [5, 8], [7, 8], [8, 8], [8, 7], [8, 5], [8, 4], [8, 3], [8, 2], [8, 1], [8, 0]];
        var fmt2 = [[8, size - 1], [8, size - 2], [8, size - 3], [8, size - 4], [8, size - 5], [8, size - 6], [8, size - 7], [8, size - 8], [size - 7, 8], [size - 6, 8], [size - 5, 8], [size - 4, 8], [size - 3, 8], [size - 2, 8], [size - 1, 8]];
        for (i = 0; i < 15; i++) { fn[fmt1[i][0]][fmt1[i][1]] = true; fn[fmt2[i][0]][fmt2[i][1]] = true; }
        setF(size - 8, 8, true); // dark module
        if (version >= 7) {
            for (i = 0; i < 18; i++) { var rr = Math.floor(i / 3), cc2 = i % 3; fn[rr][size - 11 + cc2] = true; fn[size - 11 + cc2][rr] = true; }
        }

        // Draw data (zigzag), skipping function modules.
        var bits = [];
        for (i = 0; i < cw.length; i++) for (j = 7; j >= 0; j--) bits.push((cw[i] >> j) & 1);
        var bi = 0;
        for (var right = size - 1; right >= 1; right -= 2) {
            if (right === 6) right = 5;
            for (var vert = 0; vert < size; vert++) {
                for (j = 0; j < 2; j++) {
                    var col = right - j;
                    var upward = ((right + 1) & 2) === 0;
                    var row = upward ? size - 1 - vert : vert;
                    if (!fn[row][col]) { mod[row][col] = (bi < bits.length ? bits[bi] : 0); bi++; }
                }
            }
        }

        // Draw version info (mask-independent) once.
        if (version >= 7) {
            var vv = versionValue(version);
            for (i = 0; i < 18; i++) { var bit = getBit(vv, i), b1 = Math.floor(i / 3), b2 = size - 11 + i % 3; mod[b1][b2] = bit; mod[b2][b1] = bit; }
        }

        function maskFn(k, r2, c2) {
            switch (k) {
                case 0: return (r2 + c2) % 2 === 0;
                case 1: return r2 % 2 === 0;
                case 2: return c2 % 3 === 0;
                case 3: return (r2 + c2) % 3 === 0;
                case 4: return (Math.floor(r2 / 2) + Math.floor(c2 / 3)) % 2 === 0;
                case 5: return (r2 * c2) % 2 + (r2 * c2) % 3 === 0;
                case 6: return ((r2 * c2) % 2 + (r2 * c2) % 3) % 2 === 0;
                case 7: return ((r2 + c2) % 2 + (r2 * c2) % 3) % 2 === 0;
            }
        }
        function drawFormat(mask) {
            var val = formatValue(mask);
            for (var x = 0; x < 15; x++) { var bit = getBit(val, x); mod[fmt1[x][0]][fmt1[x][1]] = bit; mod[fmt2[x][0]][fmt2[x][1]] = bit; }
        }
        function penalty() {
            var n = size, p = 0, x, y, run, cnt;
            for (y = 0; y < n; y++) { run = 1; for (x = 1; x < n; x++) { if (mod[y][x] === mod[y][x - 1]) { run++; if (run === 5) p += 3; else if (run > 5) p++; } else run = 1; } }
            for (x = 0; x < n; x++) { run = 1; for (y = 1; y < n; y++) { if (mod[y][x] === mod[y - 1][x]) { run++; if (run === 5) p += 3; else if (run > 5) p++; } else run = 1; } }
            for (y = 0; y < n - 1; y++) for (x = 0; x < n - 1; x++) { var s = mod[y][x] + mod[y][x + 1] + mod[y + 1][x] + mod[y + 1][x + 1]; if (s === 0 || s === 4) p += 3; }
            cnt = 0; for (y = 0; y < n; y++) for (x = 0; x < n; x++) cnt += mod[y][x];
            p += Math.floor(Math.abs(cnt * 100 / (n * n) - 50) / 5) * 10;
            return p;
        }

        // Try all 8 masks, keep the lowest-penalty one. Apply mask to data cells,
        // draw its format bits, score, then revert data cells for the next trial.
        var bestMask = 0, bestPen = Infinity;
        for (var k = 0; k < 8; k++) {
            for (r = 0; r < size; r++) for (c = 0; c < size; c++) if (!fn[r][c] && maskFn(k, r, c)) mod[r][c] ^= 1;
            drawFormat(k);
            var pen = penalty();
            if (pen < bestPen) { bestPen = pen; bestMask = k; }
            for (r = 0; r < size; r++) for (c = 0; c < size; c++) if (!fn[r][c] && maskFn(k, r, c)) mod[r][c] ^= 1;
        }
        for (r = 0; r < size; r++) for (c = 0; c < size; c++) if (!fn[r][c] && maskFn(bestMask, r, c)) mod[r][c] ^= 1;
        drawFormat(bestMask);
        return mod;
    }

    // ── Public render ──────────────────────────────────────────────────────
    function render(el, text, opts) {
        opts = opts || {};
        var m = generate(text), n = m.length;
        var quiet = opts.quiet == null ? 4 : opts.quiet, scale = opts.scale || 5;
        var total = (n + quiet * 2) * scale;
        var dark = opts.dark || '#000000', light = opts.light || '#ffffff';
        var canvas = document.createElement('canvas');
        canvas.width = canvas.height = total;
        canvas.style.width = canvas.style.height = total + 'px';
        canvas.style.imageRendering = 'pixelated';
        var ctx = canvas.getContext('2d');
        ctx.fillStyle = light; ctx.fillRect(0, 0, total, total);
        ctx.fillStyle = dark;
        for (var r = 0; r < n; r++) for (var c = 0; c < n; c++) if (m[r][c]) ctx.fillRect((c + quiet) * scale, (r + quiet) * scale, scale, scale);
        el.innerHTML = ''; el.appendChild(canvas);
        return canvas;
    }

    var api = { generate: generate, render: render };
    if (typeof module !== 'undefined' && module.exports) module.exports = api;
    global.PPQR = api;
})(typeof window !== 'undefined' ? window : this);
