// 자체 QR 코드 생성기(외부 API/라이브러리 0) — 앱 다운로드 URL을 인코딩해 SVG로 렌더.
// 정본: WISE(UIWS) frontend/src/pages/AppQrCode.tsx 를 구조 그대로 이식.
//
// 외부 API 금지·신규 npm 의존성 0 제약을 준수하기 위해 QR(Model 2, byte 모드, EC level M)을
// 직접 구현한다. 다운로드 URL이 빌드마다 매니페스트로 바뀌어도 항상 라이브 URL을 인코딩한다.
// (api.qrserver.com 등 외부 QR 생성 서비스 미사용.)
//
// 정확성: 기능 패턴(파인더/정렬/타이밍/포맷/다크모듈)을 reserved 매트릭스로 분리 추적하여
//   데이터 비트 배치·마스킹이 기능 셀을 건드리지 않도록 한다. mask 0 고정(format 비트는 사전계산).
import { useMemo } from 'react';

// ── GF(256) 로그/역로그 테이블(Reed-Solomon EC용) ──
const EXP = new Uint8Array(512);
const LOG = new Uint8Array(256);
(() => {
  let x = 1;
  for (let i = 0; i < 255; i++) {
    EXP[i] = x;
    LOG[x] = i;
    x <<= 1;
    if (x & 0x100) x ^= 0x11d;
  }
  for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255];
})();

function gfMul(a: number, b: number): number {
  if (a === 0 || b === 0) return 0;
  return EXP[LOG[a] + LOG[b]];
}

function rsGenPoly(degree: number): number[] {
  // 생성 다항식 g(x) = ∏(x − α^i), poly[0]이 최고차 계수(=1)가 되도록 누적한다.
  // rsEncode는 gen[0]을 선두(최고차) 계수로 소비하므로 계수 순서가 반대가 되면 EC가 전부 틀어진다.
  // (x + α^i) 곱셈: x·poly는 같은 인덱스 유지(차수 +1), α^i·poly는 상수항 쪽(j+1)으로.
  let poly = [1];
  for (let i = 0; i < degree; i++) {
    const next = new Array(poly.length + 1).fill(0);
    for (let j = 0; j < poly.length; j++) {
      next[j] ^= poly[j];
      next[j + 1] ^= gfMul(poly[j], EXP[i]);
    }
    poly = next;
  }
  return poly;
}

function rsEncode(data: number[], ecLen: number): number[] {
  const gen = rsGenPoly(ecLen);
  const res = new Array(data.length + ecLen).fill(0);
  for (let i = 0; i < data.length; i++) res[i] = data[i];
  for (let i = 0; i < data.length; i++) {
    const coef = res[i];
    if (coef !== 0) {
      for (let j = 0; j < gen.length; j++) res[i + j] ^= gfMul(gen[j], coef);
    }
  }
  return res.slice(data.length);
}

// ── 버전별 파라미터(EC level M, byte 모드) — 버전 1~10 (URL 길이 충분히 커버) ──
interface VerSpec {
  size: number; // 모듈 한 변
  ec: number; // 블록당 EC 코드워드
  g1: number; // 그룹1 블록 수
  g1d: number; // 그룹1 블록당 데이터 코드워드
  g2: number; // 그룹2 블록 수
  g2d: number; // 그룹2 블록당 데이터 코드워드
  cap: number; // 총 데이터 코드워드(= g1*g1d + g2*g2d)
}
const SPECS: Record<number, VerSpec> = {
  1: { size: 21, ec: 10, g1: 1, g1d: 16, g2: 0, g2d: 0, cap: 16 },
  2: { size: 25, ec: 16, g1: 1, g1d: 28, g2: 0, g2d: 0, cap: 28 },
  3: { size: 29, ec: 26, g1: 1, g1d: 44, g2: 0, g2d: 0, cap: 44 },
  4: { size: 33, ec: 18, g1: 2, g1d: 32, g2: 0, g2d: 0, cap: 64 },
  5: { size: 37, ec: 24, g1: 2, g1d: 43, g2: 0, g2d: 0, cap: 86 },
  6: { size: 41, ec: 16, g1: 4, g1d: 27, g2: 0, g2d: 0, cap: 108 },
  7: { size: 45, ec: 18, g1: 4, g1d: 31, g2: 0, g2d: 0, cap: 124 },
  8: { size: 49, ec: 22, g1: 2, g1d: 38, g2: 2, g2d: 39, cap: 154 },
  9: { size: 53, ec: 22, g1: 3, g1d: 36, g2: 2, g2d: 37, cap: 182 },
  10: { size: 57, ec: 26, g1: 4, g1d: 43, g2: 1, g2d: 44, cap: 216 },
};

// 정렬 패턴 중심 좌표
const ALIGN: Record<number, number[]> = {
  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],
};

// 포맷 정보(EC level M, mask 0~7) 15bit 시퀀스(사전계산, BCH+마스크 0x5412 적용분)
const FORMAT_M: number[] = [
  0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0,
];

// 버전 정보(18bit, BCH(18,6) 사전계산) — 버전 7~40만 사용. 6 이하는 버전 정보 모듈 없음.
//   ★ 이 모듈이 누락되면 버전 7+ QR을 표준 스캐너가 디코드하지 못한다(원인2).
//   값은 ISO/IEC 18004 부록 D 표와 동일(버전 7~10).
const VERSION_INFO: Record<number, number> = {
  7: 0x07c94,
  8: 0x085bc,
  9: 0x09a99,
  10: 0x0a4d3,
};

function bytesOf(str: string): number[] {
  return Array.from(new TextEncoder().encode(str));
}

function chooseVersion(len: number): number {
  // +2 코드워드 여유(모드 지시자 4bit + 카운트 8/16bit)
  for (let v = 1; v <= 10; v++) if (SPECS[v].cap >= len + 2) return v;
  return 10;
}

// byte 모드 데이터 코드워드 생성
function buildData(bytes: number[], spec: VerSpec, version: number): number[] {
  const bits: number[] = [];
  const push = (val: number, n: number) => {
    for (let i = n - 1; i >= 0; i--) bits.push((val >> i) & 1);
  };
  push(0b0100, 4); // byte 모드
  push(bytes.length, version >= 10 ? 16 : 8); // 카운트(버전 1~9는 8bit)
  for (const b of bytes) push(b, 8);
  const capBits = spec.cap * 8;
  for (let i = 0; i < 4 && bits.length < capBits; i++) bits.push(0); // 종료
  while (bits.length % 8 !== 0) bits.push(0);
  const pad = [0xec, 0x11];
  let pi = 0;
  while (bits.length < capBits) {
    push(pad[pi % 2], 8);
    pi++;
  }
  const cw: number[] = [];
  for (let i = 0; i < bits.length; i += 8) {
    let v = 0;
    for (let j = 0; j < 8; j++) v = (v << 1) | bits[i + j];
    cw.push(v);
  }
  return cw;
}

// 데이터 + EC 블록 인터리브
function interleave(dataCw: number[], spec: VerSpec): number[] {
  const blocks: { data: number[]; ec: number[] }[] = [];
  let idx = 0;
  for (let i = 0; i < spec.g1; i++) {
    const d = dataCw.slice(idx, idx + spec.g1d);
    idx += spec.g1d;
    blocks.push({ data: d, ec: rsEncode(d, spec.ec) });
  }
  for (let i = 0; i < spec.g2; i++) {
    const d = dataCw.slice(idx, idx + spec.g2d);
    idx += spec.g2d;
    blocks.push({ data: d, ec: rsEncode(d, spec.ec) });
  }
  const out: number[] = [];
  const maxData = Math.max(spec.g1d, spec.g2d);
  for (let i = 0; i < maxData; i++) {
    for (const b of blocks) if (i < b.data.length) out.push(b.data[i]);
  }
  for (let i = 0; i < spec.ec; i++) for (const b of blocks) out.push(b.ec[i]);
  return out;
}

function makeMatrix(size: number): { m: number[][]; reserved: boolean[][] } {
  const m = Array.from({ length: size }, () => new Array<number>(size).fill(0));
  const reserved = Array.from({ length: size }, () => new Array<boolean>(size).fill(false));
  return { m, reserved };
}

function setF(m: number[][], reserved: boolean[][], r: number, c: number, v: number) {
  if (r < 0 || c < 0 || r >= m.length || c >= m.length) return;
  m[r][c] = v;
  reserved[r][c] = true;
}

function placeFinder(m: number[][], reserved: boolean[][], r: number, c: number) {
  for (let i = -1; i <= 7; i++) {
    for (let j = -1; j <= 7; j++) {
      const rr = r + i;
      const cc = c + j;
      if (rr < 0 || cc < 0 || rr >= m.length || cc >= m.length) continue;
      if (i === -1 || i === 7 || j === -1 || j === 7) {
        setF(m, reserved, rr, cc, 0); // 분리자(흰)
      } else {
        const onBorder = i === 0 || i === 6 || j === 0 || j === 6;
        const inside = i >= 2 && i <= 4 && j >= 2 && j <= 4;
        setF(m, reserved, rr, cc, onBorder || inside ? 1 : 0);
      }
    }
  }
}

function placeAlign(m: number[][], reserved: boolean[][], version: number) {
  const centers = ALIGN[version];
  for (const r of centers) {
    for (const c of centers) {
      if (reserved[r][c]) continue; // 파인더와 겹침
      for (let i = -2; i <= 2; i++) {
        for (let j = -2; j <= 2; j++) {
          const ring = Math.max(Math.abs(i), Math.abs(j));
          setF(m, reserved, r + i, c + j, ring === 1 ? 0 : 1);
        }
      }
    }
  }
}

function placeTiming(m: number[][], reserved: boolean[][]) {
  const size = m.length;
  for (let i = 8; i < size - 8; i++) {
    const v = i % 2 === 0 ? 1 : 0;
    if (!reserved[6][i]) setF(m, reserved, 6, i, v);
    if (!reserved[i][6]) setF(m, reserved, i, 6, v);
  }
}

// 포맷 정보 영역 예약 + 다크 모듈(값은 placeFormat에서 채움, 여기선 reserved만)
function reserveFormat(m: number[][], reserved: boolean[][]) {
  const size = m.length;
  for (let i = 0; i <= 8; i++) {
    if (!reserved[8][i]) reserved[8][i] = true;
    if (!reserved[i][8]) reserved[i][8] = true;
  }
  for (let i = 0; i < 8; i++) {
    reserved[8][size - 1 - i] = true;
    reserved[size - 1 - i][8] = true;
  }
  setF(m, reserved, size - 8, 8, 1); // 다크 모듈
}

function placeData(m: number[][], reserved: boolean[][], cw: number[]) {
  const size = m.length;
  let bitIdx = 0;
  const totalBits = cw.length * 8;
  const bitAt = (i: number) => (i < totalBits ? (cw[i >> 3] >> (7 - (i & 7))) & 1 : 0);
  let up = true;
  for (let col = size - 1; col > 0; col -= 2) {
    if (col === 6) col--; // timing 열 skip
    for (let n = 0; n < size; n++) {
      const row = up ? size - 1 - n : n;
      for (let c = 0; c < 2; c++) {
        const cc = col - c;
        if (!reserved[row][cc]) {
          m[row][cc] = bitAt(bitIdx);
          bitIdx++;
        }
      }
    }
    up = !up;
  }
}

// 데이터 영역에만 마스크 적용(기능/예약 셀 제외). mask 0: (r+c)%2==0
function applyMask0(m: number[][], reserved: boolean[][]) {
  const size = m.length;
  for (let r = 0; r < size; r++) {
    for (let c = 0; c < size; c++) {
      if (reserved[r][c]) continue;
      if ((r + c) % 2 === 0) m[r][c] ^= 1;
    }
  }
}

function placeFormat(m: number[][], mask: number) {
  // 포맷 정보(15bit) 배치 — ISO/IEC 18004 표준 위치·비트순서.
  //   주 사본(좌상단): 가로 (8,0..5)=bit14..9, (8,7)=bit8, (8,8)=bit7 /
  //                    세로 (7,8)=bit6, (5,8)=bit5, (4,8)..(0,8)=bit4..0 (타이밍행 6 건너뜀)
  //   부 사본: 좌하단 세로 (size-1-i,8)=bit(14-i) i=0..6(비트 14~8) /
  //            우상단 가로 (8,size-8+i)=bit(7-i) i=0..7(비트 7~0)
  //   다크 모듈 m[size-8][8](항상 1)은 어느 사본도 침범하지 않는다.
  const size = m.length;
  const fmt = FORMAT_M[mask];
  const bit = (i: number) => (fmt >> i) & 1;
  // 주 사본 — 가로(행 8)
  for (let i = 0; i <= 5; i++) m[8][i] = bit(14 - i); // (8,0..5) = bit14..9
  m[8][7] = bit(8);
  m[8][8] = bit(7);
  // 주 사본 — 세로(열 8)
  m[7][8] = bit(6);
  for (let i = 0; i <= 4; i++) m[4 - i][8] = bit(4 - i); // (4,8)..(0,8) = bit4..0
  // 부 사본 — 좌하단 세로(비트 14~8)
  for (let i = 0; i <= 6; i++) m[size - 1 - i][8] = bit(14 - i);
  // 부 사본 — 우상단 가로(비트 7~0)
  for (let i = 0; i <= 7; i++) m[8][size - 8 + i] = bit(7 - i);
}

// 버전 정보(18bit) 예약 + 배치 — 버전 7~40만. 좌하단 6×3 블록 + 우상단 3×6 블록.
//   bit i(i=0..17): 좌하단 (size-11 + i%3, ⌊i/3⌋), 우상단 (⌊i/3⌋, size-11 + i%3).
function reserveVersion(m: number[][], reserved: boolean[][], version: number) {
  if (version < 7) return;
  const size = m.length;
  for (let i = 0; i < 18; i++) {
    const r = i % 3;
    const c = Math.floor(i / 3);
    reserved[size - 11 + r][c] = true; // 좌하단
    reserved[c][size - 11 + r] = true; // 우상단
  }
}
function placeVersion(m: number[][], version: number) {
  if (version < 7) return;
  const size = m.length;
  const v = VERSION_INFO[version];
  if (v === undefined) return;
  for (let i = 0; i < 18; i++) {
    const bit = (v >> i) & 1;
    const r = i % 3;
    const c = Math.floor(i / 3);
    m[size - 11 + r][c] = bit; // 좌하단
    m[c][size - 11 + r] = bit; // 우상단
  }
}

function generateMatrix(text: string): number[][] {
  const bytes = bytesOf(text);
  const version = chooseVersion(bytes.length);
  const spec = SPECS[version];
  const dataCw = buildData(bytes, spec, version);
  const finalCw = interleave(dataCw, spec);

  const { m, reserved } = makeMatrix(spec.size);
  placeFinder(m, reserved, 0, 0);
  placeFinder(m, reserved, 0, spec.size - 7);
  placeFinder(m, reserved, spec.size - 7, 0);
  placeAlign(m, reserved, version);
  placeTiming(m, reserved);
  reserveFormat(m, reserved);
  reserveVersion(m, reserved, version); // 버전 7+ 정보 영역 예약(데이터 침범 방지)
  placeData(m, reserved, finalCw);
  applyMask0(m, reserved);
  placeFormat(m, 0);
  placeVersion(m, version); // 버전 7+ 정보 모듈 기입
  return m;
}

interface Props {
  value: string;
  size?: number;
  title?: string;
}

/** 다운로드 URL을 인코딩한 QR 코드를 SVG로 렌더(외부 의존성 0). */
export default function AppQrCode({ value, size = 200, title = 'QR 코드' }: Props) {
  const matrix = useMemo(() => {
    try {
      return generateMatrix(value);
    } catch {
      return null;
    }
  }, [value]);

  if (!matrix) {
    return (
      <div
        style={{ width: size, height: size, display: 'grid', placeItems: 'center', fontSize: 12, color: '#666' }}
      >
        QR을 표시할 수 없습니다
      </div>
    );
  }

  const n = matrix.length;
  const quiet = 4; // 정숙 영역 4 모듈
  const total = n + quiet * 2;
  const cells: string[] = [];
  for (let r = 0; r < n; r++) {
    for (let c = 0; c < n; c++) {
      if (matrix[r][c]) cells.push(`M${c + quiet} ${r + quiet}h1v1h-1z`);
    }
  }

  return (
    <svg
      width={size}
      height={size}
      viewBox={`0 0 ${total} ${total}`}
      role="img"
      aria-label={title}
      shapeRendering="crispEdges"
    >
      <rect width={total} height={total} fill="#ffffff" />
      <path d={cells.join('')} fill="#111111" />
    </svg>
  );
}
