/**
 * Barcode / QR / DataMatrix rasteriser for the Label Designer.
 *
 * Wraps `bwip-js` (1D symbologies + QR + DataMatrix). Renders a code element to
 * a PNG data URL via an offscreen canvas — the SAME output is used by the
 * on-screen SVG (`<image>`) and the PDF compiler (`doc.addImage`), so screen and
 * print never diverge. Client-only: bwip-js is dynamically imported behind an
 * `import.meta.client` guard and never touches the SSR bundle.
 *
 * Results are memoised by their visual inputs (value + symbology + colours +
 * dpi), so panning/selecting doesn't re-rasterise.
 */

import type { LdBarcodeElement, LdQrElement, LdDataMatrixElement } from './useLabelDesign'

export type LdCodeElement = LdBarcodeElement | LdQrElement | LdDataMatrixElement

export interface BarcodeRaster { dataUrl: string; wPx: number; hPx: number }

// 1×1 transparent PNG — SSR / pre-render / error fallback.
const TRANSPARENT_PX =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=='

let _bwip: any = null
const _cache = new Map<string, BarcodeRaster>()

const stripHash = (c?: string) => (c || '').replace('#', '').slice(0, 8)

const SYMBOLOGY_BCID: Record<string, string> = {
  code128: 'code128',
  code39: 'code39',
  ean13: 'ean13',
  ean8: 'ean8',
  upca: 'upca',
  itf14: 'itf14',
  'gs1-128': 'gs1-128'
}

export function bcidFor (el: LdCodeElement): string {
  if (el.type === 'qr') return 'qrcode'
  if (el.type === 'datamatrix') return 'datamatrix'
  return SYMBOLOGY_BCID[(el as LdBarcodeElement).symbology] || 'code128'
}

/** True for square 2D codes — renderers should preserve aspect (letterbox) rather than stretch. */
export function isSquareCode (el: LdCodeElement): boolean {
  return el.type === 'qr' || el.type === 'datamatrix'
}

const cacheKey = (el: LdCodeElement, dpi: number): string => {
  const parts: any[] = [el.type, bcidFor(el), el.value, el.barColor, el.bgColor, dpi]
  if (el.type === 'barcode') parts.push((el as LdBarcodeElement).showText ? 1 : 0)
  if (el.type === 'qr') parts.push((el as LdQrElement).eccLevel)
  return parts.join('|')
}

async function getBwip (): Promise<any> {
  if (_bwip) return _bwip
  // Explicit browser entry: exports `toCanvas` as a named export and never
  // pulls the Node `canvas` build. (The bare 'bwip-js' specifier resolves to
  // the Node build under SSR, which has no toCanvas.)
  const mod: any = await import('bwip-js/browser')
  _bwip = (mod && typeof mod.toCanvas === 'function')
    ? mod
    : (mod?.default && typeof mod.default.toCanvas === 'function')
      ? mod.default
      : (mod?.default ?? mod)
  return _bwip
}

export function useBarcode () {
  /**
   * Rasterise a code element. Returns null on failure (e.g. invalid data for
   * the chosen symbology) so callers can show an "invalid" placeholder.
   */
  async function render (el: LdCodeElement, opts: { dpi?: number } = {}): Promise<BarcodeRaster | null> {
    if (!import.meta.client) return null
    const value = String(el.value ?? '')
    if (!value) return null

    const dpi = opts.dpi ?? 300
    const key = cacheKey(el, dpi)
    const cached = _cache.get(key)
    if (cached) return cached

    try {
      const bwip = await getBwip()
      const canvas = document.createElement('canvas')

      // device pixels per module — scaled to the requested dpi for crisp print.
      const scale = Math.min(8, Math.max(2, Math.round(dpi / 96) + 1))

      const bwipOpts: any = {
        bcid: bcidFor(el),
        text: value,
        scale,
        // quiet zones left at bwip-js defaults — needed for reliable scanning;
        // fine-tune per symbology/printer during real-label testing.
        barcolor: stripHash(el.barColor) || '000000'
      }

      if (el.bgColor && el.bgColor !== 'transparent') {
        bwipOpts.backgroundcolor = stripHash(el.bgColor)
      }

      if (el.type === 'barcode') {
        bwipOpts.height = 10 // mm — fixed aspect; the box scales it on screen/PDF
        if ((el as LdBarcodeElement).showText) {
          bwipOpts.includetext = true
          bwipOpts.textxalign = 'center'
        }
      } else if (el.type === 'qr') {
        bwipOpts.eclevel = (el as LdQrElement).eccLevel || 'M'
      }

      bwip.toCanvas(canvas, bwipOpts)
      const raster: BarcodeRaster = {
        dataUrl: canvas.toDataURL('image/png'),
        wPx: canvas.width || 1,
        hPx: canvas.height || 1
      }
      _cache.set(key, raster)
      return raster
    } catch (err) {
      console.warn('[label-designer] barcode render failed:', err)
      return null
    }
  }

  return { render, bcidFor, isSquareCode, TRANSPARENT_PX }
}
