/**
 * Branded AGB PDF — renders the stored legal document HTML (RichTextEditor
 * output: h2/h3/p/ul/ol/li/strong/em/u/br) with the LogYou quote/contract
 * brand frame (header/footer/logos from quotePdf.ts).
 *
 * The converter is intentionally tiny: it only understands the tags the
 * in-app editor produces. Unknown tags are unwrapped, their text kept.
 */
import { BRAND, buildBrandHeader, buildBrandFooter, getQuoteLogos } from './quotePdf'
import { getLegalDocumentWithFallback } from '../legalDocsDb'

const decodeEntities = (s: string): string => String(s)
  .replace(/&nbsp;/g, ' ')
  .replace(/&amp;/g, '&')
  .replace(/&lt;/g, '<')
  .replace(/&gt;/g, '>')
  .replace(/&quot;/g, '"')
  .replace(/&#39;|&apos;/g, "'")
  .replace(/&#(\d+);/g, (_, n) => String.fromCharCode(Number(n)))

/** Inline HTML → pdfmake text runs (bold/italics/underline). */
const inlineRuns = (html: string): any[] => {
  const runs: any[] = []
  const stack: string[] = []
  const re = /<\/?([a-z0-9]+)[^>]*>|[^<]+/gi
  let m: RegExpExecArray | null
  while ((m = re.exec(html))) {
    const token = m[0]
    if (token.startsWith('<')) {
      const tag = m[1].toLowerCase()
      const closing = token.startsWith('</')
      if (tag === 'br') { runs.push({ text: '\n' }); continue }
      if (['strong', 'b', 'em', 'i', 'u', 's'].includes(tag)) {
        if (closing) { const i = stack.lastIndexOf(tag); if (i >= 0) stack.splice(i, 1) } else stack.push(tag)
      }
      continue
    }
    const text = decodeEntities(token.replace(/\s+/g, ' '))
    if (!text) continue
    const run: any = { text }
    if (stack.includes('strong') || stack.includes('b')) run.bold = true
    if (stack.includes('em') || stack.includes('i')) run.italics = true
    if (stack.includes('u')) run.decoration = 'underline'
    if (stack.includes('s')) run.decoration = 'lineThrough'
    runs.push(run)
  }
  // trim leading/trailing whitespace of the paragraph
  if (runs.length) {
    runs[0].text = String(runs[0].text).replace(/^\s+/, '')
    runs[runs.length - 1].text = String(runs[runs.length - 1].text).replace(/\s+$/, '')
  }
  return runs.filter((r) => r.text !== '')
}

/** Block-level HTML → pdfmake content array. */
export const htmlToPdfmakeBlocks = (html: string, opts: { tocItems?: boolean } = {}): any[] => {
  const out: any[] = []
  const src = String(html || '')
    .replace(/<!--[\s\S]*?-->/g, '')
    .replace(/<script[\s\S]*?<\/script>/gi, '')
    .replace(/<style[\s\S]*?<\/style>/gi, '')
  const blockRe = /<(h1|h2|h3|h4|p|ul|ol|blockquote)\b[^>]*>([\s\S]*?)<\/\1>|<(hr)\b[^>]*\/?>|([^<]+)/gi
  let m: RegExpExecArray | null
  while ((m = blockRe.exec(src))) {
    const tag = (m[1] || m[3] || '').toLowerCase()
    const inner = m[2] || ''
    if (!tag) {
      const runs = inlineRuns(m[4] || '')
      if (runs.length) out.push({ text: runs, margin: [0, 0, 0, 6], alignment: 'justify' })
      continue
    }
    if (tag === 'hr') { out.push({ canvas: [{ type: 'line', x1: 0, y1: 0, x2: 515, y2: 0, lineWidth: 0.5, lineColor: BRAND.grayLine }], margin: [0, 6, 0, 10] }); continue }
    if (tag === 'h1' || tag === 'h2') {
      const runs = inlineRuns(inner)
      out.push({
        stack: [
          { canvas: [{ type: 'rect', x: 0, y: 0, w: 26, h: 3, color: BRAND.orange }] },
          { text: runs, fontSize: 11.5, bold: true, color: BRAND.navy, characterSpacing: 0.3, margin: [0, 5, 0, 0], ...(opts.tocItems ? { tocItem: true, tocStyle: { fontSize: 9, color: BRAND.text } } : {}) }
        ],
        margin: [0, 14, 0, 7],
        unbreakable: true
      })
      continue
    }
    if (tag === 'h3' || tag === 'h4') {
      out.push({ text: inlineRuns(inner), fontSize: 10, bold: true, color: BRAND.navy, margin: [0, 8, 0, 4] })
      continue
    }
    if (tag === 'ul' || tag === 'ol') {
      const items: any[] = []
      const liRe = /<li\b[^>]*>([\s\S]*?)<\/li>/gi
      let li: RegExpExecArray | null
      while ((li = liRe.exec(inner))) {
        const runs = inlineRuns(li[1].replace(/<\/?(p|div)\b[^>]*>/gi, ''))
        if (runs.length) items.push({ text: runs, margin: [0, 0, 0, 3] })
      }
      if (items.length) out.push(tag === 'ul' ? { ul: items, markerColor: BRAND.orange, margin: [0, 0, 0, 7] } : { ol: items, margin: [0, 0, 0, 7] })
      continue
    }
    // p / blockquote
    const runs = inlineRuns(inner)
    if (runs.length) out.push({ text: runs, margin: [0, 0, 0, 6], alignment: 'justify', ...(tag === 'blockquote' ? { italics: true, color: BRAND.grayText } : {}) })
  }
  return out
}

/** Strip to plain text (used for e-mail text fallbacks / search). */
export const htmlToPlainText = (html: string): string => decodeEntities(
  String(html || '')
    .replace(/<\/(p|div|h[1-6]|li|tr)>/gi, '\n')
    .replace(/<br\s*\/?>/gi, '\n')
    .replace(/<[^>]+>/g, '')
).replace(/\n{3,}/g, '\n\n').trim()

export const legalDocFilename = (docKey: string, language: string, date?: string): string => {
  const d = date || new Date().toISOString().slice(0, 10)
  if (docKey === 'avv') return language === 'en' ? `Data-Processing-Agreement_LogYou_${d}.pdf` : `AVV_LogYou_${d}.pdf`
  return language === 'en' ? `Terms-and-Conditions_LogYou_${d}.pdf` : `AGB_LogYou_${d}.pdf`
}
export const agbFilename = (language: string, date?: string): string => legalDocFilename('agb', language, date)

const LEGAL_HEADERS: Record<string, { de: string; en: string }> = {
  agb: { de: 'Allgemeine Geschäftsbedingungen', en: 'Terms and Conditions' },
  avv: { de: 'Auftragsverarbeitungsvertrag (Art. 28 DSGVO)', en: 'Data Processing Agreement (Art. 28 GDPR)' }
}

export const buildAgbDocDefinition = (doc: { docKey?: string; title: string; contentHtml: string; language: string; updatedAt?: string | null; version?: number }, logos: { light: string | null; dark: string | null }) => {
  const hdr = LEGAL_HEADERS[doc.docKey || 'agb'] || LEGAL_HEADERS.agb
  const language = doc.language === 'en' ? 'en' : 'de'
  const dateWord = language === 'en' ? 'Version' : 'Stand'
  const stand = doc.updatedAt ? new Date(doc.updatedAt) : new Date()
  const dd = String(stand.getDate()).padStart(2, '0')
  const mm = String(stand.getMonth() + 1).padStart(2, '0')
  const standLabel = language === 'en' ? `${stand.getFullYear()}-${mm}-${dd}` : `${dd}.${mm}.${stand.getFullYear()}`
  const content: any[] = [
    { text: doc.title, fontSize: 18, bold: true, color: BRAND.navy, margin: [0, 6, 0, 4] },
    { text: `${dateWord}: ${standLabel}${doc.version ? ` · v${doc.version}` : ''}`, fontSize: 9, color: BRAND.grayText, margin: [0, 0, 0, 14] },
    ...htmlToPdfmakeBlocks(doc.contentHtml)
  ]
  return {
    pageSize: 'A4',
    pageMargins: [40, 106, 40, 86],
    info: { title: doc.title, author: 'LogYou GmbH', subject: doc.title },
    header: buildBrandHeader(hdr[language], `logyou.de  ·  ${dateWord} ${standLabel}`, logos.dark),
    footer: buildBrandFooter(language === 'en' ? 'Page' : 'Seite', language === 'en' ? 'of' : 'von'),
    content,
    defaultStyle: { font: 'Roboto', fontSize: 9.5, color: BRAND.text, lineHeight: 1.3 }
  }
}

const loadPdfMake = async () => {
  const pdfMake = await import('pdfmake/build/pdfmake.js')
  const pdfFonts = await import('pdfmake/build/vfs_fonts.js')
  const pdfMakeInstance: any = (pdfMake as any).default || pdfMake
  let vfs: any = null
  if ((pdfFonts as any).pdfMake?.vfs) vfs = (pdfFonts as any).pdfMake.vfs
  else if ((pdfFonts as any).default?.pdfMake?.vfs) vfs = (pdfFonts as any).default.pdfMake.vfs
  else if ((pdfFonts as any).default?.vfs) vfs = (pdfFonts as any).default.vfs
  else if ((pdfFonts as any).vfs) vfs = (pdfFonts as any).vfs
  else if ((pdfFonts as any).default && Object.keys((pdfFonts as any).default).some((k: string) => k.endsWith('.ttf'))) vfs = (pdfFonts as any).default
  if (!vfs) throw new Error('Could not find vfs fonts in pdfmake fonts module')
  pdfMakeInstance.vfs = vfs
  return pdfMakeInstance
}

/** Render any pdfmake doc definition to a Buffer (shared by the AGB PDF). */
export const renderPdfmake = async (docDefinition: any): Promise<Buffer> => {
  const pdfMakeInstance = await loadPdfMake()
  return await new Promise<Buffer>((resolve, reject) => {
    try {
      pdfMakeInstance.createPdf(docDefinition).getBuffer((buf: Buffer) => resolve(buf))
    } catch (error) {
      reject(error)
    }
  })
}

/** A stored legal document (agb / avv) as a branded PDF (German fallback). */
export const generateLegalDocPdf = async (docKey: string, language: string): Promise<{ buffer: Buffer; filename: string; language: string; version: number; title: string }> => {
  const doc = getLegalDocumentWithFallback(docKey, language)
  if (!String(doc.contentHtml || '').trim()) throw new Error(`No ${docKey.toUpperCase()} text available`)
  const logos = await getQuoteLogos()
  const buffer = await renderPdfmake(buildAgbDocDefinition(doc, logos))
  return { buffer, filename: legalDocFilename(docKey, doc.language, doc.updatedAt ? doc.updatedAt.slice(0, 10) : undefined), language: doc.language, version: doc.version, title: doc.title }
}

/** The AGB as a branded PDF for the requested language (German fallback). */
export const generateAgbPdf = (language: string) => generateLegalDocPdf('agb', language)
