function normalizeText(value = '') {
  return String(value || '').replace(/\u00a0/g, ' ').replace(/\s+/g, ' ').trim()
}

function toArray(value) {
  return Array.isArray(value) ? value : []
}

function removeNodes(root, selectors) {
  for (const selector of selectors) {
    root.querySelectorAll(selector).forEach((node) => node.remove())
  }
}

function buildCardSection({ title, meta, body, className }) {
  const section = document.createElement('section')
  section.className = className

  const heading = document.createElement('h2')
  heading.textContent = title
  section.appendChild(heading)

  if (meta) {
    const metaNode = document.createElement('p')
    metaNode.className = `${className}__meta`
    metaNode.textContent = meta
    section.appendChild(metaNode)
  }

  if (body) {
    section.appendChild(body)
  }

  return section
}

/** @typedef {'doc-card' | 'web-search-card'} CardType */

function transformDocCards(root) {
  const cards = root.querySelectorAll('.doc-card')
  
  if (cards.length === 0) {
    console.debug('[Export] No doc cards found in export root') // debug level - safe for production
  }

  cards.forEach((card) => {
    // Validate required elements exist before transform
    const titleEl = card.querySelector('.doc-card__name')
    if (!titleEl) {
      console.warn('[Export] Doc card missing title element - skipping transform') // warn for visibility
    }

    const body = card.querySelector('.doc-card__body')
    if (!body) {
      console.error('[Export] Doc card has no body content - this will cause export failure') // error for critical path
    }

    const title = normalizeText(titleEl?.textContent) || '文档块'
    const badge = normalizeText(card.querySelector('.doc-card__badge')?.textContent)
    const time = normalizeText(card.querySelector('.doc-card__time')?.textContent)
    const meta = [badge, time].filter(Boolean).join(' · ')

    // Clone body content and strip interactive elements
    const content = body ? body.cloneNode(true) : document.createElement('div')

    // Remove interactive elements that don't belong in exports
    removeNodes(content, [
      'button',           // Delete/expand buttons
      'svg',              // Icons (trash, chevron)
      '.btn-tooltip',     // Tooltip wrappers around buttons
      '.doc-card__actions',  // Action bar container (holds delete button)
      '.doc-card__status-label' // Status label ("处理中"/"已完成") - keep for context
    ])

    card.replaceWith(buildCardSection({
      title,
      meta,
      body: content,
      className: 'export-doc-block',
    }))

    // Log transform result for debugging (structured logging)
    console.debug('[Export] Transformed doc card:', { title, hasBody: Boolean(body), meta })
  })

  return cards.length // Return count for verification downstream
}

function transformWebSearchCards(root) {
  const cards = root.querySelectorAll('.web-search-card')

  if (cards.length === 0) {
    console.debug('[Export] No web search cards found in export root') // debug level - safe for production
  }

  cards.forEach((card) => {
    // Validate required elements exist before transform
    const titleEl = card.querySelector('.web-search-card__title')
    if (!titleEl) {
      console.warn('[Export] Web search card missing title element') // warn for visibility
    }

    const body = card.querySelector('.web-search-card__body')
    if (!body) {
      console.error('[Export] Web search card has no body content') // error for critical path
    }

    const title = normalizeText(titleEl?.textContent) || '联网搜索结果'
    const subtitle = normalizeText(card.querySelector('.web-search-card__subtitle')?.textContent)

    // Clone and strip interactive elements
    const content = body ? body.cloneNode(true) : document.createElement('div')

    // Remove interactive elements
    removeNodes(content, [
      'button',              // Delete/collapse buttons
      'svg',                 // Icons (spinner, chevron)
      '.btn-tooltip',        // Tooltip wrappers
      '.web-search-card__actions',   // Action bar (delete button)
      '.web-search-card__progress'   // Progress indicator - keep for status context
    ])

    card.replaceWith(buildCardSection({
      title,
      meta: subtitle,
      body: content,
      className: 'export-websearch-block',
    }))

    // Log transform result for debugging (structured logging)
    console.debug('[Export] Transformed web search card:', { title, hasBody: Boolean(body), meta })
  })

  return cards.length // Return count for verification downstream
}

function inlineTextRuns(node, docx) {
  const { TextRun, ExternalHyperlink } = docx
  const runs = []

  const walk = (current, marks = {}) => {
    if (current.nodeType === Node.TEXT_NODE) {
      const text = current.textContent || ''
      if (!text.trim() && !text.includes('\n')) return
      runs.push(new TextRun({
        text: text.replace(/\s+/g, ' '),
        bold: Boolean(marks.bold),
        italics: Boolean(marks.italics),
        underline: marks.underline ? {} : undefined,
        font: marks.code ? 'Menlo' : undefined,
      }))
      return
    }

    if (!(current instanceof HTMLElement)) return

    if (current.tagName === 'BR') {
      runs.push(new TextRun({ break: 1 }))
      return
    }

    if (current.tagName === 'A') {
      const href = current.getAttribute('href') || ''
      const childRuns = []
      const startCount = runs.length
      toArray(Array.from(current.childNodes)).forEach((child) => walk(child, { ...marks, underline: true }))
      childRuns.push(...runs.splice(startCount))
      if (href && childRuns.length > 0) {
        runs.push(new ExternalHyperlink({ link: href, children: childRuns }))
      } else {
        runs.push(...childRuns)
      }
      return
    }

    const nextMarks = {
      ...marks,
      bold: marks.bold || current.tagName === 'STRONG' || current.tagName === 'B',
      italics: marks.italics || current.tagName === 'EM' || current.tagName === 'I',
      underline: marks.underline || current.tagName === 'U',
      code: marks.code || current.tagName === 'CODE',
    }

    toArray(Array.from(current.childNodes)).forEach((child) => walk(child, nextMarks))
  }

  walk(node)

  if (runs.length === 0) {
    const text = normalizeText(node.textContent)
    if (text) runs.push(new TextRun(text))
  }

  return runs
}

async function domToDocxSections(root) {
  const docx = await import('docx')
  const {
    Paragraph,
    HeadingLevel,
    BorderStyle,
  } = docx

  const paragraphs = []

  const pushParagraph = (options) => {
    paragraphs.push(new Paragraph(options))
  }

  const walkBlock = (node, depth = 0) => {
    if (!(node instanceof HTMLElement)) return

    const tag = node.tagName
    if (['SCRIPT', 'STYLE'].includes(tag)) return

    if (tag === 'H1' || tag === 'H2' || tag === 'H3' || tag === 'H4') {
      const levelMap = {
        H1: HeadingLevel.HEADING_1,
        H2: HeadingLevel.HEADING_2,
        H3: HeadingLevel.HEADING_3,
        H4: HeadingLevel.HEADING_4,
      }
      pushParagraph({
        heading: levelMap[tag],
        children: inlineTextRuns(node, docx),
        spacing: { before: 240, after: 120 },
      })
      return
    }

    if (tag === 'UL' || tag === 'OL') {
      toArray(Array.from(node.children)).forEach((child) => walkBlock(child, depth + 1))
      return
    }

    if (tag === 'LI') {
      pushParagraph({
        children: inlineTextRuns(node, docx),
        bullet: { level: Math.max(0, depth - 1) },
        spacing: { after: 80 },
      })
      return
    }

    if (tag === 'PRE') {
      const code = node.textContent || ''
      pushParagraph({
        children: [new docx.TextRun({ text: code, font: 'Menlo' })],
        spacing: { before: 120, after: 120 },
        border: {
          top: { style: BorderStyle.SINGLE, size: 1, color: 'D1D5DB' },
          bottom: { style: BorderStyle.SINGLE, size: 1, color: 'D1D5DB' },
          left: { style: BorderStyle.SINGLE, size: 1, color: 'D1D5DB' },
          right: { style: BorderStyle.SINGLE, size: 1, color: 'D1D5DB' },
        },
        shading: { fill: 'F8FAFC' },
      })
      return
    }

    if (tag === 'BLOCKQUOTE') {
      pushParagraph({
        children: inlineTextRuns(node, docx),
        indent: { left: 480 },
        spacing: { before: 120, after: 120 },
      })
      return
    }

    if (tag === 'TABLE') {
      toArray(Array.from(node.querySelectorAll('tr'))).forEach((row) => {
        const cells = toArray(Array.from(row.children)).map((cell) => normalizeText(cell.textContent)).filter(Boolean)
        if (cells.length > 0) {
          pushParagraph({
            children: [new docx.TextRun(cells.join(' | '))],
            spacing: { after: 80 },
          })
        }
      })
      return
    }

    if (tag === 'IMG') {
      const alt = normalizeText(node.getAttribute('alt') || '')
      pushParagraph({
        children: [new docx.TextRun(alt ? `[图片] ${alt}` : '[图片]')],
        spacing: { before: 80, after: 80 },
      })
      return
    }

    const blockLike = ['P', 'DIV', 'SECTION', 'ARTICLE']
    const hasDirectBlockChildren = toArray(Array.from(node.children)).some((child) =>
      ['P', 'DIV', 'SECTION', 'ARTICLE', 'UL', 'OL', 'LI', 'PRE', 'BLOCKQUOTE', 'TABLE', 'H1', 'H2', 'H3', 'H4'].includes(child.tagName)
    )

    if (blockLike.includes(tag) && !hasDirectBlockChildren) {
      const text = normalizeText(node.textContent)
      if (text) {
        pushParagraph({
          children: inlineTextRuns(node, docx),
          spacing: { after: 120 },
        })
      }
      return
    }

    toArray(Array.from(node.children)).forEach((child) => walkBlock(child, depth))
  }

  toArray(Array.from(root.children)).forEach((child) => walkBlock(child))

  if (paragraphs.length === 0) {
    pushParagraph({ text: normalizeText(root.textContent) || '' })
  }

  return { docx, paragraphs }
}

export function createExpandedExportRoot(editorElement) {
  if (!(editorElement instanceof HTMLElement)) {
    throw new Error('导出失败：编辑器 DOM 不可用')
  }

  const host = document.createElement('div')
  host.className = 'rich-export-root'
  host.innerHTML = `
    <style>
      .rich-export-root {
        width: 800px;
        padding: 40px 48px;
        background: #ffffff;
        color: #111827;
        font: 16px/1.65 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      }
      .rich-export-root * {
        box-sizing: border-box;
        color: inherit !important;
      }
      .rich-export-root img {
        max-width: 100%;
        height: auto;
      }
      .rich-export-root .export-doc-block,
      .rich-export-root .export-websearch-block {
        margin: 24px 0;
        padding: 16px 18px;
        border: 1px solid #dbe4f0;
        border-radius: 10px;
        background: #f8fafc;
      }
      .rich-export-root .export-doc-block__meta,
      .rich-export-root .export-websearch-block__meta {
        margin: 6px 0 14px;
        color: #475569 !important;
        font-size: 13px;
      }
      .rich-export-root h1,
      .rich-export-root h2,
      .rich-export-root h3,
      .rich-export-root h4 {
        margin: 20px 0 12px;
      }
      .rich-export-root pre {
        white-space: pre-wrap;
        word-break: break-word;
        padding: 12px 14px;
        border-radius: 8px;
        background: #f1f5f9;
        overflow: hidden;
      }
      .rich-export-root table {
        width: 100%;
        border-collapse: collapse;
      }
      .rich-export-root th,
      .rich-export-root td {
        border: 1px solid #d1d5db;
        padding: 8px 10px;
        vertical-align: top;
      }
      .rich-export-root .btn-tooltip,
      .rich-export-root button,
      .rich-export-root svg {
        display: none !important;
      }
    </style>
  `

  const cloned = editorElement.cloneNode(true)
  if (!(cloned instanceof HTMLElement)) {
    throw new Error('导出失败：编辑器内容不可用')
  }

  removeNodes(cloned, [
    '.history-buttons',
    '.action-buttons',
    '.top-actions-fixed',
    '.ProseMirror-gapcursor',
    '.ProseMirror-widget',
    '.doc-card__actions',
    '.doc-card__status-label',
    '.web-search-card__actions',
    '.web-search-card__progress',
    '.btn-tooltip',
  ])

  cloned.querySelectorAll('.doc-card__body, .web-search-card__body').forEach((node) => {
    if (node instanceof HTMLElement) {
      node.style.display = 'block'
    }
  })

  transformDocCards(cloned)
  transformWebSearchCards(cloned)

  host.appendChild(cloned)
  return host
}

export async function exportEditorToPdf(editorElement, filename) {
  const html2pdfModule = await import('html2pdf.js')
  const html2pdf = html2pdfModule.default || html2pdfModule
  const root = createExpandedExportRoot(editorElement)
  root.style.position = 'fixed'
  root.style.left = '-100000px'
  root.style.top = '0'
  document.body.appendChild(root)

  try {
    await html2pdf()
      .set({
        margin: [10, 10, 10, 10],
        filename,
        html2canvas: { scale: 2, useCORS: true, backgroundColor: '#ffffff' },
        jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
        pagebreak: { mode: ['css', 'legacy'] },
      })
      .from(root)
      .save()
  } finally {
    root.remove()
  }
}

export async function exportEditorToDocxBlob(editorElement) {
  const root = createExpandedExportRoot(editorElement)
  const { docx, paragraphs } = await domToDocxSections(root)
  const { Document, Packer } = docx
  const doc = new Document({
    sections: [{ properties: {}, children: paragraphs }],
  })
  return Packer.toBlob(doc)
}
