<script setup>
import QRCode from 'qrcode'
import UQRCode from 'uqrcodejs'
import { computed, getCurrentInstance, nextTick, onMounted, ref, toRefs, watch } from 'vue'
import { envConfig } from '@/constants/env'
import { readLocalFileAsBase64 } from '@/utils/appLocalFile'
import { setPosterMergePending } from '@/utils/posterRenderMergeBridge'
import ImageSaveGuide from './ImageSaveGuide.vue'
import '@/utils/textEncoderPolyfill'

const props = defineProps({
  linkIdentifier: {
    type: String,
    required: true,
  },
  mode: {
    type: String,
    default: 'promote', // 例如 "promote" | "invitation"
  },
})
const { linkIdentifier, mode } = toRefs(props)
/** 自定义组件内 canvas API 需传入实例；勿在异步回调里再调 getCurrentInstance()（常为 null） */
const componentInstance = getCurrentInstance()
const posterCanvasRefs = ref([]) // 用于绘制海报的canvas数组
/** APP：canvas 绘制缓冲区宽高（须与 drawImage 使用的一致，否则可能空白或裁切异常） */
const posterCanvasSizes = ref(/** @type {Record<number, { width: number, height: number }>} */({}))
/** App：二维码临时路径，用于叠在底图上预览（不依赖 canvas 导出图） */
const posterQrPath = ref(/** @type {Record<number, string>} */({}))
/** App：离屏 canvas 合成后导出的整图路径，仅用于保存相册 */
const posterCompositePath = ref(/** @type {Record<number, string>} */({}))
const currentIndex = ref(0) // 当前显示的海报索引
const postersGenerated = ref([]) // 标记海报是否已经生成过，将在onMounted中初始化
const show = defineModel('show')
const canvasIdPrefix = 'posterCanvas'
/**
 * App：大图合成用 renderjs（视图层真实 DOM + HTML5 Canvas），避免 legacy canvas 大图只导出左上角。
 * 二维码仍用下方小 legacy 画布 uqrcode + canvasToTempFilePath。
 * 合成在 renderjs 内 document.createElement('canvas')，不依赖页面里挂 id 的 canvas（App 上常取不到 DOM）。
 */
const posterMergeRequest = ref(/** @type {null | Record<string, unknown>} */(null))
/** APP 端生成二维码专用（与海报主 canvas 分离），供 uqrcodejs + uni.createCanvasContext 使用 */
const QR_GEN_CANVAS_ID = 'poster-qr-gen'
const QR_GEN_SIZE = 150

/** 设为 false 可关闭海报合成相关 console（真机调试时在 HBuilderX / 自定义基座 控制台看 [QRcode]） */
const QR_POSTER_DEBUG = true

function qlog(...args) {
  if (QR_POSTER_DEBUG)
    console.warn('[QRcode]', ...args)
}

const isWebPlatform = computed(() => {
  try {
    return uni.getSystemInfoSync().uniPlatform === 'web'
  }
  catch {
    return typeof window !== 'undefined'
  }
})

function showToast(options) {
  const message = typeof options === 'string' ? options : (options?.message || options?.title || '')
  if (!message)
    return
  uni.showToast({
    title: message,
    icon: options?.type === 'success' ? 'success' : 'none',
  })
}

// 微信环境检测
const isWeChat = computed(() => {
  if (typeof navigator === 'undefined')
    return false
  return /MicroMessenger/i.test(navigator.userAgent)
})

// 图片保存指引遮罩层相关
const showImageGuide = ref(false)
const currentImageUrl = ref('')
const imageGuideTitle = ref('')
const url = computed(() => {
  const baseUrl = (envConfig.siteOrigin || '').replace(/\/$/, '') || (typeof window !== 'undefined' ? window.location.origin : '')
  return mode.value === 'promote'
    ? `${baseUrl}/agent/promotionInquire/`
    : `${baseUrl}/agent/invitationAgentApply/`
})

// 海报图片数组
const posterImages = ref([])

/**
 * 二维码在设计稿中的坐标系（与 tg_qrcode / yq 等正式海报资源像素一致）。
 * 占位图等小尺寸资源必须通过 scaleQrRectForPoster 换算，否则 y=1620 等会画在画布外，看起来像「没有二维码」。
 */
const POSTER_DESIGN_PX = {
  promote: { width: 1242, height: 2208 },
  invitation: { width: 1242, height: 2208 },
}

/** getImageInfo 仍异常时的兜底（与 POSTER_DESIGN_PX 一致，便于坐标换算） */
const POSTER_SIZE_FALLBACK_PX = { width: 1242, height: 2208 }

/**
 * getImageInfo 在部分平台对 SVG 等异常资源返回 width/height ≤0，需兜底后再参与绘制与坐标换算。
 */
function normalizeImageSize(info, fallback) {
  const w = typeof info.width === 'number' ? info.width : 0
  const h = typeof info.height === 'number' ? info.height : 0
  if (w > 0 && h > 0)
    return info
  qlog('normalizeImageSize: invalid size, using fallback', { w, h, fallback })
  return { ...info, width: fallback.width, height: fallback.height }
}

/** 将设计稿上的二维码矩形换算为当前底图实际像素 */
function scaleQrRectForPoster(position, posterInfo, modeKey) {
  const design = POSTER_DESIGN_PX[modeKey] || POSTER_DESIGN_PX.promote
  const sx = posterInfo.width / design.width
  const sy = posterInfo.height / design.height
  const scale = Math.min(sx, sy)
  const x = position.x * sx
  const y = position.y < 0
    ? posterInfo.height + position.y * sy
    : position.y * sy
  const size = position.size * scale
  return { x, y, size }
}

// QR码位置配置（为每个海报单独配置）
const qrCodePositions = ref({
  promote: [
    { x: 405, y: 1620, size: 480 }, // tg_qrcode_1.png
    { x: 405, y: 1620, size: 480 }, // tg_qrcode_2.jpg
    { x: 405, y: 1620, size: 480 }, // tg_qrcode_3.jpg
    { x: 405, y: 1620, size: 480 }, // tg_qrcode_4.jpg
    { x: 405, y: 1620, size: 480 }, // tg_qrcode_5.jpg
    { x: 180, y: 1440, size: 300 }, // tg_qrcode_6.jpg
    { x: 255, y: 940, size: 250 }, // tg_qrcode_7.jpg
    { x: 255, y: 940, size: 250 }, // tg_qrcode_8.jpg
  ],
  // invitation模式的配置 (yq_qrcode)
  invitation: [
    { x: 405, y: -1590, size: 435 }, // yq_qrcode_1.png
  ],
})

function getCanvasId(index) {
  return `${canvasIdPrefix}-${index}`
}

/** H5：预览区中等比展示整图（contain），在弹窗轮播高度内完整可见、宽可不拉满 */
function posterCanvasContainStyle(index) {
  const w = posterCanvasSizes.value[index]?.width ?? POSTER_SIZE_FALLBACK_PX.width
  const h = posterCanvasSizes.value[index]?.height ?? POSTER_SIZE_FALLBACK_PX.height
  return {
    aspectRatio: `${w} / ${h}`,
    maxWidth: '100%',
    maxHeight: '100%',
    width: 'auto',
    height: 'auto',
    objectFit: 'contain',
    display: 'block',
  }
}

function dataUrlFromBase64(mime, base64) {
  return `data:${mime};base64,${base64}`
}

function imageMimeFromInfoType(type) {
  const t = (type || 'png').toLowerCase()
  if (t === 'jpg' || t === 'jpeg')
    return 'image/jpeg'
  return `image/${t}`
}

/**
 * App：renderjs 视图层合成整图，逻辑层收 dataUrl 后写入 _doc。
 * 底图/二维码均以 data URL 传入，避免跨源 file:// 导致画布 tainted。
 */
async function compositePosterWithRenderJs(index) {
  const posterSrc = posterImages.value[index]
  const posterInfoRaw = await getImageInfo(posterSrc)
  const posterInfo = normalizeImageSize(posterInfoRaw, POSTER_SIZE_FALLBACK_PX)
  const qrRel = posterQrPath.value[index]
  if (!qrRel)
    throw new Error('missing qr path')
  const qrInfoRaw = await getImageInfo(qrRel)
  const qrInfo = normalizeImageSize(qrInfoRaw, { width: QR_GEN_SIZE, height: QR_GEN_SIZE })

  const positions = qrCodePositions.value[mode.value]
  const position = positions[index] || positions[0]
  const { x: qrX, y: qrY, size: qrSize } = scaleQrRectForPoster(position, posterInfo, mode.value)

  const pw = posterInfo.width
  const ph = posterInfo.height

  posterCanvasSizes.value = {
    ...posterCanvasSizes.value,
    [index]: { width: pw, height: ph },
  }

  const posterB64 = await readLocalFileAsBase64(posterInfo.path)
  const qrB64 = await readLocalFileAsBase64(qrInfo.path)
  const posterDataUrl = dataUrlFromBase64(imageMimeFromInfoType(posterInfo.type), posterB64)
  const qrDataUrl = dataUrlFromBase64('image/png', qrB64)

  const path = await new Promise((resolve, reject) => {
    setPosterMergePending({ resolve, reject, index })
    posterMergeRequest.value = null
    nextTick(() => {
      posterMergeRequest.value = {
        posterDataUrl,
        qrDataUrl,
        w: pw,
        h: ph,
        qrx: qrX,
        qry: qrY,
        qrs: qrSize,
        nonce: Date.now(),
      }
    })
  })
  qlog('poster renderjs ok', { index, path: String(path).slice(0, 72), wh: [pw, ph] })
  return path
}

/** App：合成导出（预览与保存共用）。有缓存则复用。 */
async function runPosterCompositeExport(index) {
  const cached = posterCompositePath.value[index]
  if (cached)
    return cached

  const path = await compositePosterWithRenderJs(index)
  posterCompositePath.value = { ...posterCompositePath.value, [index]: path }
  return path
}

/**
 * 单例合成 canvas，并发调用会互相踩缓冲区；所有合成（生成预览 / 保存）串行排队。
 */
let _compositeExportQueueTail = Promise.resolve()

function scheduleCompositeExport(index) {
  const p = _compositeExportQueueTail.then(() => runPosterCompositeExport(index))
  _compositeExportQueueTail = p.catch(() => { })
  return p
}

// 处理轮播图切换事件
function onSwipeChange(payload) {
  const nextIndex = typeof payload === 'number'
    ? payload
    : Number(payload?.detail?.current ?? 0)
  currentIndex.value = nextIndex
  if (!postersGenerated.value[nextIndex]) {
    generatePoster(nextIndex)
  }
}

/** 推广模式 8 张底图，邀请模式 1 张；文件放在 `src/static/images/`，运行时为 `/static/images/...` */
const PROMOTE_POSTER_URLS = [
  '/static/images/tg_qrcode_1.png',
  '/static/images/tg_qrcode_2.png',
  '/static/images/tg_qrcode_3.png',
  '/static/images/tg_qrcode_4.png',
  '/static/images/tg_qrcode_5.png',
  '/static/images/tg_qrcode_6.png',
  '/static/images/tg_qrcode_7.png',
  '/static/images/tg_qrcode_8.png',
]
const INVITATION_POSTER_URLS = ['/static/images/yq_qrcode_1.png']

function loadPosterImages() {
  return mode.value === 'promote' ? [...PROMOTE_POSTER_URLS] : [...INVITATION_POSTER_URLS]
}

onMounted(() => {
  posterImages.value = loadPosterImages()
  postersGenerated.value = Array.from({ length: posterImages.value.length }).fill(false)
})

watch(mode, () => {
  posterImages.value = loadPosterImages()
  postersGenerated.value = Array.from({ length: posterImages.value.length }).fill(false)
  currentIndex.value = 0
  posterCanvasSizes.value = {}
  posterQrPath.value = {}
  posterCompositePath.value = {}
  if (show.value) {
    nextTick(() => {
      generatePoster(currentIndex.value)
    })
  }
})

watch(linkIdentifier, () => {
  postersGenerated.value = Array.from({ length: posterImages.value.length }).fill(false)
  posterCanvasSizes.value = {}
  posterQrPath.value = {}
  posterCompositePath.value = {}
  if (show.value) {
    nextTick(() => {
      generatePoster(currentIndex.value)
    })
  }
})

async function getImageInfo(src) {
  return await new Promise((resolve, reject) => {
    uni.getImageInfo({
      src,
      success: resolve,
      fail: reject,
    })
  })
}

async function getQRCodeDataUrl() {
  return await new Promise((resolve, reject) => {
    QRCode.toDataURL(
      generalUrl(),
      { width: QR_GEN_SIZE, margin: 0 },
      (err, qrCodeUrl) => {
        if (err) {
          reject(err)
          return
        }
        resolve(qrCodeUrl)
      },
    )
  })
}

/**
 * APP 端：qrcode 的 toDataURL 依赖 document.createElement('canvas')，在 App 中会报
 * “You need to specify a canvas element”。改用 uqrcodejs + uni canvas 生成临时图片路径。
 */
async function getQRCodeTempPathForApp() {
  const instance = componentInstance?.proxy
  const link = generalUrl()
  qlog('getQRCodeTempPathForApp start', { linkLen: link.length, hasProxy: !!instance, canvasId: QR_GEN_CANVAS_ID })
  const qr = new UQRCode()
  qr.data = link
  qr.size = QR_GEN_SIZE
  qr.margin = 0
  qr.make()
  const ctx = uni.createCanvasContext(QR_GEN_CANVAS_ID, instance)
  qr.canvasContext = ctx
  await qr.drawCanvas()

  await new Promise(resolve => setTimeout(resolve, 300))

  return await new Promise((resolve, reject) => {
    uni.canvasToTempFilePath({
      canvasId: QR_GEN_CANVAS_ID,
      width: QR_GEN_SIZE,
      height: QR_GEN_SIZE,
      destWidth: QR_GEN_SIZE,
      destHeight: QR_GEN_SIZE,
      fileType: 'png',
      success: (res) => {
        qlog('canvasToTempFilePath(qr) ok', String(res.tempFilePath).slice(0, 80))
        resolve(res.tempFilePath)
      },
      fail: (err) => {
        qlog('canvasToTempFilePath(qr) fail', err)
        reject(err)
      },
    }, instance)
  })
}

async function generatePosterByWeb(index) {
  const canvas = posterCanvasRefs.value[index]
  if (!canvas)
    return

  const ctx = canvas.getContext('2d')
  const posterImg = new Image()
  posterImg.src = posterImages.value[index]

  posterImg.onload = async () => {
    canvas.width = posterImg.width
    canvas.height = posterImg.height
    ctx.drawImage(posterImg, 0, 0)

    try {
      const qrCodeUrl = await getQRCodeDataUrl()
      const qrCodeImg = new Image()
      qrCodeImg.src = qrCodeUrl
      qrCodeImg.onload = () => {
        const positions = qrCodePositions.value[mode.value]
        const position = positions[index] || positions[0]
        const qrY = position.y < 0 ? posterImg.height + position.y : position.y
        ctx.drawImage(qrCodeImg, position.x, qrY, position.size, position.size)
        postersGenerated.value[index] = true
      }
    }
    catch (error) {
      console.error('生成二维码失败:', error)
    }
  }
}

async function generatePosterByUni(index) {
  try {
    let sys = {}
    try {
      sys = uni.getSystemInfoSync()
    }
    catch (e) {
      qlog('getSystemInfoSync fail', e)
    }
    qlog('generatePosterByUni start', {
      index,
      canvasId: getCanvasId(index),
      mode: mode.value,
      hasProxy: !!componentInstance?.proxy,
      uniPlatform: sys.uniPlatform,
      windowWidth: sys.windowWidth,
    })

    const posterSrc = posterImages.value[index]
    const posterInfoRaw = await getImageInfo(posterSrc)
    const posterInfo = normalizeImageSize(posterInfoRaw, POSTER_SIZE_FALLBACK_PX)
    qlog('poster getImageInfo', {
      src: typeof posterSrc === 'string' ? posterSrc.slice(0, 120) : posterSrc,
      path: posterInfo.path,
      w: posterInfo.width,
      h: posterInfo.height,
      rawWh: [posterInfoRaw.width, posterInfoRaw.height],
      type: posterInfo.type,
    })

    posterCanvasSizes.value = {
      ...posterCanvasSizes.value,
      [index]: { width: posterInfo.width, height: posterInfo.height },
    }
    await nextTick()

    const qrTempPath = await getQRCodeTempPathForApp()
    posterQrPath.value = { ...posterQrPath.value, [index]: qrTempPath }
    qlog('qr temp for composite', { index, path: String(qrTempPath).slice(0, 80) })

    const positions = qrCodePositions.value[mode.value]
    const position = positions[index] || positions[0]
    const { x: qrX, y: qrY, size: qrSize } = scaleQrRectForPoster(position, posterInfo, mode.value)
    qlog('scaled qr rect', { raw: position, qrX, qrY, qrSize, posterWh: [posterInfo.width, posterInfo.height] })

    await scheduleCompositeExport(index)
    postersGenerated.value[index] = true
  }
  catch (error) {
    qlog('generatePosterByUni ERROR', error)
    console.error('App海报绘制失败:', error)
    showToast('海报生成失败，请稍后重试')
  }
}

// 生成海报并合成二维码
async function generatePoster(index) {
  if (postersGenerated.value[index])
    return

  await nextTick()
  qlog('generatePoster', { index, isWeb: isWebPlatform.value, show: show.value })
  if (isWebPlatform.value)
    await generatePosterByWeb(index)
  else
    await generatePosterByUni(index)
}

// 监听 show 变化，show 为 true 时生成海报
watch(show, (newVal) => {
  if (newVal && !postersGenerated.value[currentIndex.value]) {
    generatePoster(currentIndex.value) // 当弹窗显示且当前海报未生成时生成海报
  }
})

// 微信环境保存图片
async function exportCurrentPosterDataUrl() {
  const canvas = posterCanvasRefs.value[currentIndex.value]
  if (!canvas)
    throw new Error('海报尚未生成完成')
  return canvas.toDataURL('image/png')
}

async function exportCurrentPosterTempFilePath() {
  const idx = currentIndex.value
  try {
    return await scheduleCompositeExport(idx)
  }
  catch (e) {
    console.error('导出合成海报失败:', e)
    throw e
  }
}

async function savePosterForWeChat() {
  try {
    const dataURL = await exportCurrentPosterDataUrl()
    currentImageUrl.value = dataURL
    imageGuideTitle.value = '保存图片到相册'
    showImageGuide.value = true
  }
  catch (error) {
    console.error('获取海报失败:', error)
    showToast('海报尚未生成完成，请稍后重试')
  }
}

// 关闭图片保存指引
function closeImageGuide() {
  showImageGuide.value = false
}

// 保存海报图片 - 多种保存方式（非微信环境）
async function savePosterByUni() {
  try {
    const tempFilePath = await exportCurrentPosterTempFilePath()
    await new Promise((resolve, reject) => {
      uni.saveImageToPhotosAlbum({
        filePath: tempFilePath,
        success: resolve,
        fail: reject,
      })
    })
    showToast({ message: '图片已保存到相册' })
  }
  catch (error) {
    console.error('App 保存图片失败:', error)
    showToast({ message: '保存失败，请检查相册权限' })
  }
}

async function savePoster() {
  if (!isWebPlatform.value) {
    await savePosterByUni()
    return
  }

  try {
    const dataURL = await exportCurrentPosterDataUrl()
    const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
    if (isMobile)
      await saveForMobile(dataURL)
    else
      saveForPC(dataURL)
  }
  catch (error) {
    console.error('导出海报失败:', error)
    showToast({ message: '海报尚未生成完成，请稍后重试' })
  }
}

// PC浏览器保存方式
function saveForPC(dataURL) {
  const a = document.createElement('a')
  a.href = dataURL
  a.download = '赤眉海报.png'
  a.click()
}

// 手机浏览器保存方式
async function saveForMobile(dataURL) {
  // 方法1: 尝试使用 File System Access API (Chrome 86+)
  const fileSystemSuccess = await saveWithFileSystemAPI(dataURL)
  if (fileSystemSuccess)
    return

  // 方法2: 尝试使用 Blob 和 URL.createObjectURL
  try {
    const blob = dataURLToBlob(dataURL)
    const url = URL.createObjectURL(blob)

    const a = document.createElement('a')
    a.href = url
    a.download = '赤眉海报.png'
    a.style.display = 'none'
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)

    // 清理 URL 对象
    setTimeout(() => URL.revokeObjectURL(url), 100)

    showToast({ message: '图片已保存到相册' })
  }
  catch (error) {
    console.error('Blob保存失败:', error)
    // 方法3: 尝试使用 share API (支持分享到其他应用)
    const shareSuccess = await tryShareAPI(dataURL)
    if (!shareSuccess) {
      // 方法4: 降级到长按保存提示
      showLongPressTip(dataURL)
    }
  }
}

// 显示长按保存提示（非微信环境使用）
function showLongPressTip(dataURL) {
  // 设置遮罩层内容
  currentImageUrl.value = dataURL
  imageGuideTitle.value = '保存图片到相册'

  // 显示遮罩层
  showImageGuide.value = true
}

// 将 dataURL 转换为 Blob
function dataURLToBlob(dataURL) {
  const arr = dataURL.split(',')
  const mime = arr[0].match(/:(.*?);/)[1]
  const bstr = atob(arr[1])
  let n = bstr.length
  const u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n)
  }
  return new Blob([u8arr], { type: mime })
}

// 备用保存方法 - 使用 File System Access API (现代浏览器)
async function saveWithFileSystemAPI(dataURL) {
  if ('showSaveFilePicker' in window) {
    try {
      const blob = dataURLToBlob(dataURL)
      const fileHandle = await window.showSaveFilePicker({
        suggestedName: '赤眉海报.png',
        types: [{
          description: 'PNG images',
          accept: { 'image/png': ['.png'] },
        }],
      })

      const writable = await fileHandle.createWritable()
      await writable.write(blob)
      await writable.close()

      showToast({ message: '图片已保存' })
      return true
    }
    catch (error) {
      console.error('File System API 保存失败:', error)
      return false
    }
  }
  return false
}

// 尝试使用 Share API
async function tryShareAPI(dataURL) {
  if (navigator.share && navigator.canShare) {
    try {
      const blob = dataURLToBlob(dataURL)
      const file = new File([blob], '赤眉海报.png', { type: 'image/png' })

      if (navigator.canShare({ files: [file] })) {
        await navigator.share({
          title: '赤眉海报',
          text: '分享海报图片',
          files: [file],
        })
        showToast({ message: '图片已分享' })
        return true
      }
    }
    catch (error) {
      console.error('Share API 失败:', error)
    }
  }
  return false
}

function generalUrl() {
  return url.value + encodeURIComponent(linkIdentifier.value)
}

function copyUrl() {
  copyToClipboard(generalUrl())
}

// 复制链接
function copyToClipboard(text) {
  if (!isWebPlatform.value) {
    uni.setClipboardData({
      data: text,
      success: () => showToast({ message: '链接已复制！' }),
      fail: () => showToast({ message: '复制失败，请手动复制' }),
    })
    return
  }

  if (navigator.clipboard && window.isSecureContext) {
    // 支持 Clipboard API
    navigator.clipboard
      .writeText(text)
      .then(() => {
        showToast({ message: '链接已复制！' })
      })
      .catch((err) => {
        console.error('复制失败:', err)
      })
  }
  else {
    // 对于不支持 Clipboard API 的浏览器，使用 fallback 方法
    const textArea = document.createElement('textarea')
    textArea.value = text
    document.body.appendChild(textArea)
    textArea.select()
    try {
      document.execCommand('copy')
      showToast({ message: '链接已复制！' })
    }
    catch (err) {
      console.error('复制失败:', err)
    }
    finally {
      document.body.removeChild(textArea)
    }
  }
}
</script>

<script>
import { handlePosterRenderMergeDone, handlePosterRenderMergeFailed } from '@/utils/posterRenderMergeBridge'

export default {
  methods: {
    onPosterRenderMergeDone(e) {
      handlePosterRenderMergeDone(e?.dataUrl).catch((err) => {
        console.error('[QRcode] poster merge write failed', err)
      })
    },
    onPosterRenderMergeFailed(e) {
      handlePosterRenderMergeFailed(e?.err)
    },
  },
}
</script>

<script module="posterRender" lang="renderjs">
export default {
  methods: {
    onMergeReqChange(newVal, _oldVal, ownerInstance) {
      if (!newVal || !newVal.posterDataUrl || !newVal.qrDataUrl)
        return
      const c = document.createElement('canvas')
      c.width = newVal.w
      c.height = newVal.h
      const ctx = c.getContext('2d')
      if (!ctx) {
        ownerInstance.callMethod('onPosterRenderMergeFailed', { err: 'no_canvas_2d' })
        return
      }
      const p = new Image()
      p.onload = () => {
        const q = new Image()
        q.onload = () => {
          try {
            ctx.drawImage(p, 0, 0, newVal.w, newVal.h)
            ctx.drawImage(q, newVal.qrx, newVal.qry, newVal.qrs, newVal.qrs)
            const dataUrl = c.toDataURL('image/png')
            ownerInstance.callMethod('onPosterRenderMergeDone', { dataUrl })
          }
          catch (err) {
            ownerInstance.callMethod('onPosterRenderMergeFailed', { err: String(err) })
          }
        }
        q.onerror = () => ownerInstance.callMethod('onPosterRenderMergeFailed', { err: 'qr_image_load' })
        q.src = newVal.qrDataUrl
      }
      p.onerror = () => ownerInstance.callMethod('onPosterRenderMergeFailed', { err: 'poster_image_load' })
      p.src = newVal.posterDataUrl
    },
  },
}
</script>

<template>
  <!-- 放在弹窗外，避免 wd-popup 关闭时子树未挂载导致 APP 无法 createCanvasContext -->
  <canvas v-if="!isWebPlatform" :id="QR_GEN_CANVAS_ID" :canvas-id="QR_GEN_CANVAS_ID" class="poster-qr-gen-canvas" />
  <!-- App：仅作 renderjs 的 change 锚点；真正画布在 renderjs 里 createElement('canvas') -->
  <view
    v-if="!isWebPlatform" class="poster-renderjs-anchor" :poster-merge-req="posterMergeRequest"
    :change:poster-merge-req="posterRender.onMergeReqChange" aria-hidden="true"
  />
  <wd-popup v-model="show" round position="bottom" :style="{ maxHeight: '95vh' }">
    <view class="qrcode-popup-container">
      <view class="qrcode-content">
        <swiper
          class="poster-swiper rounded-lg shadow sm:rounded-xl" indicator-color="white" circular indicator-dots
          @change="onSwipeChange"
        >
          <swiper-item v-for="(_, index) in posterImages" :key="index" class="poster-swiper-item">
            <view class="poster-canvas-box">
              <template v-if="isWebPlatform">
                <view class="poster-preview-box web-preview">
                  <canvas
                    :id="getCanvasId(index)" :ref="(el) => (posterCanvasRefs[index] = el)"
                    :canvas-id="getCanvasId(index)" :width="posterCanvasSizes[index]?.width ?? 300"
                    :height="posterCanvasSizes[index]?.height ?? 300"
                    :style="posterCanvasContainStyle(index)"
                    class="poster-canvas poster-canvas--h5-contain rounded-lg sm:rounded-xl"
                  />
                </view>
              </template>
              <template v-else>
                <!-- App：预览与保存同源；aspectFit 在轮播高度内整图可见，等比、宽可不拉满 -->
                <view v-if="posterCompositePath[index]" class="poster-app-preview">
                  <image
                    :src="posterCompositePath[index]" mode="aspectFit"
                    class="poster-app-composite rounded-lg sm:rounded-xl"
                  />
                </view>
                <view v-else class="poster-preview-placeholder">
                  <text class="poster-preview-placeholder-text">
                    海报生成中…
                  </text>
                </view>
              </template>
            </view>
          </swiper-item>
        </swiper>
      </view>
      <view v-if="mode === 'promote'" class="swipe-tip mb-1 px-2 text-center text-xs text-gray-700 sm:mb-2 sm:text-sm">
        <text class="swipe-icon">
          ←
        </text> 左右滑动切换海报
        <text class="swipe-icon">
          →
        </text>
      </view>
      <wd-divider class="my-2 sm:my-3">
        保存与复制
      </wd-divider>

      <view class="flex items-center justify-around px-4 pb-3 sm:pb-4">
        <!-- 微信环境：保存与复制 -->
        <template v-if="isWeChat">
          <view class="flex flex-col cursor-pointer items-center justify-center" @click="savePosterForWeChat">
            <image src="/static/images/icon_share_img.svg" class="share-icon h-9 w-9 rounded-full sm:h-10 sm:w-10" />
            <view class="mt-1 text-center text-xs text-gray-600">
              保存图片
            </view>
          </view>
          <view class="flex flex-col cursor-pointer items-center justify-center" @click="copyUrl">
            <image src="/static/images/icon_share_url.svg" class="share-icon h-9 w-9 rounded-full sm:h-10 sm:w-10" />
            <view class="mt-1 text-center text-xs text-gray-600">
              复制链接
            </view>
          </view>
        </template>
        <!-- 非微信环境：显示保存和复制按钮 -->
        <template v-else>
          <view class="flex flex-col cursor-pointer items-center justify-center" @click="savePoster">
            <image src="/static/images/icon_share_img.svg" class="share-icon h-9 w-9 rounded-full sm:h-10 sm:w-10" />
            <view class="mt-1 text-center text-xs text-gray-600">
              保存图片
            </view>
          </view>
          <view class="flex flex-col cursor-pointer items-center justify-center" @click="copyUrl">
            <image src="/static/images/icon_share_url.svg" class="share-icon h-9 w-9 rounded-full sm:h-10 sm:w-10" />
            <view class="mt-1 text-center text-xs text-gray-600">
              复制链接
            </view>
          </view>
        </template>
      </view>
    </view>
  </wd-popup>

  <!-- 图片保存指引遮罩层 -->
  <ImageSaveGuide
    :show="showImageGuide" :image-url="currentImageUrl" :title="imageGuideTitle"
    @close="closeImageGuide"
  />
</template>

<style lang="scss" scoped>
.qrcode-popup-container {
  display: flex;
  flex-direction: column;
  max-height: 95vh;
  overflow: hidden;
}

/* 移出可视区域，避免与海报轮播重叠；尺寸需与 QR_GEN_SIZE 一致 */
.poster-qr-gen-canvas {
  position: fixed;
  left: -9999px;
  top: 0;
  width: 150px;
  height: 150px;
  overflow: hidden;
}

.qrcode-content {
  flex-shrink: 0;
  padding: 0.75rem;
}

/* 小屏设备优化 */
@media (max-width: 375px) {
  .qrcode-content {
    padding: 0.5rem;
  }
}

/* 中等及以上屏幕 */
@media (min-width: 640px) {
  .qrcode-content {
    padding: 1rem;
  }
}

.poster-swiper {
  height: calc(95vh - 180px);
  min-height: 300px;
  max-height: 500px;
  width: 100%;
}

/* 轮播项铺满高度，内部再「contain」缩放海报，避免 canvas 被 100%×100% 非等比拉伸、看起来像放大裁切 */
.poster-swiper-item {
  box-sizing: border-box;
  height: 100%;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.poster-canvas-box {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: stretch;
  justify-content: center;
  box-sizing: border-box;
}

.poster-canvas-box > view {
  flex: 1;
  min-width: 0;
  min-height: 0;
  width: 100%;
}

/* App：在轮播固定高度内等比居中，整图可见（不强制拉满宽） */
.poster-app-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.poster-app-composite {
  width: 100%;
  height: 100%;
}

/* App：renderjs 属性绑定锚点（不占布局） */
.poster-renderjs-anchor {
  position: fixed;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  z-index: -9999;
}

.poster-preview-box.web-preview {
  width: 100%;
  height: 100%;
  position: relative;
  flex-shrink: 0;
  overflow: hidden;
  border-radius: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

.poster-canvas--h5-contain {
  margin: 0 auto;
}

.poster-preview-placeholder {
  width: 100%;
  height: 100%;
  min-height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.poster-preview-placeholder-text {
  font-size: 14px;
  color: #999;
}

/* 小屏设备：更小的海报高度 */
@media (max-width: 375px) {
  .poster-swiper {
    height: calc(95vh - 160px);
    min-height: 280px;
    max-height: 400px;
  }
}

/* 中等屏幕 */
@media (min-width: 640px) and (max-width: 767px) {
  .poster-swiper {
    height: calc(95vh - 190px);
    max-height: 520px;
  }
}

/* 大屏幕 */
@media (min-width: 768px) {
  .poster-swiper {
    height: calc(95vh - 200px);
    max-height: 600px;
  }
}

/* H5：未加 contain 类名时兜底（当前 web 分支已用 poster-canvas--h5-contain） */
.poster-canvas {
  display: block;
}

.swipe-tip {
  animation: fadeInOut 2s infinite;
  flex-shrink: 0;
}

.swipe-icon {
  display: inline-block;
  animation: slideLeftRight 1.5s infinite;
  font-size: 14px;
}

@media (min-width: 640px) {
  .swipe-icon {
    font-size: 16px;
  }
}

.share-icon {
  transition: transform 0.2s ease;
}

.share-icon:active {
  transform: scale(0.95);
}

@keyframes fadeInOut {

  0%,
  100% {
    opacity: 0.5;
  }

  50% {
    opacity: 1;
  }
}

@keyframes slideLeftRight {

  0%,
  100% {
    transform: translateX(0);
  }

  50% {
    transform: translateX(5px);
  }
}

/* 优化 van-divider 在小屏幕上的间距 */
:deep(.van-divider) {
  margin: 0.5rem 0;
}

@media (min-width: 640px) {
  :deep(.van-divider) {
    margin: 0.75rem 0;
  }
}
</style>
