import { jsPDF } from 'jspdf'
import { lexer } from 'marked'

/** Fortlaufende Abschnittsnummer nur für die Ausgabe (nicht persistiert). */
export function legalSectionNumber(indexZeroBased) {
  return `§${indexZeroBased + 1}`
}

const PDF_FONT = {
  normal: ['helvetica', 'normal'],
  bold: ['helvetica', 'bold'],
  italic: ['helvetica', 'italic'],
  bolditalic: ['helvetica', 'bolditalic'],
}

function setPdfFont(pdf, styleKey) {
  const [name, variant] = PDF_FONT[styleKey] || PDF_FONT.normal
  try {
    pdf.setFont(name, variant)
  } catch {
    pdf.setFont('helvetica', styleKey === 'bolditalic' || styleKey === 'bold' ? 'bold' : 'normal')
  }
}

/** Inline-Tokens → zusammengefügte Läufe mit Schriftstil */
function collectInlineRuns(tokens, base = { bold: false, italic: false }) {
  const runs = []

  const styleKey = (b) => {
    if (b.bold && b.italic) return 'bolditalic'
    if (b.bold) return 'bold'
    if (b.italic) return 'italic'
    return 'normal'
  }

  const flush = (text, b) => {
    if (!text) return
    runs.push({ style: styleKey(b), text })
  }

  const walk = (ts, b) => {
    for (const t of ts || []) {
      switch (t.type) {
        case 'text':
        case 'escape':
          flush(t.text, b)
          break
        case 'strong':
          walk(t.tokens || [{ type: 'text', text: t.text }], { ...b, bold: true })
          break
        case 'em':
          walk(t.tokens || [{ type: 'text', text: t.text }], { ...b, italic: true })
          break
        case 'codespan':
          flush(`„${t.text}“`, b)
          break
        case 'link': {
          walk(t.tokens || [{ type: 'text', text: t.text }], b)
          const href = t.href || ''
          if (href) flush(` (${href})`, b)
          break
        }
        case 'br':
          flush('\n', b)
          break
        case 'del':
          walk(t.tokens || [{ type: 'text', text: t.text }], b)
          break
        case 'image':
          flush(`[${t.text || 'Bild'}]`, b)
          break
        default:
          if (t.tokens) walk(t.tokens, b)
          else if (t.text) flush(t.text, b)
      }
    }
  }

  walk(tokens, base)

  const merged = []
  for (const r of runs) {
    const last = merged[merged.length - 1]
    if (last && last.style === r.style) last.text += r.text
    else merged.push({ ...r })
  }
  return merged
}

function splitLongWord(pdf, word, maxW) {
  if (pdf.getTextWidth(word) <= maxW) return [word]
  const out = []
  let buf = ''
  for (const ch of word) {
    const tryBuf = buf + ch
    if (pdf.getTextWidth(tryBuf) > maxW && buf) {
      out.push(buf)
      buf = ch
    } else {
      buf = tryBuf
    }
  }
  if (buf) out.push(buf)
  return out
}

/** Eine Zeile aus mehreren Läufen mit unterschiedlichen Schriften zeichnen */
function drawLine(pdf, pieces, x, y) {
  let cx = x
  for (const p of pieces) {
    setPdfFont(pdf, p.style)
    pdf.text(p.text, cx, y)
    cx += pdf.getTextWidth(p.text)
  }
}

function collectWordsFromRuns(pdf, runs, maxChunkW) {
  const words = []
  for (const run of runs) {
    setPdfFont(pdf, run.style)
    const parts = run.text.split(/(\s+)/)
    for (const part of parts) {
      if (!part) continue
      if (part === '\n') {
        words.push({ forceBreak: true })
        continue
      }
      for (const c of splitLongWord(pdf, part, maxChunkW)) {
        words.push({ ...run, text: c })
      }
    }
  }
  return words
}

/** Absatz aus Inline-Läufen umbrechen und zeichnen */
function runParagraphRuns(pdf, xStart, maxWidth, y, runs, lineStep, checkBreak, prefixRuns = []) {
  const words = collectWordsFromRuns(pdf, [...prefixRuns, ...runs], maxWidth)

  let linePieces = []
  let lineW = 0

  const flushLine = () => {
    if (!linePieces.length) return
    checkBreak(lineStep)
    drawLine(pdf, linePieces, xStart, y)
    y += lineStep
    linePieces = []
    lineW = 0
  }

  for (const w of words) {
    if (w.forceBreak) {
      flushLine()
      continue
    }
    setPdfFont(pdf, w.style)
    const tw = pdf.getTextWidth(w.text)
    if (lineW + tw > maxWidth && linePieces.length) flushLine()
    linePieces.push({ style: w.style, text: w.text })
    lineW += tw
  }
  flushLine()
  return y
}

/** Listenpunkt: Aufzählungszeichen in der ersten Zeile, hängender Einzug */
function renderListItemRuns(pdf, bullet, runs, x, y, maxW, lineStep, checkBreak) {
  pdf.setFont('helvetica', 'normal')
  const bulletW = pdf.getTextWidth(bullet)
  const textX = x + bulletW
  const textMaxW = Math.max(12, maxW - bulletW)

  const words = collectWordsFromRuns(pdf, runs, textMaxW)
  let linePieces = []
  let lineW = 0
  let firstLine = true

  const flushLine = () => {
    if (!linePieces.length) return
    checkBreak(lineStep)
    if (firstLine) {
      pdf.setFont('helvetica', 'normal')
      pdf.text(bullet, x, y)
      firstLine = false
    }
    drawLine(pdf, linePieces, textX, y)
    y += lineStep
    linePieces = []
    lineW = 0
  }

  for (const w of words) {
    if (w.forceBreak) {
      flushLine()
      continue
    }
    setPdfFont(pdf, w.style)
    const tw = pdf.getTextWidth(w.text)
    if (lineW + tw > textMaxW && linePieces.length) flushLine()
    linePieces.push({ style: w.style, text: w.text })
    lineW += tw
  }
  flushLine()
  return y
}

/**
 * Fließtext mit **fett** / *kursiv* / Listen usw. ins PDF — Zeilenumbruch per Wortgrenze.
 * Gibt neue y-Position (mm) zurück.
 */
function renderMarkdownBlockToPdf(pdf, markdown, x, y, maxW, lineStep, checkBreak, gapAfterBlock = 2) {
  if (!markdown || !String(markdown).trim()) return y + gapAfterBlock

  const tokens = lexer(String(markdown), { gfm: true, breaks: true })

  for (const token of tokens) {
    switch (token.type) {
      case 'space':
        break
      case 'paragraph': {
        const runs = collectInlineRuns(token.tokens || [])
        y = runParagraphRuns(pdf, x, maxW, y, runs, lineStep, checkBreak)
        y += gapAfterBlock
        break
      }
      case 'heading': {
        checkBreak(8)
        pdf.setFont('helvetica', 'bold')
        pdf.setFontSize(10.5)
        const hruns = collectInlineRuns(token.tokens || [])
        const htext = hruns.map(r => r.text).join('')
        const lines = pdf.splitTextToSize(htext, maxW)
        for (const line of lines) {
          checkBreak(6)
          pdf.text(line, x, y)
          y += 5.5
        }
        pdf.setFontSize(10)
        pdf.setFont('helvetica', 'normal')
        y += gapAfterBlock
        break
      }
      case 'blockquote': {
        const bx = x + 4
        const bw = Math.max(20, maxW - 4)
        let quoteStartY = null
        for (const inner of token.tokens || []) {
          if (inner.type === 'paragraph') {
            if (quoteStartY == null) quoteStartY = y
            const runs = collectInlineRuns(inner.tokens || [])
            y = runParagraphRuns(pdf, bx, bw, y, runs, lineStep, checkBreak)
          }
        }
        if (quoteStartY != null) {
          pdf.setDrawColor(180, 180, 180)
          pdf.setLineWidth(0.3)
          pdf.line(x, quoteStartY - 3.5, x, y - 0.5)
          pdf.setDrawColor(0, 0, 0)
        }
        y += gapAfterBlock
        break
      }
      case 'code': {
        checkBreak(lineStep)
        pdf.setFont('courier', 'normal')
        pdf.setFontSize(9)
        const lines = pdf.splitTextToSize(token.text, maxW)
        for (const line of lines) {
          checkBreak(4.5)
          pdf.text(line, x, y)
          y += 4.5
        }
        pdf.setFontSize(10)
        pdf.setFont('helvetica', 'normal')
        y += gapAfterBlock
        break
      }
      case 'list': {
        let n = typeof token.start === 'number' ? token.start : 1
        for (const item of token.items || []) {
          const bullet = token.ordered ? `${n}. ` : '• '
          n += 1
          for (const it of item.tokens || []) {
            if (it.type === 'paragraph') {
              const runs = collectInlineRuns(it.tokens || [])
              y = renderListItemRuns(pdf, bullet, runs, x, y, maxW, lineStep, checkBreak)
            }
          }
        }
        y += gapAfterBlock
        break
      }
      case 'hr':
        checkBreak(4)
        pdf.setDrawColor(200, 200, 200)
        pdf.line(x, y, x + maxW, y)
        y += 5
        pdf.setDrawColor(0, 0, 0)
        break
      default:
        break
    }
  }

  return y
}

/**
 * Rechtstext als PDF (Abschnitte mit §-Nummerierung, Markdown im Fließtext).
 * @param {object} doc – API-Dokument mit title, content_sections, version, …
 * @param {string} metaLine – z. B. Version + Datum
 */
export function generateLegalPdf(doc, metaLine) {
  const pdf = new jsPDF({ format: 'a4', unit: 'mm' })
  const marginL = 22
  const marginR = 22
  const marginTop = 28
  const pageW = 210
  const contentW = pageW - marginL - marginR
  const bottomLimit = 277
  let y = marginTop

  const checkBreak = (need) => {
    if (y + need > bottomLimit) {
      pdf.addPage()
      y = marginTop
    }
  }

  pdf.setFont('helvetica', 'bold')
  pdf.setFontSize(20)
  pdf.text(doc.title, marginL, y)
  y += 10

  pdf.setFont('helvetica', 'normal')
  pdf.setFontSize(10)
  pdf.setTextColor(90, 90, 90)
  pdf.text(metaLine, marginL, y)
  y += 3
  pdf.setDrawColor(0, 0, 0)
  pdf.setLineWidth(0.4)
  pdf.line(marginL, y, pageW - marginR, y)
  y += 8
  pdf.setTextColor(0, 0, 0)

  const sections = doc.content_sections || []
  sections.forEach((section, si) => {
    const secNum = legalSectionNumber(si)
    const head = [section.heading || '']
      .filter(Boolean)
      .length
      ? `${secNum} ${section.heading || ''}`
      : secNum

    checkBreak(14)
    pdf.setFont('helvetica', 'bold')
    pdf.setFontSize(11)
    const headLines = pdf.splitTextToSize(head, contentW)
    for (const hl of headLines) {
      checkBreak(6)
      pdf.text(hl, marginL, y)
      y += 6
    }

    if (section.content) {
      pdf.setFont('helvetica', 'normal')
      pdf.setFontSize(10)
      y = renderMarkdownBlockToPdf(pdf, section.content, marginL, y, contentW, 5, checkBreak, 4)
    }
    y += 3
  })

  const total = pdf.getNumberOfPages()
  for (let i = 1; i <= total; i++) {
    pdf.setPage(i)
    pdf.setFont('helvetica', 'normal')
    pdf.setFontSize(8)
    pdf.setTextColor(150, 150, 150)
    const fy = 289
    pdf.text(
      `Shinkan Jinkendo  |  Exportiert am ${new Date().toLocaleDateString('de-DE')}`,
      marginL,
      fy,
    )
    pdf.text(`Seite ${i} von ${total}`, pageW - marginR, fy, { align: 'right' })
    pdf.setTextColor(0, 0, 0)
  }

  pdf.save(`${doc.document_type}_v${doc.version}.pdf`)
}
