/**
 * 报告 PDF 导出 composable
 *
 * 将一个已渲染好的 DOM 节点截图为整张长图,
 * 然后由 jsPDF 按 A4 高度自动分页,生成多页 PDF。
 *
 * 关键点:
 *  - 不克隆 DOM(避免 canvas/svg 截图空白、scoped 样式丢失)
 *  - 把原 DOM 临时移到离屏容器,截图后恢复原位
 *  - 自动隐藏指定选择器的元素(如 .body-header / .el-loading-mask)
 *
 * 用法:
 *   const { exportReportPdf } = useReportPdfExport()
 *   await exportReportPdf(bodyWrapEl, 'report.pdf')
 */

import html2canvas from 'html2canvas'
import jsPDF from 'jspdf'
import { ElMessage } from 'element-plus'

export interface ExportReportOptions {
  /** 离屏容器宽度,默认 800 */
  width?: number
  /** 缩放比例,值越大越清晰,默认 2 */
  scale?: number
  /** JPEG 质量 0~1,默认 0.95 */
  quality?: number
  /** 临时隐藏元素的选择器列表(这些元素在截图前 display:none,完成后恢复) */
  hideSelectors?: string[]
  /** 是否不显示内部 loading 提示(由调用方自己控制) */
  silent?: boolean
}

export function useReportPdfExport() {
  /**
   * 将原 DOM 节点导出为 PDF。
   * @param sourceEl 已渲染好的 DOM 节点(通常是 .body-wrap)
   * @param fileName 下载文件名,需带 .pdf 后缀
   * @param options 额外配置
   */
  const exportReportPdf = async (
    sourceEl: HTMLElement,
    fileName: string,
    options: ExportReportOptions = {},
  ): Promise<void> => {
    if (!sourceEl) {
      ElMessage.error('未找到可导出的报告内容')
      return
    }

    const {
      width = 800,
      scale = 2,
      quality = 0.95,
      hideSelectors = ['.body-header', '.el-loading-mask', '.el-loading-spinner'],
      silent = false,
    } = options

    // 1) 记录原父节点 + 下一个兄弟节点,截图完毕后还原
    const originalParent = sourceEl.parentNode as HTMLElement
    const originalNext = sourceEl.nextSibling as ChildNode | null

    // 2) 读取 sourceEl 的计算样式,移动后强制 inline 注入 padding(防止全局样式在离屏环境下失效)
    const originalStyleAttr = sourceEl.getAttribute('style') || ''
    const computed = window.getComputedStyle(sourceEl)
    sourceEl.style.paddingTop = computed.paddingTop
    sourceEl.style.paddingRight = computed.paddingRight
    sourceEl.style.paddingBottom = computed.paddingBottom
    sourceEl.style.paddingLeft = computed.paddingLeft

    // 3) 临时隐藏指定元素(完成后恢复原 display 值)
    const hiddenDisplays: Array<{ el: HTMLElement; prev: string }> = []
    hideSelectors.forEach((sel) => {
      sourceEl.querySelectorAll<HTMLElement>(sel).forEach((el) => {
        hiddenDisplays.push({ el, prev: el.style.display })
        el.style.display = 'none'
      })
    })

    // 4) 创建离屏容器,把原 DOM 移进去(不克隆,保留 canvas/svg/scoped 样式)
    const offScreen = document.createElement('div')
    offScreen.style.cssText = [
      'position: fixed',
      'left: -99999px',
      'top: 0',
      `width: ${width}px`,
      'background: #ffffff',
      'z-index: -1',
      'pointer-events: none',
    ].join(';')
    document.body.appendChild(offScreen)
    offScreen.appendChild(sourceEl)

    // 4.1) 强制 sourceEl 宽度,让 ECharts/vue-echarts 按目标宽度重绘 canvas
    const prevWidth = sourceEl.style.width
    const prevMaxWidth = sourceEl.style.maxWidth
    const prevBoxSizing = sourceEl.style.boxSizing
    sourceEl.style.width = `${width}px`
    sourceEl.style.maxWidth = `${width}px`
    sourceEl.style.boxSizing = 'border-box'

    let loadingMsg: { close: () => void } | null = null
    if (!silent) {
      loadingMsg = ElMessage({
        message: '正在生成 PDF…',
        type: 'info',
        duration: 0,
      })
    }

    try {
      // 给浏览器一帧时间让样式/layout 生效
      await new Promise((r) => requestAnimationFrame(() => r(null)))
      await new Promise((r) => requestAnimationFrame(() => r(null)))

      // 给 ECharts canvas 充分时间完成 resize + 重绘(异步绘制,800ms 不够,延长到 1500ms)
      await new Promise((r) => setTimeout(r, 1500))

      const canvas = await html2canvas(sourceEl, {
        scale,
        backgroundColor: '#ffffff',
        logging: true,
        useCORS: false, // 全部本地资源
        allowTaint: true,
        windowWidth: width,
        foreignObjectRendering: false,
        // onclone 兜底:html2canvas 克隆出的 DOM 里,保护 canvas 不被丢弃 + 表格样式正常
        onclone: (clonedDoc) => {
          clonedDoc.querySelectorAll('canvas').forEach((c) => {
            ;(c as HTMLElement).style.display = 'block'
            ;(c as HTMLElement).style.visibility = 'visible'
          })
          clonedDoc.querySelectorAll('.report-table').forEach((t) => {
            ;(t as HTMLElement).style.tableLayout = 'auto'
          })
        },
      })

      const imgData = canvas.toDataURL('image/jpeg', quality)

      const pdf = new jsPDF('p', 'mm', 'a4')
      const pdfWidth = pdf.internal.pageSize.getWidth() // 210mm
      const pdfHeight = pdf.internal.pageSize.getHeight() // 297mm

      const imgWidth = pdfWidth
      const imgHeight = (canvas.height * imgWidth) / canvas.width

      let heightLeft = imgHeight
      let position = 0

      // 第一页(从顶部开始)
      pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight)
      heightLeft -= pdfHeight

      // 超出 A4 一页时,继续分页
      while (heightLeft > 0) {
        position = heightLeft - imgHeight
        pdf.addPage()
        pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight)
        heightLeft -= pdfHeight
      }

      pdf.save(fileName)

      if (!silent) ElMessage.success('PDF 下载成功')
    } catch (err) {
      console.error('[useReportPdfExport] PDF 生成失败:', err)
      if (!silent) ElMessage.error((err as Error)?.message || 'PDF 生成失败')
      throw err
    } finally {
      loadingMsg?.close()
      // 恢复隐藏元素的 display
      hiddenDisplays.forEach(({ el, prev }) => {
        el.style.display = prev
      })
      // 把原 DOM 移回原位置
      if (offScreen.parentNode) {
        offScreen.parentNode.removeChild(offScreen)
      }
      if (originalNext && originalNext.parentNode === originalParent) {
        originalParent.insertBefore(sourceEl, originalNext)
      } else {
        originalParent.appendChild(sourceEl)
      }
      // 恢复 sourceEl 原 inline style(去掉临时注入的 padding 和宽度)
      sourceEl.setAttribute('style', originalStyleAttr)
      sourceEl.style.width = prevWidth
      sourceEl.style.maxWidth = prevMaxWidth
      sourceEl.style.boxSizing = prevBoxSizing
    }
  }

  return { exportReportPdf }
}
