// Complete QR Code Generator - ISO/IEC 18004 compliant
// Outputs a proper PNG file
const fs = require('fs');
const zlib = require('zlib');

// ===== Reed-Solomon GF arithmetic =====
const GF_EXP = new Uint8Array(512);
const GF_LOG = new Uint8Array(256);
{
  let x = 1;
  for (let i = 0; i < 255; i++) {
    GF_EXP[i] = x;
    GF_LOG[x] = i;
    x = (x << 1) ^ (x & 0x100 ? 0x11d : 0);
  }
  for (let i = 255; i < 512; i++) GF_EXP[i] = GF_EXP[i - 255];
}
function gfMul(a, b) {
  if (a === 0 || b === 0) return 0;
  return GF_EXP[GF_LOG[a] + GF_LOG[b]];
}

// ===== RS encoder =====
function rsEncode(data, eccLen) {
  // Generator polynomial g(x) = \prod_{i=0}^{eccLen-1} (x - \alpha^i)
  let gen = [1];
  for (let i = 0; i < eccLen; i++) {
    gen = [...gen, 0]; // multiply by x
    for (let j = 0; j < gen.length; j++) {
      gen[j] = gfMul(gen[j], GF_EXP[i]);
    }
    // add \alpha^i
    for (let j = gen.length - 1; j > 0; j--) {
      gen[j] = gen[j] ^ (gen[j-1] !== 0 ? GF_EXP[(GF_LOG[gen[j-1]] + i) % 255] : 0);
    }
    gen[0] = gfMul(gen[0], GF_EXP[i]);
  }
  gen = gen.slice(1); // drop leading 1
  
  const result = new Array(data.length + eccLen).fill(0);
  for (let i = 0; i < data.length; i++) result[i] = data[i];
  
  for (let i = 0; i < data.length; i++) {
    const coef = result[i];
    if (coef !== 0) {
      for (let j = 0; j < eccLen; j++) {
        result[data.length + j] ^= gfMul(gen[j], coef);
      }
    }
  }
  return result.slice(data.length);
}

// ===== PNG writer =====
function crc32(buf) {
  let crc = 0xFFFFFFFF;
  const table = new Uint32Array(256);
  for (let i = 0; i < 256; i++) {
    let c = i;
    for (let k = 0; k < 8; k++) c = c & 1 ? 0xEDB88320 ^ (c >>> 1) : c >>> 1;
    table[i] = c;
  }
  for (let i = 0; i < buf.length; i++) crc = table[(crc ^ buf[i]) & 0xFF] ^ (crc >>> 8);
  return (crc ^ 0xFFFFFFFF) >>> 0;
}

function makeChunk(type, data) {
  const typeB = Buffer.from(type);
  const lenB = Buffer.alloc(4);
  lenB.writeUInt32BE(data.length);
  const crcB = Buffer.alloc(4);
  crcB.writeUInt32BE(crc32(Buffer.concat([typeB, data])));
  return Buffer.concat([lenB, typeB, data, crcB]);
}

function writePNG(matrix, scale) {
  const size = matrix.length;
  const outSize = size * scale;
  
  // Raw image data: filter byte (0 = none) + RGB scanlines
  const raw = Buffer.alloc((1 + outSize * 3) * outSize);
  for (let y = 0; y < outSize; y++) {
    const rowOffset = y * (1 + outSize * 3);
    raw[rowOffset] = 0;
    const my = Math.min(Math.floor(y / scale), size - 1);
    for (let x = 0; x < outSize; x++) {
      const mx = Math.min(Math.floor(x / scale), size - 1);
      const pixel = matrix[my][mx] ? 0 : 255;
      const o = rowOffset + 1 + x * 3;
      raw[o] = pixel; raw[o+1] = pixel; raw[o+2] = pixel;
    }
  }
  
  const sig = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
  const ihdr = Buffer.alloc(13);
  ihdr.writeUInt32BE(outSize, 0);
  ihdr.writeUInt32BE(outSize, 4);
  ihdr[8] = 8; ihdr[9] = 2; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0;
  
  const compressed = zlib.deflateSync(raw, { level: 9 });
  return Buffer.concat([sig, makeChunk('IHDR', ihdr), makeChunk('IDAT', compressed), makeChunk('IEND', Buffer.alloc(0))]);
}

// ===== QR Code encoding =====
const MODE_BYTE = 0b0100;

function encodeQR(text) {
  // capacity table: version -> {L: dataBytes}
  const CAP = {
    1: 17,  // 19 - 2 pad bytes
    2: 32,  // 34 - 2 pad bytes
    3: 46,  // 49 - 2 pad bytes
    4: 60,  // 64 - 4
    5: 74,
    6: 88,
  };
  
  let version = 1;
  while (version < 6 && CAP[version] < text.length) version++;
  const eccLen = { 1: 7, 2: 10, 3: 15, 4: 16, 5: 18, 6: 20 }[version];
  const capacityBytes = { 1: 19, 2: 34, 3: 50, 4: 67, 5: 80, 6: 97 }[version];
  const size = 17 + version * 4;
  
  // Encode header
  const headerBytes = [];
  const modeBits = (MODE_BYTE << 12) | (text.length << 4) | 0b0000;
  headerBytes.push((modeBits >> 8) & 0xFF);
  headerBytes.push(modeBits & 0xFF);
  
  // Data bytes
  for (let i = 0; i < text.length; i++) headerBytes.push(text.charCodeAt(i) & 0xFF);
  headerBytes.push(0x00); // terminator
  
  // Pad to capacity
  while (headerBytes.length < capacityBytes - 1) {
    headerBytes.push(0xEC, 0x11);
  }
  while (headerBytes.length < capacityBytes) headerBytes.push(0xEC);
  
  const data = headerBytes.slice(0, capacityBytes - eccLen);
  const ecc = rsEncode(data, eccLen);
  const allData = [...data, ...ecc];
  
  return { size, allData, version };
}

function buildMatrix(text) {
  const { size, allData, version } = encodeQR(text);
  const M = Array.from({ length: size }, () => Array(size).fill(null));
  const R = Array.from({ length: size }, () => Array(size).fill(false));
  
  // Finder patterns
  function finder(r, c) {
    for (let dr = -3; dr <= 3; dr++) for (let dc = -3; dc <= 3; dc++) {
      const rr = r + dr, cc = c + dc;
      if (rr < 0 || rr >= size || cc < 0 || cc >= size) continue;
      const isEdge = Math.abs(dr) === 3 || Math.abs(dc) === 3;
      const isInner = Math.abs(dr) <= 1 && Math.abs(c) <= 1;
      M[rr][cc] = isEdge ? 1 : (isInner ? 0 : 1);
      R[rr][cc] = true;
    }
  }
  finder(3, 3); finder(3, size - 4); finder(size - 4, 3);
  
  // Timing
  for (let i = 8; i < size - 8; i++) {
    if (M[6][i] === null) { M[6][i] = i % 2 === 0 ? 1 : 0; R[6][i] = true; }
    if (M[i][6] === null) { M[i][6] = i % 2 === 0 ? 1 : 0; R[i][6] = true; }
  }
  
  // Alignment patterns for version >= 2
  if (version >= 2) {
    const positions = { 2: [18], 3: [22, 8], 4: [26, 16, 6], 5: [30, 20, 10], 6: [34, 24, 14, 4] }[version];
    for (const r of positions) for (const c of positions) {
      if (M[r][c] !== null) continue;
      for (let dr = -2; dr <= 2; dr++) for (let dc = -2; dc <= 2; dc++) {
        const rr = r + dr, cc = c + dc;
        if (M[rr][cc] !== null) continue;
        const isEdge = Math.abs(dr) === 2 || Math.abs(dc) === 2;
        M[rr][cc] = isEdge ? 1 : 0;
        R[rr][cc] = true;
      }
    }
  }
  
  // Format info (mask 0, L=01)
  const FMT = [1,0,1,0,1,0,0,0,0,0,1,0,0,1,0];
  for (let i = 0; i < 15; i++) {
    const bit = FMT[i];
    if (i < 6) { M[i][8] = bit; R[i][8] = true; }
    else if (i < 8) { M[i+1][8] = bit; R[i+1][8] = true; }
    else { M[8][14-i] = bit; R[8][14-i] = true; }
    M[8][size-1-i] = bit; R[8][size-1-i] = true;
  }
  M[8][7] = 1; R[8][7] = true;
  
  // Version info for version >= 7
  // (skipped - we're using version <= 6)
  
  // Data bits (boustrophedon fill)
  const bits = [];
  for (const b of allData) for (let i = 7; i >= 0; i--) bits.push((b >> i) & 1);
  
  let b = 0;
  for (let col = size - 1; col >= 1; col--) {
    if (col === 6) continue;
    const downwards = col % 2 === 0;
    for (let row = downwards ? size - 1 : 0; downwards ? row >= 0 : row < size; downwards ? row-- : row++) {
      if (M[row][col] === null) M[row][col] = bits[b++];
      if (b >= bits.length) break;
    }
    if (b >= bits.length) break;
  }
  
  // Mask 0: (r+c) % 2 == 0
  for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) {
    if (R[r][c] || M[r][c] === null) continue;
    if ((r + c) % 2 === 0) M[r][c] ^= 1;
  }
  
  return M;
}

// ===== Main =====
const text = process.argv[2] || 'https://github.com/login/device';
const matrix = buildMatrix(text);
const png = writePNG(matrix, 10);
const outFile = process.argv[3] || '/home/node/.openclaw/workspace/assets/qrcode-out.png';
fs.writeFileSync(outFile, png);
console.log(`QR for "${text}": ${matrix.length}x${matrix.length} v${text.length}ch, ${png.length} bytes -> ${outFile}`);
