/**
 * 文章多格式导出工具
 * 支持 Markdown、纯文本、PDF、PNG、PPT 等格式
 */
import html2canvas from 'html2canvas'
import { jsPDF } from 'jspdf'
import PptxGenJS from 'pptxgenjs'
import type { Post } from '../services/api'
import {
  buildPptSlides,
  estimateBlockHeight,
  inferVideoExtn,
  resolveImageUrl,
  type ContentBlock,
  type PptSectionSlide,
  type PptSlide,
} from './articlePptSlides'
import { buildExportBasename, downloadBlob, downloadText } from './fileDownload'
import '../styles/articleExport.css'

/** 导出视觉主题：浅色适合打印，深色与页面一致 */
export type ExportTheme = 'light' | 'dark'

/** 支持的导出格式 */
export type ExportFormat = 'md' | 'txt' | 'pdf' | 'png' | 'ppt-structured' | 'ppt-screenshot'

/** 导出操作结果 */
export interface ExportResult {
  success: boolean
  warning?: string
}

const EXPORT_CONTAINER_WIDTH = 800
const PPT_CONTENT_X = 0.5
const PPT_CONTENT_W = 9
const PPT_SLIDE_BOTTOM = 5.1
const MAX_VIDEO_EXPORT_BYTES = 30 * 1024 * 1024

function buildMarkdownFrontmatter(post: Post): string {
  const tags = post.tags?.map((t) => t.name).join(', ') || ''
  const lines = [
    '---',
    `title: ${JSON.stringify(post.title)}`,
    `date: ${post.date || ''}`,
    `category: ${JSON.stringify(post.categoryName || '未分类')}`,
  ]
  if (tags) lines.push(`tags: [${post.tags?.map((t) => JSON.stringify(t.name)).join(', ')}]`)
  if (post.userName) lines.push(`author: ${JSON.stringify(post.userName)}`)
  lines.push('---', '')
  return lines.join('\n')
}

function markdownToPlainText(markdown: string): string {
  let text = markdown
  text = text.replace(/```[\s\S]*?```/g, (block) =>
    block.replace(/^```[\w-]*\n?/, '').replace(/```$/, '').trim()
  )
  text = text.replace(/`([^`]+)`/g, '$1')
  text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, '$1')
  text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
  text = text.replace(/^#{1,6}\s+/gm, '')
  text = text.replace(/(\*\*|__)(.*?)\1/g, '$2')
  text = text.replace(/(\*|_)(.*?)\1/g, '$2')
  text = text.replace(/^>\s+/gm, '')
  text = text.replace(/^[-*+]\s+/gm, '• ')
  text = text.replace(/<[^>]+>/g, '')
  text = text.replace(/\n{3,}/g, '\n\n')
  return text.trim()
}

function buildTextHeader(post: Post): string {
  const lines = [
    post.title,
    '='.repeat(Math.min(post.title.length, 40)),
    `日期：${post.date || '未知'}`,
    `分类：${post.categoryName || '未分类'}`,
  ]
  if (post.userName) lines.push(`作者：${post.userName}`)
  if (post.tags?.length) lines.push(`标签：${post.tags.map((t) => t.name).join('、')}`)
  lines.push('', '---', '')
  return lines.join('\n')
}

function sanitizeExportHtml(html: string): string {
  if (typeof DOMParser === 'undefined') return html
  const doc = new DOMParser().parseFromString(html, 'text/html')
  doc.querySelectorAll('video').forEach((video) => {
    const poster = video.getAttribute('poster')
    const placeholder = document.createElement('div')
    placeholder.className = 'export-video-placeholder'
    placeholder.textContent = poster ? '[视频]' : '[视频内容]'
    placeholder.style.cssText = 'padding:2rem;text-align:center;color:#888;border:1px dashed #ccc;border-radius:8px;margin:1rem 0;'
    video.replaceWith(placeholder)
  })
  return doc.body.innerHTML
}

function buildExportContainer(post: Post, renderedHtml: string, theme: ExportTheme): HTMLDivElement {
  const container = document.createElement('div')
  container.className = `article-export-root export-theme-${theme}`
  container.style.position = 'fixed'
  container.style.left = '-9999px'
  container.style.top = '0'
  container.style.width = `${EXPORT_CONTAINER_WIDTH}px`
  container.style.zIndex = '-1'

  const tagNames = post.tags?.map((t) => t.name).join(' · ') || ''
  const metaParts = [post.date, post.categoryName || '未分类', tagNames].filter(Boolean)

  container.innerHTML = `
    <div class="export-header">
      <div class="export-header-meta">
        <span class="export-category">${escapeHtml(post.categoryName || '未分类')}</span>
        <span class="export-meta">${escapeHtml(metaParts.join(' · '))}</span>
      </div>
      <h1 class="export-title">${escapeHtml(post.title)}</h1>
      ${post.userName ? `<div class="export-meta export-author">作者：${escapeHtml(post.userName)}</div>` : ''}
    </div>
    <div class="export-body">${sanitizeExportHtml(renderedHtml)}</div>
  `

  container.querySelectorAll('.copy-btn').forEach((el) => el.remove())
  document.body.appendChild(container)
  return container
}

function escapeHtml(str: string): string {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
}

async function fetchImageAsDataUrl(url: string): Promise<{ data: string | null; failed: boolean }> {
  try {
    const response = await fetch(resolveImageUrl(url), { mode: 'cors' })
    if (!response.ok) return { data: null, failed: true }
    const blob = await response.blob()
    const data = await new Promise<string>((resolve, reject) => {
      const reader = new FileReader()
      reader.onload = () => resolve(reader.result as string)
      reader.onerror = reject
      reader.readAsDataURL(blob)
    })
    return { data, failed: false }
  } catch {
    return { data: null, failed: true }
  }
}

async function fetchVideoAsBase64(
  url: string,
  mimeType?: string
): Promise<{ data: string | null; extn: string; failed: boolean; tooLarge: boolean }> {
  try {
    const response = await fetch(resolveImageUrl(url), { mode: 'cors' })
    if (!response.ok) return { data: null, extn: inferVideoExtn(url, mimeType), failed: true, tooLarge: false }
    const blob = await response.blob()
    if (blob.size > MAX_VIDEO_EXPORT_BYTES) {
      return { data: null, extn: inferVideoExtn(url, mimeType), failed: true, tooLarge: true }
    }
    const data = await new Promise<string>((resolve, reject) => {
      const reader = new FileReader()
      reader.onload = () => resolve(reader.result as string)
      reader.onerror = reject
      reader.readAsDataURL(blob)
    })
    return { data, extn: inferVideoExtn(url, blob.type || mimeType), failed: false, tooLarge: false }
  } catch {
    return { data: null, extn: inferVideoExtn(url, mimeType), failed: true, tooLarge: false }
  }
}

function createPptx(): PptxGenJS {
  const pptx = new PptxGenJS()
  pptx.layout = 'LAYOUT_16x9'
  pptx.author = 'Blog Export'
  return pptx
}

function addTitleSlideToPptx(pptx: PptxGenJS, slide: Extract<PptSlide, { kind: 'title' }>): void {
  const pptSlide = pptx.addSlide()
  const metaLines = [
    slide.date || '',
    slide.categoryName || '未分类',
    slide.userName ? `作者：${slide.userName}` : '',
    slide.tagNames.length ? `标签：${slide.tagNames.join(' · ')}` : '',
  ].filter(Boolean)

  pptSlide.addText(slide.title, {
    x: PPT_CONTENT_X,
    y: 1.4,
    w: PPT_CONTENT_W,
    h: 1.5,
    fontSize: 32,
    bold: true,
    color: '1A1A1A',
    align: 'center',
    valign: 'middle',
  })

  if (metaLines.length) {
    pptSlide.addText(metaLines.join('\n'), {
      x: PPT_CONTENT_X,
      y: 3.2,
      w: PPT_CONTENT_W,
      h: 1.5,
      fontSize: 14,
      color: '666666',
      align: 'center',
      valign: 'top',
    })
  }
}

function addSectionHeader(slide: PptxGenJS.Slide, title: string, y = 0.35): number {
  slide.addText(title, {
    x: PPT_CONTENT_X,
    y,
    w: PPT_CONTENT_W,
    h: 0.6,
    fontSize: 22,
    bold: true,
    color: '1A1A1A',
  })
  slide.addShape('rect' as PptxGenJS.SHAPE_NAME, {
    x: PPT_CONTENT_X,
    y: y + 0.55,
    w: 1.2,
    h: 0.05,
    fill: { color: 'C9A962' },
    line: { color: 'C9A962' },
  })
  return y + 0.85
}

async function addBlockToPptxSlide(
  slide: PptxGenJS.Slide,
  block: ContentBlock,
  y: number,
  mediaWarning: { value: boolean }
): Promise<number> {
  switch (block.type) {
    case 'h1':
      slide.addText(block.text, {
        x: PPT_CONTENT_X,
        y,
        w: PPT_CONTENT_W,
        h: 0.5,
        fontSize: 22,
        bold: true,
        color: '222222',
      })
      return y + 0.6

    case 'h2':
      slide.addText(block.text, {
        x: PPT_CONTENT_X,
        y,
        w: PPT_CONTENT_W,
        h: 0.45,
        fontSize: 18,
        bold: true,
        color: '333333',
      })
      return y + 0.5

    case 'h3':
      slide.addText(block.text, {
        x: PPT_CONTENT_X,
        y,
        w: PPT_CONTENT_W,
        h: 0.4,
        fontSize: 16,
        bold: true,
        color: '333333',
      })
      return y + 0.45

    case 'paragraph':
      slide.addText(block.text, {
        x: PPT_CONTENT_X,
        y,
        w: PPT_CONTENT_W,
        h: PPT_SLIDE_BOTTOM - y,
        fontSize: 14,
        color: '444444',
        valign: 'top',
        breakLine: true,
      })
      return y + estimateBlockHeight(block) + 0.1

    case 'code': {
      const label = block.lang ? block.lang.toUpperCase() : 'CODE'
      slide.addText(label, {
        x: PPT_CONTENT_X,
        y,
        w: PPT_CONTENT_W,
        h: 0.25,
        fontSize: 9,
        color: '888888',
      })
      const codeHeight = estimateBlockHeight(block)
      slide.addText(block.text, {
        x: PPT_CONTENT_X,
        y: y + 0.25,
        w: PPT_CONTENT_W,
        h: codeHeight,
        fontSize: 10,
        fontFace: 'Courier New',
        color: 'EEEEEE',
        fill: { color: '1E1E1E' },
        valign: 'top',
        margin: 0.08,
        breakLine: true,
      })
      return y + codeHeight + 0.35
    }

    case 'list': {
      const listHeight = estimateBlockHeight(block)
      slide.addText(
        block.items.map((item) => ({
          text: item,
          options: { bullet: block.ordered ? { type: 'number' } : true, breakLine: true },
        })),
        {
          x: PPT_CONTENT_X,
          y,
          w: PPT_CONTENT_W,
          h: listHeight,
          fontSize: 14,
          color: '444444',
          valign: 'top',
        }
      )
      return y + listHeight + 0.1
    }

    case 'image': {
      const { data, failed } = await fetchImageAsDataUrl(block.src)
      if (failed || !data) {
        mediaWarning.value = true
        slide.addText(`[图片: ${block.alt}]`, {
          x: PPT_CONTENT_X,
          y,
          w: PPT_CONTENT_W,
          h: 0.4,
          fontSize: 12,
          color: '888888',
          italic: true,
        })
        return y + 0.45
      }

      slide.addImage({
        data,
        x: PPT_CONTENT_X + 1,
        y,
        w: 7,
        h: 3,
        sizing: { type: 'contain', w: 7, h: 3 },
      })
      return y + 3.2
    }

    case 'video': {
      const { data, failed, tooLarge, extn } = await fetchVideoAsBase64(block.src, block.mimeType)
      if (failed || !data) {
        mediaWarning.value = true
        slide.addText(tooLarge ? '[视频: 文件过大，未嵌入]' : '[视频: 无法嵌入]', {
          x: PPT_CONTENT_X,
          y,
          w: PPT_CONTENT_W,
          h: 0.4,
          fontSize: 12,
          color: '888888',
          italic: true,
        })
        return y + 0.45
      }

      slide.addMedia({
        type: 'video',
        data,
        extn,
        x: PPT_CONTENT_X,
        y,
        w: PPT_CONTENT_W,
        h: 3,
      })
      return y + 3.5
    }

    case 'placeholder':
      slide.addText(block.text, {
        x: PPT_CONTENT_X,
        y,
        w: PPT_CONTENT_W,
        h: 0.35,
        fontSize: 12,
        color: '888888',
        italic: true,
      })
      return y + 0.4

    default:
      return y
  }
}

async function addSectionSlideToPptx(
  pptx: PptxGenJS,
  sectionSlide: PptSectionSlide,
  mediaWarning: { value: boolean }
): Promise<void> {
  const slide = pptx.addSlide()
  let y = addSectionHeader(slide, sectionSlide.title)

  const titleParts = sectionSlide.title.split(' · ')
  const h2Title = titleParts.length >= 2 ? titleParts[titleParts.length - 1] : sectionSlide.title
  let skipDuplicateH2 = true

  for (const block of sectionSlide.blocks) {
    if (skipDuplicateH2 && block.type === 'h2' && block.text === h2Title) {
      skipDuplicateH2 = false
      continue
    }
    skipDuplicateH2 = false
    y = await addBlockToPptxSlide(slide, block, y, mediaWarning)
  }
}

function canvasToPptSlides(pptx: PptxGenJS, canvas: HTMLCanvasElement): void {
  const sliceHeight = Math.floor(canvas.width * (9 / 16))
  if (sliceHeight <= 0) return

  for (let y = 0; y < canvas.height; y += sliceHeight) {
    const height = Math.min(sliceHeight, canvas.height - y)
    const sliceCanvas = document.createElement('canvas')
    sliceCanvas.width = canvas.width
    sliceCanvas.height = height
    const ctx = sliceCanvas.getContext('2d')
    if (!ctx) continue

    ctx.drawImage(canvas, 0, y, canvas.width, height, 0, 0, canvas.width, height)
    const data = sliceCanvas.toDataURL('image/png')
    const slide = pptx.addSlide()
    slide.addImage({ data, x: 0, y: 0, w: '100%', h: '100%' })
  }
}

async function domToCanvas(element: HTMLElement): Promise<{ canvas: HTMLCanvasElement; hasImageWarning: boolean }> {
  let hasImageWarning = false

  const canvas = await html2canvas(element, {
    useCORS: true,
    allowTaint: true,
    scale: 2,
    backgroundColor: null,
    logging: false,
    scrollY: -window.scrollY,
    scrollX: -window.scrollX,
    onclone: (_doc, clonedEl) => {
      clonedEl.querySelectorAll('img').forEach((img) => {
        if (img.src && !img.src.startsWith(window.location.origin) && !img.complete) {
          hasImageWarning = true
        }
      })
    },
  })

  return { canvas, hasImageWarning }
}

function canvasToPng(canvas: HTMLCanvasElement, filename: string): void {
  canvas.toBlob((blob) => {
    if (blob) downloadBlob(blob, filename)
  }, 'image/png')
}

function canvasToPdf(canvas: HTMLCanvasElement, filename: string): void {
  const pdf = new jsPDF('p', 'mm', 'a4')
  const pageWidth = pdf.internal.pageSize.getWidth()
  const pageHeight = pdf.internal.pageSize.getHeight()
  const imgData = canvas.toDataURL('image/png')
  const imgWidth = pageWidth
  const imgHeight = (canvas.height * imgWidth) / canvas.width

  let heightLeft = imgHeight
  let position = 0

  pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight)
  heightLeft -= pageHeight

  while (heightLeft > 0) {
    position -= pageHeight
    pdf.addPage()
    pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight)
    heightLeft -= pageHeight
  }

  pdf.save(filename)
}

export function exportAsMarkdown(post: Post): ExportResult {
  const content = post.content || ''
  const frontmatter = buildMarkdownFrontmatter(post)
  const basename = buildExportBasename(post.title, post.date)
  downloadText(frontmatter + content, `${basename}.md`, 'text/markdown;charset=utf-8')
  return { success: true }
}

export function exportAsTxt(post: Post): ExportResult {
  const header = buildTextHeader(post)
  const body = markdownToPlainText(post.content || '')
  const basename = buildExportBasename(post.title, post.date)
  downloadText(header + body, `${basename}.txt`, 'text/plain;charset=utf-8')
  return { success: true }
}

export async function exportAsPng(
  post: Post,
  renderedHtml: string,
  theme: ExportTheme
): Promise<ExportResult> {
  const container = buildExportContainer(post, renderedHtml, theme)
  try {
    const { canvas, hasImageWarning } = await domToCanvas(container)
    const basename = buildExportBasename(post.title, post.date)
    canvasToPng(canvas, `${basename}.png`)
    return {
      success: true,
      warning: hasImageWarning ? '部分图片可能因跨域限制未能导出' : undefined,
    }
  } finally {
    container.remove()
  }
}

export async function exportAsPdf(
  post: Post,
  renderedHtml: string,
  theme: ExportTheme
): Promise<ExportResult> {
  const container = buildExportContainer(post, renderedHtml, theme)
  try {
    const { canvas, hasImageWarning } = await domToCanvas(container)
    const basename = buildExportBasename(post.title, post.date)
    canvasToPdf(canvas, `${basename}.pdf`)
    return {
      success: true,
      warning: hasImageWarning ? '部分图片可能因跨域限制未能导出' : undefined,
    }
  } finally {
    container.remove()
  }
}

export async function exportAsPptStructured(post: Post): Promise<ExportResult> {
  const pptx = createPptx()
  const mediaWarning = { value: false }
  const slides = buildPptSlides(post)

  for (const slide of slides) {
    if (slide.kind === 'title') {
      addTitleSlideToPptx(pptx, slide)
    } else {
      await addSectionSlideToPptx(pptx, slide, mediaWarning)
    }
  }

  const basename = buildExportBasename(post.title, post.date)
  await pptx.writeFile({ fileName: `${basename}.pptx` })

  return {
    success: true,
    warning: mediaWarning.value ? '部分图片或视频可能因跨域/大小限制未能导出' : undefined,
  }
}

export async function exportAsPptScreenshot(
  post: Post,
  renderedHtml: string,
  theme: ExportTheme
): Promise<ExportResult> {
  const container = buildExportContainer(post, renderedHtml, theme)
  try {
    const { canvas, hasImageWarning } = await domToCanvas(container)
    const pptx = createPptx()
    canvasToPptSlides(pptx, canvas)
    const basename = buildExportBasename(post.title, post.date)
    await pptx.writeFile({ fileName: `${basename}-screenshot.pptx` })
    return {
      success: true,
      warning: hasImageWarning ? '部分图片可能因跨域限制未能导出' : undefined,
    }
  } finally {
    container.remove()
  }
}

export async function exportArticle(
  format: ExportFormat,
  post: Post,
  renderedHtml: string,
  theme: ExportTheme = 'light'
): Promise<ExportResult> {
  switch (format) {
    case 'md':
      return exportAsMarkdown(post)
    case 'txt':
      return exportAsTxt(post)
    case 'png':
      return exportAsPng(post, renderedHtml, theme)
    case 'pdf':
      return exportAsPdf(post, renderedHtml, theme)
    case 'ppt-structured':
      return exportAsPptStructured(post)
    case 'ppt-screenshot':
      return exportAsPptScreenshot(post, renderedHtml, theme)
    default:
      return { success: false, warning: '不支持的导出格式' }
  }
}

export { buildPptSlides } from './articlePptSlides'
