import QRCode from 'qrcode';

export type QrFrame = 'none' | 'scan-me';

export interface QrStyle {
  fg?: string;
  bg?: string;
  margin?: number;
  ecc?: 'L' | 'M' | 'Q' | 'H';
  logo_data_url?: string | null;
  frame?: QrFrame | null;
}

const DEFAULTS: Required<Pick<QrStyle, 'fg' | 'bg' | 'margin' | 'ecc'>> = {
  fg: '#111827',
  bg: '#ffffff',
  margin: 2,
  ecc: 'M',
};

function merge(style?: QrStyle) {
  const fg = style?.fg ?? DEFAULTS.fg;
  const bg = style?.bg ?? DEFAULTS.bg;
  // Force high error correction when a logo is overlaid so the code stays scannable
  const eccBase = (style?.ecc as 'L' | 'M' | 'Q' | 'H') ?? DEFAULTS.ecc;
  const ecc: 'L' | 'M' | 'Q' | 'H' = style?.logo_data_url ? 'H' : eccBase;
  const margin = typeof style?.margin === 'number' ? style.margin : DEFAULTS.margin;
  return { fg, bg, ecc, margin };
}

function loadImage(src: string): Promise<HTMLImageElement> {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = 'anonymous';
    img.onload = () => resolve(img);
    img.onerror = () => reject(new Error('Failed to load image'));
    img.src = src;
  });
}

export async function qrToDataUrl(text: string, style?: QrStyle, width = 320): Promise<string> {
  const { fg, bg, ecc, margin } = merge(style);
  const baseUrl = await QRCode.toDataURL(text, {
    errorCorrectionLevel: ecc,
    margin,
    width,
    color: { dark: fg, light: bg },
  });
  if (!style?.logo_data_url || typeof document === 'undefined') return baseUrl;
  try {
    const [qrImg, logoImg] = await Promise.all([loadImage(baseUrl), loadImage(style.logo_data_url)]);
    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = width;
    const ctx = canvas.getContext('2d');
    if (!ctx) return baseUrl;
    ctx.drawImage(qrImg, 0, 0, width, width);
    const logoSize = Math.round(width * 0.22);
    const x = Math.round((width - logoSize) / 2);
    const padding = Math.max(2, Math.round(width * 0.012));
    ctx.fillStyle = bg || '#ffffff';
    ctx.fillRect(x - padding, x - padding, logoSize + padding * 2, logoSize + padding * 2);
    ctx.drawImage(logoImg, x, x, logoSize, logoSize);
    return canvas.toDataURL('image/png');
  } catch {
    return baseUrl;
  }
}

export async function qrToSvgString(text: string, style?: QrStyle, width = 320): Promise<string> {
  const { fg, bg, ecc, margin } = merge(style);
  return QRCode.toString(text, {
    type: 'svg',
    errorCorrectionLevel: ecc,
    margin,
    width,
    color: { dark: fg, light: bg },
  });
}

export function downloadBlob(blob: Blob, filename: string) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  setTimeout(() => {
    URL.revokeObjectURL(url);
    a.remove();
  }, 0);
}

export function dataUrlToBlob(dataUrl: string): Blob {
  const [meta, b64] = dataUrl.split(',');
  const mimeMatch = /data:([^;]+)/.exec(meta);
  const mime = mimeMatch?.[1] ?? 'application/octet-stream';
  const bin = atob(b64);
  const arr = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
  return new Blob([arr], { type: mime });
}
