<script setup lang="ts">
import SignaturePad from 'signature_pad'

const props = withDefaults(
  defineProps<{
    bg?: string
    pen?: string
  }>(),
  {
    bg: '#F8FAF9',
    pen: 'black',
  },
)

const canvasPanelRef = ref()
const canvasRef = ref()
let signaturePad: any = null
let hasBackground = false
let backgroundSrc: string | null = null
let paintTaskId = 0

function fillCanvasBackground() {
  if (!canvasRef.value) return
  const canvas = canvasRef.value as HTMLCanvasElement
  const ctx = canvas.getContext('2d')
  if (!ctx) return
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  ctx.fillStyle = props.bg
  ctx.fillRect(0, 0, canvas.width, canvas.height)
}

async function drawBackground(src: string, taskId = paintTaskId) {
  if (!canvasRef.value || !src) return
  const canvas = canvasRef.value as HTMLCanvasElement
  const ctx = canvas.getContext('2d')
  if (!ctx) return

  const draw = async (imgSrc: string) => {
    const img = new Image()
    img.src = imgSrc
    await new Promise<void>((resolve, reject) => {
      img.onload = () => resolve()
      img.onerror = () => reject(new Error('Image load error'))
    })
    if (taskId !== paintTaskId) return
    const sw = img.naturalWidth || img.width
    const sh = img.naturalHeight || img.height
    const cw = canvas.width
    const ch = canvas.height
    const scale = Math.min(1, cw / sw, ch / sh)
    const dw = sw * scale
    const dh = sh * scale
    const dx = (cw - dw) / 2
    const dy = (ch - dh) / 2
    ctx.drawImage(img, dx, dy, dw, dh)
  }

  try {
    await draw(src)
  } catch {
    try {
      const res = await fetch(src)
      const blob = await res.blob()
      const reader = new FileReader()
      await new Promise<void>(resolve => {
        reader.onload = () => resolve()
        reader.readAsDataURL(blob)
      })
      await draw(String(reader.result || ''))
    } catch {}
  }
}

async function repaintWith(pointGroups?: any[]) {
  if (!signaturePad) return
  const taskId = ++paintTaskId
  signaturePad.clear()
  if (backgroundSrc) {
    await drawBackground(backgroundSrc, taskId)
  }
  if (taskId !== paintTaskId) return
  if (pointGroups?.length) {
    signaturePad.fromData(pointGroups, backgroundSrc ? { clear: false } : undefined)
  }
}

function handleSave() {
  if (signaturePad.isEmpty() && !hasBackground) {
    return { url: null, blob: null }
  }
  return {
    url: signaturePad.toDataURL('image/jpeg', 0.85),
    data: JSON.stringify(signaturePad.toData()),
  }
}

function handleSaveAsync(): Promise<{ blob: Blob | null; data: string | null }> {
  return new Promise(resolve => {
    if (signaturePad.isEmpty() && !hasBackground) {
      resolve({ blob: null, data: null })
      return
    }
    const canvas = canvasRef.value as HTMLCanvasElement
    const pointData = JSON.stringify(signaturePad.toData())
    canvas.toBlob(blob => resolve({ blob, data: pointData }), 'image/jpeg', 0.85)
  })
}

async function setBackgroundImage(url: string) {
  if (!canvasRef.value || !url || !signaturePad) return
  const data = signaturePad.toData()
  const taskId = ++paintTaskId
  signaturePad.clear()
  backgroundSrc = url
  await drawBackground(url, taskId)
  if (taskId !== paintTaskId) return
  if (data?.length) signaturePad.fromData(data, { clear: false })
  hasBackground = true
}

function handleRepaint(data?: string) {
  if (!signaturePad) return
  if (!data) {
    repaintWith().then(() => {})
    return
  }
  try {
    repaintWith(JSON.parse(data)).then(() => {})
  } catch {
    repaintWith().then(() => {})
  }
}

function handleLoad(data?: string) {
  if (!signaturePad || !data) return
  try {
    repaintWith(JSON.parse(data)).then(() => {})
  } catch {
    repaintWith().then(() => {})
  }
}

function handleFallback() {
  if (!signaturePad) return
  const data = signaturePad.toData()
  data.pop()
  repaintWith(data).then(() => {})
}

function handleClear() {
  if (!signaturePad) return
  paintTaskId++
  backgroundSrc = null
  hasBackground = false
  signaturePad.clear()
  fillCanvasBackground()
}

function handleEraser() {
  signaturePad.penColor = props.bg
  signaturePad.minWidth = 50
  signaturePad.maxWidth = 50
}

function handlePen() {
  signaturePad.penColor = props.pen
  signaturePad.minWidth = 0.5
  signaturePad.maxWidth = 2.5
}

onMounted(() => {
  if (canvasRef.value) {
    canvasRef.value.width = canvasPanelRef.value.clientWidth
    canvasRef.value.height = canvasPanelRef.value.clientHeight
    signaturePad = new SignaturePad(canvasRef.value, {
      backgroundColor: props.bg,
      throttle: 0,
    })
    fillCanvasBackground()
  }
})

defineExpose({
  handleSave,
  handleSaveAsync,
  handleRepaint,
  handleLoad,
  handleFallback,
  handleClear,
  handlePen,
  handleEraser,
  setBackgroundImage,
})
</script>
<template>
  <div class="signature-pad">
    <div ref="canvasPanelRef" class="canvas-panel">
      <canvas ref="canvasRef" width="300" height="200"></canvas>
    </div>
  </div>
</template>
<style scoped lang="scss">
.signature-pad {
  width: 100%;
  height: 100%;
  .canvas-panel {
    width: 100%;
    height: 100%;
  }
}
</style>
