// One place that knows how to draw a barcode or a QR code for a label.
//
// Seven views were generating codes - three in core, four in plugins - each
// importing qrcode/jsbarcode directly and each carrying its own answer to the
// same questions: what margin, what width, which error correction, how big a
// module has to be before a scanner can read it. The answers had already
// drifted (margin 0 in one place and 2 in another, width 150 against 160), and
// none of the label pages knew the printer-resolution arithmetic the Tech Tools
// generator worked out.
//
// So the knowledge lives here and the views pass what they need. Nothing about
// a specific label belongs in this file; nothing about how to render a code
// belongs anywhere else.
//
// WHY SVG, MOSTLY. A code renders to an SVG data URI rather than a canvas or a
// PNG for two reasons found the hard way on the label pages: an <img> prints
// reliably where a live canvas or inline SVG does not, and SVG rasterises at
// the printer's resolution with hard module edges. A PNG gets downscaled to the
// label size and smears exactly the edges a scanner reads. The one exception is
// a QR with a logo composited into it, which needs a canvas to composite on.

import QRCode from 'qrcode'
import JsBarcode from 'jsbarcode'

import { getQrLogo } from '@/utils/siteSettings'

// Label stock actually loaded in the printers, with the code size, padding,
// quiet zone and code-to-caption gap that fit each one. Sizes are inches,
// because that is what the stock is sold as and what @page takes.
//
// quiet and gap are NOT the same measurement. quiet is the clear space a
// scanner needs around the code and is fixed by the symbology; gap is only how
// far the caption sits from it, and is a matter of taste per stock.
export const LABEL_PRESETS = [
  { id: 'zebra1x05', name: '1.00 x 0.50 in (Zebra gap)', labelwidth: 1.0, labelheight: 0.5, codesize: 0.4286, padding: 0, quiet: 0.035, gap: 0.03, labelfont: 7 },
  { id: 'zebra2x1', name: '2.00 x 1.00 in', labelwidth: 2.0, labelheight: 1.0, codesize: 0.85, padding: 0.03, quiet: 0.05, gap: 0.05, labelfont: 10 },
  { id: 'zebra225x125', name: '2.25 x 1.25 in', labelwidth: 2.25, labelheight: 1.25, codesize: 1.05, padding: 0.04, quiet: 0.06, gap: 0.06, labelfont: 11 },
  { id: 'zebra4x6', name: '4.00 x 6.00 in (shipping)', labelwidth: 4.0, labelheight: 6.0, codesize: 3.0, padding: 0.15, quiet: 0.12, gap: 0.12, labelfont: 20 },
  { id: 'badge', name: '2.13 x 3.38 in (badge)', labelwidth: 2.13, labelheight: 3.38, codesize: 1.5, padding: 0.15, quiet: 0.1, gap: 0.1, labelfont: 12 },
]

// CODE128 is what the scanners on the floor are configured for, and a label
// carries its text separately, so the barcode never renders its own.
export const BARCODE_DEFAULTS = {
  format: 'CODE128',
  displayValue: false,
  margin: 0,
  width: 2,
  height: 100,
}

export function svgDataUri(svg) {
  return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg)
}

/**
 * Modules across a QR for this content and error correction, quiet zone
 * excluded. 0 when the content cannot be encoded at all.
 */
export function qrModuleCount(text, errorCorrection = 'M') {
  if (!text) return 0
  try {
    return QRCode.create(text, { errorCorrectionLevel: errorCorrection }).modules.size
  } catch {
    return 0
  }
}

/**
 * Whether a QR of `sizeInches` can actually be printed at `dpi`.
 *
 * A scanner reads modules, not inches. Below about two printer dots per module
 * the edges blur into each other and the code stops scanning reliably - and a
 * fractional dots-per-module means some modules are a dot wider than others,
 * which is the difference between a label that always works and one that works
 * on most printers. `snapped` is the nearest size that divides evenly.
 */
export function qrFit(text, { sizeInches, dpi = 203, errorCorrection = 'M' } = {}) {
  const modules = qrModuleCount(text, errorCorrection)
  if (!modules || !sizeInches) return null
  const dotsPerModule = (sizeInches * dpi) / modules
  const whole = Math.floor(dotsPerModule)
  return {
    modules,
    dotsPerModule,
    wholeDots: whole,
    snapped: whole > 0 ? (modules * whole) / dpi : 0,
    moduleMm: whole > 0 ? (whole / dpi) * 25.4 : 0,
    // Below two dots per module the code is not reliably scannable, whatever
    // it looks like on screen.
    tooSmall: whole < 2,
    even: whole > 0 && Math.abs((modules * whole) / dpi - sizeInches) < 0.002,
  }
}

/**
 * A QR as an SVG data URI.
 *
 * margin 0 by default: on a label the quiet zone is blank label supplied by the
 * layout, so spending the code box on white the page cannot then adjust makes
 * the printed code smaller for no benefit. A caller rendering to a screen, or
 * onto something with no controlled surround, should pass a margin.
 */
export async function qrSvgDataUri(text, { errorCorrection = 'M', margin = 0 } = {}) {
  const svg = await QRCode.toString(text, {
    type: 'svg',
    errorCorrectionLevel: errorCorrection,
    margin,
  })
  return svgDataUri(svg)
}

/**
 * A QR as a PNG data URL. Use only where a raster is genuinely needed - a
 * composited logo, or an existing page built around <img src=png>.
 */
export async function qrPngDataUrl(text, { width = 160, margin = 0, errorCorrection = 'M' } = {}) {
  return QRCode.toDataURL(text, { width, margin, errorCorrectionLevel: errorCorrection })
}

/**
 * Render a barcode into an existing SVG or canvas element.
 *
 * Kept as a thin pass-through because several pages hold an element ref and
 * want JsBarcode to draw straight into it; the value here is the shared
 * defaults, not the indirection.
 */
export function barcodeInto(element, text, options = {}) {
  JsBarcode(element, text, { ...BARCODE_DEFAULTS, ...options })
  return element
}

/** A barcode as an SVG data URI, for pages that print an <img>. */
export function barcodeSvgDataUri(text, options = {}) {
  const element = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
  barcodeInto(element, text, options)
  return svgDataUri(new XMLSerializer().serializeToString(element))
}

// -- QR with a logo composited in the middle ---------------------------------
//
// Moved here from views/print/qrLogo.js, which core label pages and the printer
// QR pages both reached into. The overlay image is a site setting; the monogram
// below is the last-resort fallback when that setting is empty or unreachable,
// so a label still carries a mark rather than a hole.

const FALLBACK_LOGO_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32.5 32"><path d="M19.8915 11.8362C19.8915 10.0196 21.1404 8.25119 21.826 8.5888C22.6014 8.97061 21.2424 10.6868 19.8915 11.8362ZM11.3823 12.4994C11.3823 11.0364 12.8475 8.25521 13.7453 8.54861C14.8023 8.89425 12.8679 11.6996 11.3823 12.4994ZM9.89679 22.9611C9.2234 22.9932 8.77447 22.5672 8.77447 21.8558C8.77447 19.9508 11.4558 18.1301 13.4841 17.1535C13.125 19.8141 12.2108 22.8525 9.90087 22.957M22.279 16.7516C20.7486 16.7516 19.5773 17.8608 19.5773 19.1912C19.5773 20.3004 20.2507 21.1846 21.1526 21.1846C21.4668 21.1846 21.7811 21.0078 21.7811 20.6099C21.7811 20.0352 21.0057 19.8945 21.0669 19.0304C21.1036 18.4637 21.6505 18.0819 22.1892 18.0819C23.2707 18.0819 23.7768 19.1148 23.7768 20.1758C23.7319 21.8156 22.5075 22.957 21.0669 22.957C19.1773 22.957 17.9611 21.1846 17.9611 19.2756C17.9611 16.4381 19.8507 15.3328 20.8424 15.0676C20.8547 15.0676 23.4299 15.5217 23.3483 14.4004C23.3156 13.9101 22.5688 13.7212 22.03 13.6971C21.4301 13.6729 20.8302 13.886 20.8302 13.886C20.5159 13.7292 20.2996 13.4238 20.165 13.0701C22.0096 11.6956 23.3156 10.3652 23.3156 8.85808C23.3156 8.0623 22.7769 7.35092 21.7403 7.35092C19.8956 7.35092 18.4998 9.65386 18.4998 11.7398C18.4998 12.0934 18.4998 12.4511 18.5896 12.7606C17.4183 13.6046 16.5491 14.1271 14.9737 15.0555C14.9737 14.8626 15.0145 14.3602 15.1492 13.7131C15.6879 13.1384 16.4307 12.2743 16.4307 11.6112C16.4307 11.3017 16.2511 11.0364 15.892 11.0364C14.9941 11.0364 14.3167 12.3667 14.1371 13.2952C13.7331 13.7815 12.9209 14.4044 12.2475 14.4044C11.7088 14.4044 11.5292 13.9141 11.4803 13.7413C13.1903 13.1625 15.3084 10.8596 15.3084 8.77769C15.3084 8.33559 15.1288 7.35895 13.778 7.35895C11.7537 7.35895 10.0437 10.3291 10.0437 12.632C9.32134 12.632 9.05607 11.8764 9.05607 11.3017C9.05607 10.727 9.28053 10.1482 9.28053 9.97136C9.28053 9.79452 9.19075 9.57347 8.92139 9.57347C8.248 9.57347 7.83989 10.4617 7.83989 11.4785C7.88478 12.8973 8.83161 13.7855 10.0886 13.8739C10.2682 14.7179 11.0354 15.5137 11.9782 15.5137C12.5659 15.5137 13.2841 15.3369 13.778 14.8947C13.7331 15.2042 13.6882 15.4695 13.6433 15.7388C11.6639 16.7596 10.2233 17.467 8.91731 18.6204C7.88478 19.5529 7.29709 20.7908 7.29709 21.7674C7.29709 23.0977 8.15005 24.3356 9.90903 24.3356C11.9782 24.3356 13.5535 22.6958 14.3208 20.4371C14.6799 19.372 14.8268 17.8247 14.9166 16.4059C16.9857 15.2565 17.9693 14.5853 19.0467 13.8337C19.1814 14.0548 19.3202 14.2316 19.4956 14.3642C18.5529 14.8505 16.3001 16.2251 16.3001 19.4604C16.3001 21.7674 17.8754 24.3356 20.9812 24.3356C23.5482 24.3356 25.3031 22.2537 25.3031 20.2602C25.3031 18.4436 24.2665 16.7596 22.2872 16.7596M30.025 20.5657C30.025 20.5657 29.9924 20.6019 29.9434 20.5818C29.9067 20.5697 29.8944 20.5496 29.8944 20.5255C29.8944 20.4974 30.4372 18.9219 30.4331 17.1133C30.429 15.164 29.621 13.9663 28.5884 13.9663C27.96 13.9663 27.5069 14.4084 27.5069 15.0756C27.5069 16.2733 28.9925 16.3617 28.9925 18.9781C28.9925 20.0432 28.768 21.06 28.4089 22.1693C26.7438 27.7076 21.4301 30.2798 16.2593 30.2798C13.8718 30.2798 12.1781 29.7975 11.6721 29.5765C11.6517 29.5684 11.6354 29.5283 11.6517 29.4881C11.6639 29.4559 11.6966 29.4358 11.717 29.4439C11.921 29.5242 13.378 29.9744 15.1778 29.9744C17.1571 29.9744 18.3284 29.1786 18.3284 28.202C18.3284 27.583 17.8346 27.0967 17.202 27.0967C15.9859 27.0967 15.8961 28.6039 13.2882 28.6039C12.1618 28.6039 11.1742 28.3828 10.0029 28.0291C4.41988 26.3451 1.76306 21.1605 1.76714 16.0161C1.76714 13.5122 2.48134 11.5187 2.49358 11.4986C2.50174 11.4866 2.53439 11.4705 2.5752 11.4866C2.61602 11.4986 2.62418 11.5348 2.62418 11.5428C2.55888 11.7518 2.08547 13.1786 2.08547 14.951C2.08547 16.9003 2.89353 18.0538 3.93015 18.0538C4.51783 18.0538 5.01165 17.6117 5.01165 16.9887C5.01165 15.791 3.52611 15.6584 3.52611 13.0862C3.52611 11.9769 3.75058 11.0043 4.10972 9.85079C5.80747 4.34464 11.0722 1.7684 16.2471 1.72821C18.6509 1.70811 20.7567 2.41949 20.8383 2.47978C20.8506 2.49184 20.8669 2.52399 20.8506 2.56016C20.8343 2.60035 20.8057 2.60839 20.7935 2.60437C20.769 2.60437 19.3977 2.03768 17.3286 2.03768C15.3941 2.03768 14.1779 2.83346 14.1779 3.85431C14.1779 4.42904 14.6268 4.91535 15.3043 4.91535C16.5205 4.91535 16.6103 3.4524 19.2181 3.4524C20.3445 3.4524 21.3322 3.67345 22.5035 4.02713C28.1314 5.71113 30.6902 10.94 30.7392 15.996C30.7637 18.5843 30.025 20.5456 30.0169 20.5576M16.2471 0.75157C7.69705 0.75157 0.763175 7.58001 0.763175 16C0.763175 24.42 7.69705 31.2444 16.2471 31.2444C24.7971 31.2444 31.7269 24.42 31.7269 16C31.7269 7.58001 24.7971 0.75157 16.2471 0.75157ZM16.2471 32C7.28893 32 0 24.8661 0 16C0 7.13389 7.28893 0 16.2471 0C25.2052 0 32.4941 7.18212 32.4941 16C32.4941 24.8179 25.2011 32 16.2471 32Z" fill="black"/></svg>`

let logoImage = null

function loadLogo(url) {
  if (logoImage) return Promise.resolve(logoImage)
  return new Promise(resolve => {
    const img = new Image()
    img.onload = () => { logoImage = img; resolve(img) }
    img.onerror = () => {
      const fallback = new Image()
      fallback.onload = () => { logoImage = fallback; resolve(fallback) }
      fallback.onerror = () => resolve(null)
      fallback.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(FALLBACK_LOGO_SVG)
    }
    img.src = url
  })
}

function drawLogoOverlay(canvas, logo) {
  const canvasContext = canvas.getContext('2d')
  const size = canvas.width
  const logoSize = Math.round(size * 0.22)
  const x = (size - logoSize) / 2
  const y = (size - logoSize) / 2

  // White circle behind the mark, so it sits on the code rather than in it.
  canvasContext.beginPath()
  canvasContext.arc(size / 2, size / 2, logoSize / 2 + 4, 0, Math.PI * 2)
  canvasContext.fillStyle = '#fff'
  canvasContext.fill()

  if (logo) {
    canvasContext.drawImage(logo, x, y, logoSize, logoSize)
  }
}

/**
 * A QR with the configured logo composited in the centre, as a PNG data URL.
 *
 * Error correction H, because up to 30% of the code can be obscured and still
 * decode - which is what makes covering the middle of it survivable at all.
 * Empty qr_logo setting means no overlay. Returns '' on failure: a label with
 * no code is better than a page that throws while printing a batch.
 */
export async function renderQrDataUrl(url, { width = 144, margin = 0 } = {}) {
  const qrLogoUrl = await getQrLogo()
  const canvas = document.createElement('canvas')
  try {
    await QRCode.toCanvas(canvas, url, { width, margin, errorCorrectionLevel: 'H' })
    if (qrLogoUrl) {
      const logo = await loadLogo(qrLogoUrl)
      drawLogoOverlay(canvas, logo)
    }
    return canvas.toDataURL('image/png')
  } catch (err) {
    console.error('QR error:', err)
    return ''
  }
}
