/**
 * Label Designer — compile a `LabelTemplate` to a PDF at exact label size.
 *
 * Uses jsPDF (unit:'mm', format:[wMm,hMm]) so the design's millimetre
 * coordinates map straight onto the page. The output PDF is either sent to the
 * server print pipeline (`/api/print/labelprint`) or downloaded. Client-only:
 * jsPDF is dynamically imported behind an `import.meta.client` guard.
 *
 * Code elements (barcode/qr/datamatrix) are rasterised via `useBarcode` — the
 * same raster the on-screen SVG shows — and embedded with `addImage`.
 *
 * NOTE: v1 renders every element axis-aligned. The model carries a `rotation`
 * field (default 0, no UI control yet) reserved for a later enhancement; since
 * nothing sets it, screen and print are guaranteed identical.
 */

import type {
  LabelTemplate, LdElement, LdTextElement, LdImageElement,
  LdRectElement, LdEllipseElement, LdLineElement
} from './useLabelDesign'
import type { LdCodeElement } from './useBarcode'

const MM_PER_PT = 25.4 / 72

interface RGB { r: number; g: number; b: number }

const hexToRgb = (hex: string): RGB => {
  let h = (hex || '#000000').replace('#', '').trim()
  if (h.length === 3) h = h.split('').map(c => c + c).join('')
  const n = parseInt(h.slice(0, 6) || '000000', 16)
  return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 }
}

const isTransparent = (c?: string) => !c || c === 'transparent' || c === 'none'

const imageFormatOf = (dataUrl: string): 'PNG' | 'JPEG' => {
  const m = /^data:image\/(png|jpe?g|webp)/i.exec(dataUrl || '')
  return m && /jpe?g/i.test(m[1]) ? 'JPEG' : 'PNG'
}

/** Natural pixel size of a data URL (client only). */
const imageSize = (dataUrl: string): Promise<{ w: number; h: number }> =>
  new Promise(resolve => {
    if (!import.meta.client || !dataUrl) return resolve({ w: 1, h: 1 })
    const img = new Image()
    img.onload = () => resolve({ w: img.naturalWidth || 1, h: img.naturalHeight || 1 })
    img.onerror = () => resolve({ w: 1, h: 1 })
    img.src = dataUrl
  })

/** Fit (w×h source aspect) into a box, returning the centred draw rect. */
const fitRect = (
  bx: number, by: number, bw: number, bh: number,
  sw: number, sh: number, mode: 'fill' | 'contain' | 'cover'
) => {
  if (mode === 'fill' || sw <= 0 || sh <= 0) return { x: bx, y: by, w: bw, h: bh }
  const sr = sw / sh
  const br = bw / bh
  let dw = bw; let dh = bh
  if (mode === 'contain' ? sr > br : sr < br) { dw = bw; dh = bw / sr } else { dh = bh; dw = bh * sr }
  return { x: bx + (bw - dw) / 2, y: by + (bh - dh) / 2, w: dw, h: dh }
}

export function usePdfCompiler () {
  const { render: renderCode, isSquareCode } = useBarcode()

  const drawText = (doc: any, el: LdTextElement) => {
    const { r, g, b } = hexToRgb(el.color)
    doc.setTextColor(r, g, b)
    const style = el.fontStyle || 'normal'
    doc.setFont(el.fontFamily || 'helvetica', style)
    doc.setFontSize(el.fontSizePt || 11)
    const lineHeight = el.lineHeight || 1.15
    doc.setLineHeightFactor(lineHeight)
    if (el.letterSpacingPt) doc.setCharSpace(el.letterSpacingPt * MM_PER_PT)

    const raw = String(el.text ?? '')
    let lines: string[]
    if (el.wrap) {
      lines = raw.split('\n').flatMap((para: string) => doc.splitTextToSize(para, Math.max(1, el.w)))
    } else {
      lines = raw.split('\n')
    }

    const lineMm = (el.fontSizePt || 11) * lineHeight * MM_PER_PT
    const blockH = lines.length * lineMm
    let yTop = el.y
    if (el.vAlign === 'middle') yTop = el.y + (el.h - blockH) / 2
    else if (el.vAlign === 'bottom') yTop = el.y + (el.h - blockH)

    let ax = el.x
    if (el.align === 'center') ax = el.x + el.w / 2
    else if (el.align === 'right') ax = el.x + el.w

    doc.text(lines, ax, yTop, { baseline: 'top', align: el.align || 'left' })
    if (el.letterSpacingPt) doc.setCharSpace(0)
  }

  const drawRect = (doc: any, el: LdRectElement) => {
    const hasFill = !isTransparent(el.fill)
    const hasStroke = !isTransparent(el.stroke) && el.strokeWidthMm > 0
    if (!hasFill && !hasStroke) return
    if (hasFill) { const c = hexToRgb(el.fill); doc.setFillColor(c.r, c.g, c.b) }
    if (hasStroke) { const c = hexToRgb(el.stroke); doc.setDrawColor(c.r, c.g, c.b); doc.setLineWidth(el.strokeWidthMm) }
    const style = hasFill && hasStroke ? 'FD' : hasFill ? 'F' : 'S'
    if (el.radiusMm > 0) {
      const rr = Math.min(el.radiusMm, el.w / 2, el.h / 2)
      doc.roundedRect(el.x, el.y, el.w, el.h, rr, rr, style)
    } else {
      doc.rect(el.x, el.y, el.w, el.h, style)
    }
  }

  const drawEllipse = (doc: any, el: LdEllipseElement) => {
    const hasFill = !isTransparent(el.fill)
    const hasStroke = !isTransparent(el.stroke) && el.strokeWidthMm > 0
    if (!hasFill && !hasStroke) return
    if (hasFill) { const c = hexToRgb(el.fill); doc.setFillColor(c.r, c.g, c.b) }
    if (hasStroke) { const c = hexToRgb(el.stroke); doc.setDrawColor(c.r, c.g, c.b); doc.setLineWidth(el.strokeWidthMm) }
    const style = hasFill && hasStroke ? 'FD' : hasFill ? 'F' : 'S'
    doc.ellipse(el.x + el.w / 2, el.y + el.h / 2, el.w / 2, el.h / 2, style)
  }

  const drawLine = (doc: any, el: LdLineElement) => {
    if (isTransparent(el.stroke) || el.strokeWidthMm <= 0) return
    const c = hexToRgb(el.stroke)
    doc.setDrawColor(c.r, c.g, c.b)
    doc.setLineWidth(el.strokeWidthMm)
    if (Array.isArray(el.dash) && el.dash.length) doc.setLineDashPattern(el.dash, 0)
    doc.line(el.x, el.y, el.x + el.w, el.y + el.h)
    if (Array.isArray(el.dash) && el.dash.length) doc.setLineDashPattern([], 0)
  }

  const drawImage = async (doc: any, el: LdImageElement) => {
    if (!el.dataUrl) return
    const fmt = imageFormatOf(el.dataUrl)
    const { w: sw, h: sh } = await imageSize(el.dataUrl)
    const rect = fitRect(el.x, el.y, el.w, el.h, sw, sh, el.fit || 'contain')
    const opacity = el.opacity ?? 1
    const useG = opacity < 1 && typeof doc.GState === 'function'
    if (useG) { doc.saveGraphicsState(); doc.setGState(new doc.GState({ opacity })) }
    try {
      doc.addImage(el.dataUrl, fmt, rect.x, rect.y, rect.w, rect.h, undefined, 'FAST')
    } catch (err) {
      console.warn('[label-designer] addImage failed:', err)
    }
    if (useG) doc.restoreGraphicsState()
  }

  const drawCode = async (doc: any, el: LdCodeElement, dpi: number) => {
    const raster = await renderCode(el, { dpi })
    if (!raster) {
      // invalid data — mark the spot with a faint outline so it's noticed.
      doc.setDrawColor(200, 60, 60); doc.setLineWidth(0.2)
      doc.rect(el.x, el.y, el.w, el.h, 'S')
      return
    }
    let { x, y, w, h } = { x: el.x, y: el.y, w: el.w, h: el.h }
    if (isSquareCode(el)) {
      const side = Math.min(el.w, el.h)
      x = el.x + (el.w - side) / 2
      y = el.y + (el.h - side) / 2
      w = side; h = side
    }
    try {
      doc.addImage(raster.dataUrl, 'PNG', x, y, w, h, undefined, 'FAST')
    } catch (err) {
      console.warn('[label-designer] code addImage failed:', err)
    }
  }

  const drawElement = async (doc: any, el: LdElement, dpi: number) => {
    if (el.visible === false) return
    switch (el.type) {
      case 'text': return drawText(doc, el as LdTextElement)
      case 'rect': return drawRect(doc, el as LdRectElement)
      case 'ellipse': return drawEllipse(doc, el as LdEllipseElement)
      case 'line': return drawLine(doc, el as LdLineElement)
      case 'image': return drawImage(doc, el as LdImageElement)
      case 'barcode':
      case 'qr':
      case 'datamatrix': return drawCode(doc, el as LdCodeElement, dpi)
    }
  }

  const paintBackground = (doc: any, tpl: LabelTemplate) => {
    const bg = (tpl.background || '#FFFFFF').toLowerCase()
    if (bg === '#ffffff' || bg === '#fff' || isTransparent(bg)) return
    const c = hexToRgb(bg)
    doc.setFillColor(c.r, c.g, c.b)
    doc.rect(0, 0, tpl.size.wMm, tpl.size.hMm, 'F')
  }

  /**
   * Build the jsPDF document. `copies` appends N identical pages.
   * Returns the jsPDF instance (caller decides save / output).
   */
  async function compileToPdf (tpl: LabelTemplate, opts: { copies?: number; dpi?: number } = {}): Promise<any> {
    if (!import.meta.client) throw new Error('PDF generation is client-only')
    const copies = Math.max(1, Math.min(200, Math.floor(opts.copies ?? 1)))
    const dpi = opts.dpi ?? 300
    const wMm = tpl.size.wMm
    const hMm = tpl.size.hMm
    const orientation = wMm >= hMm ? 'landscape' : 'portrait'

    const { jsPDF } = await import('jspdf')
    const doc = new jsPDF({ orientation, unit: 'mm', format: [wMm, hMm], compress: true, putOnlyUsedFonts: true })

    for (let copy = 0; copy < copies; copy++) {
      if (copy > 0) doc.addPage([wMm, hMm], orientation)
      paintBackground(doc, tpl)
      for (const el of tpl.elements) {
        // sequential: keeps draw order == z-order and avoids interleaved jsPDF state
        await drawElement(doc, el, dpi)
      }
    }
    return doc
  }

  /** Base64 (no data: prefix) ready for POST /api/print/labelprint. */
  function toBase64 (doc: any): string {
    return String(doc.output('dataurlstring') || '')
      .replace(/^data:application\/pdf;[^,]*base64,/, '')
  }

  return { compileToPdf, toBase64 }
}
