// Complete QR Code Generator - Version 1, Byte mode
// Based on ISO/IEC 18004

const fs = require('fs');

// GF setup
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]];
}

// Reed-Solomon generator polynomial for ECLevel 1 (L), 7 ECC for version 1
// g(x) = (x - α^0)(x - α^1)...(x - α^6) = x^7 + 87x^6 + 229x^5 + 146x^4 + 149x^3 + 238x^2 + 102x + 21
function rsGeneratorPolynomial(eccLen) {
  let poly = [1];
  for (let i = 0; i < eccLen; i++) {
    poly = [...poly, 0];
    for (let j = 0; j < poly.length; j++) {
      poly[j] = gfMul(poly[j], GF_EXP[i]);
    }
    // Multiply by x+α^i
    const newPoly = [1];
    for (let j = 1; j < poly.length; j++) {
      const a = poly[j - 1];
      const b = poly[j];
      newPoly[j] = b ^ (a !== 0 ? GF_EXP[(GF_LOG[a] + i) % 255] : 0);
    }
    poly = newPoly;
  }
  return poly.slice(1); // return coefficient array
}

function rsEncode(data, eccLen) {
  const gen = rsGeneratorPolynomial(eccLen);
  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);
}

// Mode: byte = 0100
const MODE_BYTE = 0b0100;
// Version 1: 21x21 modules, capacity: 19 bytes L, 16 bytes M, 13 bytes Q, 9 bytes H
// For "https://github.com/login/device" (29 chars) - need Version 2
// But let's try with a shorter URL that fits in Version 1

function encodeData(text) {
  const capacityTable = {
    1: { L: 19, M: 16, Q: 13, H: 9 },
    2: { L: 34, M: 28, Q: 22, H: 16 },
  };
  
  // Find minimum version that fits
  let version = 1;
  while (version <= 2) {
    const cap = capacityTable[version];
    const byteCount = text.length + 3; // mode (1) + char count (1) + data + terminator
    if (byteCount <= cap.L) break;
    version++;
  }
  if (version > 2) version = 2;
  
  // For Version 1-L, 19 data bytes
  const dataBytes = [];
  
  // Mode indicator (4 bits) + character count (9 bits for version 1-9)
  const modeBits = (MODE_BYTE << 12) | (text.length << 4) | (0b0000);
  dataBytes.push(modeBits >> 8);
  dataBytes.push(modeBits & 0xff);
  
  // Character data
  for (let i = 0; i < text.length; i++) {
    dataBytes.push(text.charCodeAt(i));
  }
  
  // Terminator (4 zeros)
  dataBytes.push(0x00);
  
  // Pad to fill capacity
  while (dataBytes.length < 19) {
    dataBytes.push(0xec);
    if (dataBytes.length < 19) dataBytes.push(0x11);
  }
  
  const data = dataBytes.slice(0, 19);
  const ecc = rsEncode(data, 7);
  
  return { version, data: [...data, ...ecc], modules: version === 1 ? 21 : 25 };
}

function createQRMatrix(text) {
  const result = encodeData(text);
  const size = result.modules;
  const matrix = Array.from({ length: size }, () => Array(size).fill(null));
  const reserved = Array.from({ length: size }, () => Array(size).fill(false));
  
  // Add finder pattern
  function addFinder(mrow, mcol) {
    for (let r = -3; r <= 3; r++) {
      for (let c = -3; c <= 3; c++) {
        const rr = mrow + r, cc = mcol + c;
        if (rr < 0 || rr >= size || cc < 0 || cc >= size) continue;
        const isOuter = Math.abs(r) === 3 || Math.abs(c) === 3;
        const isInner = Math.abs(r) <= 1 && Math.abs(c) <= 1;
        matrix[rr][cc] = isOuter ? 1 : (isInner ? 0 : 1);
        reserved[rr][cc] = true;
      }
    }
  }
  addFinder(3, 3);
  addFinder(3, size - 4);
  addFinder(size - 4, 3);
  
  // Timing patterns
  for (let i = 8; i < size - 8; i++) {
    if (matrix[6][i] === null) { matrix[6][i] = i % 2 === 0 ? 1 : 0; reserved[6][i] = true; }
    if (matrix[i][6] === null) { matrix[i][6] = i % 2 === 0 ? 1 : 0; reserved[i][6] = true; }
  }
  
  // Alignment pattern (Version 2 = center + one)
  if (size === 25) {
    const pos = [22];
    for (const r of pos) {
      for (const c of pos) {
        if (matrix[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 (matrix[rr][cc] !== null) continue;
            const isEdge = Math.abs(dr) === 2 || Math.abs(dc) === 2;
            matrix[rr][cc] = isEdge ? 1 : 0;
            reserved[rr][cc] = true;
          }
        }
      }
    }
  }
  
  // Format info (mask 0, L=01)
  // Format string: 15 bits: 5 data + 10 ECC for L level
  // Simplified: just use correct bits for format
  const FORMAT_BITS = [
    1,0,1,0,1,0,0,0,0,0,1,0,0,1,0  // mask 0, L
  ];
  
  // Place format info
  for (let i = 0; i < 15; i++) {
    const bit = FORMAT_BITS[i];
    // Above top finder
    if (i < 6) { matrix[i][8] = bit; reserved[i][8] = true; }
    else if (i < 8) { matrix[i + 1][8] = bit; reserved[i + 1][8] = true; }
    else { matrix[8][14 - i] = bit; reserved[8][14 - i] = true; }
    // Left of left finder
    if (i < 8) { matrix[8][size - 1 - i] = bit; reserved[8][size - 1 - i] = true; }
  }
  matrix[8][7] = 1; reserved[8][7] = true; // dark module
  
  // Fill data (boustrophedon)
  const bits = [];
  for (const b of result.data) {
    for (let i = 7; i >= 0; i--) bits.push((b >> i) & 1);
  }
  
  let bitIdx = 0;
  for (let col = size - 1; col >= 1; col--) {
    if (col === 6) continue;
    if (col % 2 === 0) {
      for (let row = size - 1; row >= 0; row--) {
        if (matrix[row][col] === null) {
          matrix[row][col] = bits[bitIdx++];
          if (bitIdx >= bits.length) break;
        }
      }
    } else {
      for (let row = 0; row < size; row++) {
        if (matrix[row][col] === null) {
          matrix[row][col] = bits[bitIdx++];
          if (bitIdx >= bits.length) break;
        }
      }
    }
    if (bitIdx >= bits.length) break;
  }
  
  // Mask 0: (r+c) % 2 == 0
  for (let r = 0; r < size; r++) {
    for (let c = 0; c < size; c++) {
      if (reserved[r][c] || matrix[r][c] === null) continue;
      if ((r + c) % 2 === 0) matrix[r][c] ^= 1;
    }
  }
  
  return { size, matrix, version: result.version };
}

function matrixToSVG(matrix, size) {
  const MOD = 10;
  const total = size * MOD;
  let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${total}" height="${total}" viewBox="0 0 ${size} ${size}">`;
  svg += `<rect width="${size}" height="${size}" fill="white"/>`;
  for (let r = 0; r < size; r++) {
    for (let c = 0; c < size; c++) {
      if (matrix[r][c] === 1) {
        svg += `<rect x="${c}" y="${r}" width="1" height="1" fill="black"/>`;
      }
    }
  }
  svg += `</svg>`;
  return svg;
}

const text = process.argv[2] || 'TEST';
const { size, matrix } = createQRMatrix(text);
const svg = matrixToSVG(matrix, size);
const outFile = process.argv[3] || '/home/node/.openclaw/workspace/assets/qr-output.svg';
fs.writeFileSync(outFile, svg);
console.log(`QR for "${text}": ${size}x${size} Version ${text.length} chars, saved to ${outFile}`);
