<script setup lang="ts">
/**
 * Renders ONE label element as an SVG node, inside the mm-unit viewBox of the
 * Stage. Visual-only (no interaction — the Stage owns pointer/selection). The
 * exact same model fields drive `usePdfCompiler`, so screen ≈ print.
 *
 * Code elements (barcode/qr/datamatrix) are rasterised to a PNG via useBarcode
 * (the identical raster the PDF embeds) and shown as <image>.
 */
import type { LdElement, LdTextElement, LdImageElement, LdRectElement, LdEllipseElement, LdLineElement } from '~/composables/useLabelDesign'
import type { LdCodeElement } from '~/composables/useBarcode'

const props = defineProps<{ el: LdElement }>()

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

const MM_PER_PT = 25.4 / 72
const PX_PER_PT = 96 / 72
const PX_PER_MM = 96 / 25.4

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

const FONT_CSS: Record<string, string> = {
  helvetica: 'Helvetica, Arial, sans-serif',
  times: '"Times New Roman", Times, serif',
  courier: '"Courier New", Courier, monospace'
}

// rotation reserved for a later enhancement; 0 in v1 so this is a no-op.
const rotateTransform = computed(() => {
  const r = props.el.rotation || 0
  if (!r) return undefined
  const cx = props.el.x + props.el.w / 2
  const cy = props.el.y + props.el.h / 2
  return `rotate(${r} ${cx} ${cy})`
})

/* ----------------------------- TEXT ----------------------------- */
let _measureCanvas: HTMLCanvasElement | null = null
const measureCtx = () => {
  if (!import.meta.client) return null
  if (!_measureCanvas) _measureCanvas = document.createElement('canvas')
  return _measureCanvas.getContext('2d')
}

const asText = computed(() => props.el as LdTextElement)

const fontString = (t: LdTextElement) => {
  const px = (t.fontSizePt || 11) * PX_PER_PT
  const bold = t.fontStyle === 'bold' || t.fontStyle === 'bolditalic'
  const italic = t.fontStyle === 'italic' || t.fontStyle === 'bolditalic'
  return `${italic ? 'italic ' : ''}${bold ? '700 ' : '400 '}${px}px ${FONT_CSS[t.fontFamily] || FONT_CSS.helvetica}`
}

const wrapLine = (ctx: CanvasRenderingContext2D, text: string, maxPx: number): string[] => {
  const words = text.split(/(\s+)/) // keep spaces
  const out: string[] = []
  let cur = ''
  for (const w of words) {
    const test = cur + w
    if (ctx.measureText(test).width > maxPx && cur.trim() !== '') {
      out.push(cur.replace(/\s+$/, ''))
      cur = w.replace(/^\s+/, '')
    } else {
      cur = test
    }
  }
  if (cur.trim() !== '' || out.length === 0) out.push(cur.replace(/\s+$/, ''))
  return out
}

const textLines = computed<string[]>(() => {
  const t = asText.value
  const raw = String(t.text ?? '')
  if (!t.wrap) return raw.split('\n')
  const ctx = measureCtx()
  if (!ctx) return raw.split('\n')
  ctx.font = fontString(t)
  const maxPx = Math.max(1, t.w) * PX_PER_MM
  return raw.split('\n').flatMap(p => wrapLine(ctx, p, maxPx))
})

const textLayout = computed(() => {
  const t = asText.value
  const fsMm = (t.fontSizePt || 11) * MM_PER_PT
  const lhMm = (t.fontSizePt || 11) * (t.lineHeight || 1.15) * MM_PER_PT
  const n = textLines.value.length
  const blockH = n * lhMm
  let yTop = t.y
  if (t.vAlign === 'middle') yTop = t.y + (t.h - blockH) / 2
  else if (t.vAlign === 'bottom') yTop = t.y + (t.h - blockH)
  let anchorX = t.x
  let textAnchor = 'start'
  if (t.align === 'center') { anchorX = t.x + t.w / 2; textAnchor = 'middle' }
  else if (t.align === 'right') { anchorX = t.x + t.w; textAnchor = 'end' }
  return {
    fsMm,
    lhMm,
    anchorX,
    textAnchor,
    weight: (t.fontStyle === 'bold' || t.fontStyle === 'bolditalic') ? 700 : 400,
    italic: (t.fontStyle === 'italic' || t.fontStyle === 'bolditalic'),
    family: FONT_CSS[t.fontFamily] || FONT_CSS.helvetica,
    color: t.color || '#000000',
    letterSpacing: (t.letterSpacingPt || 0) * MM_PER_PT,
    lines: textLines.value.map((text, i) => ({ text, y: yTop + i * lhMm }))
  }
})

/* --------------------------- CODES ------------------------------ */
const codeHref = ref<string>('')
const codeInvalid = ref(false)
const isCode = computed(() => ['barcode', 'qr', 'datamatrix'].includes(props.el.type))

const refreshCode = async () => {
  if (!isCode.value) return
  codeInvalid.value = false
  const raster = await renderCode(props.el as LdCodeElement, { dpi: 144 })
  if (raster) { codeHref.value = raster.dataUrl } else { codeHref.value = ''; codeInvalid.value = true }
}

watch(
  () => isCode.value
    ? JSON.stringify({ t: props.el.type, v: (props.el as any).value, s: (props.el as any).symbology, e: (props.el as any).eccLevel, b: (props.el as any).barColor, g: (props.el as any).bgColor, st: (props.el as any).showText })
    : '',
  () => { refreshCode() },
  { immediate: true }
)

const codeAspect = computed(() => isSquareCode(props.el as LdCodeElement) ? 'xMidYMid meet' : 'none')

/* --------------------------- IMAGE ------------------------------ */
const asImage = computed(() => props.el as LdImageElement)
const imageAspect = computed(() => {
  const f = asImage.value.fit
  if (f === 'fill') return 'none'
  if (f === 'cover') return 'xMidYMid slice'
  return 'xMidYMid meet'
})

/* ------------------- SHAPES (typed views) ----------------------- */
// Kept in script (not template) so no TS `as`/`!` syntax reaches the template.
const rectEl = computed(() => props.el as LdRectElement)
const ellipseEl = computed(() => props.el as LdEllipseElement)
const lineEl = computed(() => props.el as LdLineElement)
const lineDash = computed(() => {
  const d = (props.el as LdLineElement).dash
  return d && d.length ? d.join(' ') : undefined
})
</script>

<template>
  <g :transform="rotateTransform">
    <!-- TEXT -->
    <text
      v-if="el.type === 'text'"
      :font-size="textLayout.fsMm"
      :font-family="textLayout.family"
      :font-weight="textLayout.weight"
      :font-style="textLayout.italic ? 'italic' : 'normal'"
      :fill="textLayout.color"
      :text-anchor="textLayout.textAnchor"
      :letter-spacing="textLayout.letterSpacing"
      dominant-baseline="text-before-edge"
      style="white-space: pre"
    >
      <tspan
        v-for="(ln, i) in textLayout.lines"
        :key="i"
        :x="textLayout.anchorX"
        :y="ln.y"
      >{{ ln.text === '' ? ' ' : ln.text }}</tspan>
    </text>

    <!-- RECT -->
    <rect
      v-else-if="el.type === 'rect'"
      :x="el.x" :y="el.y" :width="el.w" :height="el.h"
      :rx="rectEl.radiusMm || 0"
      :fill="fillAttr(rectEl.fill)"
      :stroke="strokeAttr(rectEl.stroke)"
      :stroke-width="rectEl.strokeWidthMm"
    />

    <!-- ELLIPSE -->
    <ellipse
      v-else-if="el.type === 'ellipse'"
      :cx="el.x + el.w / 2" :cy="el.y + el.h / 2"
      :rx="el.w / 2" :ry="el.h / 2"
      :fill="fillAttr(ellipseEl.fill)"
      :stroke="strokeAttr(ellipseEl.stroke)"
      :stroke-width="ellipseEl.strokeWidthMm"
    />

    <!-- LINE -->
    <line
      v-else-if="el.type === 'line'"
      :x1="el.x" :y1="el.y" :x2="el.x + el.w" :y2="el.y + el.h"
      :stroke="strokeAttr(lineEl.stroke)"
      :stroke-width="lineEl.strokeWidthMm"
      stroke-linecap="round"
      :stroke-dasharray="lineDash"
    />

    <!-- IMAGE -->
    <template v-else-if="el.type === 'image'">
      <image
        v-if="asImage.dataUrl"
        :href="asImage.dataUrl"
        :x="el.x" :y="el.y" :width="el.w" :height="el.h"
        :preserveAspectRatio="imageAspect"
        :opacity="asImage.opacity ?? 1"
      />
      <g v-else>
        <rect :x="el.x" :y="el.y" :width="el.w" :height="el.h" fill="#f4f4f5" stroke="#c9c9cf" :stroke-width="0.2" stroke-dasharray="1 1" />
        <text :x="el.x + el.w / 2" :y="el.y + el.h / 2" font-size="3" fill="#9a9aa3" text-anchor="middle" dominant-baseline="middle">Image</text>
      </g>
    </template>

    <!-- CODES -->
    <template v-else-if="isCode">
      <image
        v-if="codeHref"
        :href="codeHref"
        :x="el.x" :y="el.y" :width="el.w" :height="el.h"
        :preserveAspectRatio="codeAspect"
      />
      <g v-else>
        <rect :x="el.x" :y="el.y" :width="el.w" :height="el.h" :fill="codeInvalid ? '#fdecec' : '#f4f4f5'" :stroke="codeInvalid ? '#d64545' : '#c9c9cf'" :stroke-width="0.2" stroke-dasharray="1 1" />
        <text :x="el.x + el.w / 2" :y="el.y + el.h / 2" font-size="3" :fill="codeInvalid ? '#d64545' : '#9a9aa3'" text-anchor="middle" dominant-baseline="middle">{{ codeInvalid ? 'invalid' : el.type }}</text>
      </g>
    </template>
  </g>
</template>
