/**
 * Branded "Angebot LogShip Cloud" PDF — the SaaS offer type next to the 3PL
 * fulfillment quote (quotePdf.ts). Same brand header/footer, A4, pdfmake.
 *
 * Payload = buildSoftwareQuotePayload() from app/composables/useOfferSoftwareQuote.ts
 * (offerType 'software'). Sections (each switchable via payload.sections):
 * overview (package card) → pricing (base fee, overage staffel, extra users,
 * one-time) → features (4-column comparison with the chosen package highlighted,
 * or a plain list) → onboarding (price, highlights, phases) → addons →
 * positions / requirements → forecast (monthly, one-time, first year) → notes.
 */
import { BRAND, buildBrandHeader, buildBrandFooter, getQuoteLogos, resolveQuoteValidity } from './quotePdf'

const CHECK_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="11" fill="#1f7a3d"/><path d="M7 12.5l3 3 7-7" stroke="#ffffff" stroke-width="2.4" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>'
const CROSS_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="11" fill="#ff3860"/><path d="M8 8l8 8M16 8l-8 8" stroke="#ffffff" stroke-width="2.4" fill="none" stroke-linecap="round"/></svg>'
const DASH_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="11" fill="#D5DBE1"/><path d="M7 12h10" stroke="#ffffff" stroke-width="2.6" fill="none" stroke-linecap="round"/></svg>'
const numberBadge = (n: number) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28"><circle cx="14" cy="14" r="13" fill="${BRAND.navy}"/><text x="14" y="19" font-family="Helvetica, Arial, sans-serif" font-size="14" font-weight="bold" fill="#ffffff" text-anchor="middle">${n}</text></svg>`

// Catalog (packages + feature matrix) comes with the payload as a snapshot of
// what the modal used (`payload.catalog`, see useOfferSoftwareQuote.ts); older
// payloads without one render from the code defaults.
import {
  DEFAULT_SOFTWARE_CATALOG, normalizeSoftwareCatalog, activePlans, featureDefaultState, catalogLabel,
  type SoftwareCatalog
} from '../../../app/utils/softwareCatalogDefaults'

export const SOFTWARE_QUOTE_TEXTS: any = {
  de: {
    docTitle: 'Angebot LogShip Cloud',
    dateWord: 'Angebot vom',
    date: 'Datum',
    validUntil: 'Gültig bis',
    plannedStart: 'Geplanter Start',
    billing: 'Abrechnung',
    billingYearly: 'jährlich (Vorauszahlung)',
    billingMonthly: 'monatlich',
    billingYearlyShort: 'jährliche Zahlung',
    billingMonthlyShort: 'monatliche Zahlung',
    channelsIncl: '{n} Verkaufskanäle',
    channelsUnlimitedIncl: 'unbegrenzte Verkaufskanäle',
    minTerm: 'Mindestlaufzeit',
    months: '{n} Monate',
    noMinTerm: 'keine (monatlich kündbar)',
    intro: 'Vielen Dank für Ihr Interesse an LogShip Cloud — der Lagerverwaltungs- und Versandsoftware, mit der LogYou täglich das eigene Fulfillment betreibt. Gerne unterbreiten wir Ihnen folgendes Angebot.',
    overviewTitle: 'Ihr Paket',
    popular: 'Beliebtestes Paket',
    perMonth: 'pro Monat',
    onRequest: 'auf Anfrage',
    priceYearlyNote: 'bei jährlicher Zahlung · monatlich: {price}',
    priceMonthlyNote: 'bei monatlicher Zahlung · jährlich: {price} pro Monat',
    ordersPerMonth: 'Bestellungen / Monat',
    users: 'Benutzer',
    channels: 'Verkaufskanäle',
    support: 'Support',
    supportTicket: 'Ticket',
    supportPhone: 'Ticket + Telefon',
    supportSla: 'Ticket + Telefon + SLA',
    unlimited: 'unbegrenzt',
    individual: 'individuell',
    included: 'inklusive',
    pricingTitle: 'Preise & Konditionen',
    service: 'Leistung',
    unit: 'Einheit',
    price: 'Preis',
    baseFee: 'Paket {plan} — Grundgebühr',
    monthlyFee: 'monatliche Softwaregebühr',
    baseFeeIncl: 'inkl. {orders} Bestellungen/Monat, {users} Benutzer, {channels}, Support: {support}',
    baseFeeInclEnterprise: 'Volumen, Benutzer und Leistungsumfang individuell nach Vereinbarung',
    extraOrders: 'Weitere Bestellungen ({range})',
    extraOrdersFlat: 'Weitere Bestellungen (über {n}/Monat)',
    rangeFromTo: '{from} – {to}',
    rangeFrom: 'ab {from}',
    perOrder: 'pro Bestellung',
    extraUser: 'Weiterer Benutzer',
    onboardingRow: 'Onboarding-Paket (Einrichtung, Datenimport, Schulung, Hypercare)',
    onboardingRowOptional: 'Onboarding-Paket – optional (Einrichtung, Datenimport, Schulung, Hypercare)',
    priceFrom: 'ab {price}',
    onboardingOptional: 'optional',
    onboardingTravelNote: 'Zzgl. Reise- und Übernachtungskosten für Termine vor Ort (nach tatsächlichem Aufwand).',
    onboardingFromNote: 'Der genannte Preis ist ein Ab-Preis; der endgültige Festpreis wird nach dem Kickoff-Gespräch auf Basis Ihres Setups festgelegt.',
    onboardingSelfService: 'Einrichtung im Self-Service (ohne Onboarding-Paket)',
    free: 'kostenfrei',
    once: 'einmalig',
    perHour: 'pro Stunde',
    overageNote: 'Zusätzliche Bestellungen werden monatlich nachträglich nach tatsächlichem Verbrauch abgerechnet.',
    featuresTitle: 'Leistungsumfang',
    featureCol: 'Funktion',
    group_core: 'Kern-WMS',
    group_warehouse: 'Lager & Automatisierung',
    group_advanced: 'Erweitert & Schnittstellen',
    group_enterprise: 'Enterprise',
    stateOnRequest: 'möglich',
    stateAddon: 'gegen Angebot',
    stateIncluded: 'enthalten',
    stateExcluded: 'nicht enthalten',
    featuresIncludedTitle: 'Im Paket {plan} enthalten',
    featuresUpgradeTitle: 'Nicht enthalten (Upgrade / Zusatzvereinbarung)',
    yourPlan: 'Ihr Paket',
    onboardingTitle: 'Onboarding-Paket',
    onboardingPriceOnce: '{price} einmalig',
    onboardingIncluded: 'im Angebot enthalten',
    onboardingSteps: 'So läuft Ihr Onboarding',
    addonsTitle: 'Optionale Zusatzleistungen',
    addonSelected: 'im Angebot',
    addonOptional: 'optional',
    unitMonth: 'pro Monat',
    unitOnce: 'einmalig',
    unitHour: 'pro Stunde',
    extraPositions: 'Weitere Positionen',
    dimension: 'Dimension',
    requirementsTitle: 'Kundenanforderungen',
    requirementsAnswer: 'Möglich',
    forecastTitle: 'Beispielrechnung — Ihre voraussichtlichen Kosten',
    fcPlan: 'Paket {plan} ({billing})',
    fcOverage: 'Weitere Bestellungen ({n} über Inklusivvolumen)',
    fcExtraUsers: 'Weitere Benutzer ({n})',
    fcAddons: 'Zusatzleistungen (monatlich)',
    fcTotalMonthly: 'Gesamt pro Monat',
    fcOneTime: 'Einmalig',
    fcOnboarding: 'Onboarding-Paket',
    fcAddonsOnce: 'Zusatzleistungen (einmalig)',
    fcFirstYear: 'Gesamt im ersten Jahr (12 × monatlich + einmalig)',
    fcPerOrder: 'Ø Softwarekosten pro Bestellung',
    fcAssumption: 'Annahme: {orders} Bestellungen/Monat, {users} Benutzer.',
    fcDisclaimer: 'Unverbindliche, beispielhafte Kalkulation auf Basis der genannten Annahmen — kein Vertragsbestandteil. Abgerechnet werden die Grundgebühr des gewählten Pakets sowie tatsächlich genutzte Zusatzbestellungen, Benutzer und Zusatzleistungen.',
    notesTitle: 'Hinweise',
    noteVat: 'Alle Preise verstehen sich netto zzgl. 19 % MwSt.',
    noteValidity: 'Das Angebot ist {days} Tage gültig (bis {date}).',
    noteTermYearly: 'Laufzeit {months} Monate, Abrechnung jährlich im Voraus; danach Verlängerung um jeweils 12 Monate, kündbar mit 3 Monaten Frist zum Laufzeitende.',
    noteTermMonthly: 'Abrechnung monatlich im Voraus{term}.',
    noteTermMonthlyMin: ', Mindestlaufzeit {months} Monate, danach monatlich kündbar',
    noteTermMonthlyNone: ', keine Mindestlaufzeit — monatlich kündbar',
    noteOverage: 'Bestellungen über dem Inklusivvolumen und zusätzliche Benutzer werden monatlich nachträglich abgerechnet; ein Paketwechsel nach oben ist jederzeit zum Monatswechsel möglich.',
    noteHosting: 'Hosting in Deutschland, tägliche Backups, Auftragsverarbeitungsvertrag (AVV) auf Wunsch. Es gelten die AGB der LogYou GmbH für LogShip Cloud.',
    noteOnboarding: 'Das Onboarding-Paket ist optional; ohne Paket erfolgt die Einrichtung im Self-Service mit Ticket-Support.',
    noteOnboardingTravel: 'Reise- und Übernachtungskosten für Vor-Ort-Termine im Rahmen des Onboardings werden nach tatsächlichem Aufwand gesondert berechnet.',
    closing: 'Wir freuen uns darauf, Ihr Lager mit LogShip live zu bringen.',
    closingTeam: 'Ihr LogShip Team · LogYou GmbH · app.logship.de',
    page: 'Seite',
    of: 'von'
  },
  en: {
    docTitle: 'LogShip Cloud offer',
    dateWord: 'Offer dated',
    date: 'Date',
    validUntil: 'Valid until',
    plannedStart: 'Planned start',
    billing: 'Billing',
    billingYearly: 'yearly (in advance)',
    billingMonthly: 'monthly',
    billingYearlyShort: 'yearly payment',
    billingMonthlyShort: 'monthly payment',
    channelsIncl: '{n} sales channels',
    channelsUnlimitedIncl: 'unlimited sales channels',
    minTerm: 'Minimum term',
    months: '{n} months',
    noMinTerm: 'none (cancel monthly)',
    intro: 'Thank you for your interest in LogShip Cloud — the warehouse and shipping software LogYou runs its own fulfillment on every day. We are pleased to submit the following offer.',
    overviewTitle: 'Your package',
    popular: 'Most popular',
    perMonth: 'per month',
    onRequest: 'on request',
    priceYearlyNote: 'with yearly payment · monthly: {price}',
    priceMonthlyNote: 'with monthly payment · yearly: {price} per month',
    ordersPerMonth: 'Orders / month',
    users: 'Users',
    channels: 'Sales channels',
    support: 'Support',
    supportTicket: 'Ticket',
    supportPhone: 'Ticket + phone',
    supportSla: 'Ticket + phone + SLA',
    unlimited: 'unlimited',
    individual: 'individual',
    included: 'included',
    pricingTitle: 'Prices & conditions',
    service: 'Service',
    unit: 'Unit',
    price: 'Price',
    baseFee: '{plan} package — base fee',
    monthlyFee: 'monthly software fee',
    baseFeeIncl: 'incl. {orders} orders/month, {users} users, {channels}, support: {support}',
    baseFeeInclEnterprise: 'volume, users and scope individually as agreed',
    extraOrders: 'Additional orders ({range})',
    extraOrdersFlat: 'Additional orders (above {n}/month)',
    rangeFromTo: '{from} – {to}',
    rangeFrom: 'from {from}',
    perOrder: 'per order',
    extraUser: 'Additional user',
    onboardingRow: 'Onboarding package (setup, data import, training, hypercare)',
    onboardingRowOptional: 'Onboarding package – optional (setup, data import, training, hypercare)',
    priceFrom: 'from {price}',
    onboardingOptional: 'optional',
    onboardingTravelNote: 'Plus travel and accommodation costs for on-site appointments (billed as incurred).',
    onboardingFromNote: 'The stated price is a starting price; the final fixed price is agreed after the kickoff call based on your setup.',
    onboardingSelfService: 'Self-service setup (no onboarding package)',
    free: 'free of charge',
    once: 'one-time',
    perHour: 'per hour',
    overageNote: 'Additional orders are billed monthly in arrears based on actual usage.',
    featuresTitle: 'Scope of features',
    featureCol: 'Feature',
    group_core: 'Core WMS',
    group_warehouse: 'Warehouse & automation',
    group_advanced: 'Advanced & integrations',
    group_enterprise: 'Enterprise',
    stateOnRequest: 'possible',
    stateAddon: 'on quote',
    stateIncluded: 'included',
    stateExcluded: 'not included',
    featuresIncludedTitle: 'Included in the {plan} package',
    featuresUpgradeTitle: 'Not included (upgrade / separate agreement)',
    yourPlan: 'Your package',
    onboardingTitle: 'Onboarding package',
    onboardingPriceOnce: '{price} one-time',
    onboardingIncluded: 'included in this offer',
    onboardingSteps: 'How your onboarding works',
    addonsTitle: 'Optional add-ons',
    addonSelected: 'in this offer',
    addonOptional: 'optional',
    unitMonth: 'per month',
    unitOnce: 'one-time',
    unitHour: 'per hour',
    extraPositions: 'Additional positions',
    dimension: 'Dimension',
    requirementsTitle: 'Customer requirements',
    requirementsAnswer: 'Possible',
    forecastTitle: 'Example calculation — your expected costs',
    fcPlan: '{plan} package ({billing})',
    fcOverage: 'Additional orders ({n} above included volume)',
    fcExtraUsers: 'Additional users ({n})',
    fcAddons: 'Add-ons (monthly)',
    fcTotalMonthly: 'Total per month',
    fcOneTime: 'One-time',
    fcOnboarding: 'Onboarding package',
    fcAddonsOnce: 'Add-ons (one-time)',
    fcFirstYear: 'Total first year (12 × monthly + one-time)',
    fcPerOrder: 'Ø software cost per order',
    fcAssumption: 'Assumption: {orders} orders/month, {users} users.',
    fcDisclaimer: 'Non-binding example calculation based on the stated assumptions — not part of the contract. Billed are the base fee of the chosen package plus actually used additional orders, users and add-ons.',
    notesTitle: 'Notes',
    noteVat: 'All prices are net, plus 19 % VAT.',
    noteValidity: 'This offer is valid for {days} days (until {date}).',
    noteTermYearly: 'Term {months} months, billed yearly in advance; thereafter renewal by 12 months each, cancellable with 3 months notice to the end of the term.',
    noteTermMonthly: 'Billed monthly in advance{term}.',
    noteTermMonthlyMin: ', minimum term {months} months, cancellable monthly thereafter',
    noteTermMonthlyNone: ', no minimum term — cancellable monthly',
    noteOverage: 'Orders above the included volume and additional users are billed monthly in arrears; an upgrade to a higher package is possible at any month change.',
    noteHosting: 'Hosted in Germany, daily backups, data processing agreement (DPA) on request. The LogYou GmbH terms for LogShip Cloud apply.',
    noteOnboarding: 'The onboarding package is optional; without it, setup is self-service with ticket support.',
    noteOnboardingTravel: 'Travel and accommodation costs for on-site onboarding appointments are billed separately as incurred.',
    closing: 'We look forward to taking your warehouse live with LogShip.',
    closingTeam: 'Your LogShip team · LogYou GmbH · app.logship.de',
    page: 'Page',
    of: 'of'
  }
}

const fmt = (template: string, vars: Record<string, any>): string =>
  String(template).replace(/\{(\w+)\}/g, (_, k) => String(vars[k] ?? ''))

const formatDateLocal = (iso: string, language: string): string => {
  if (!iso) return '—'
  const d = new Date(iso)
  if (isNaN(d.getTime())) return iso
  const dd = String(d.getDate()).padStart(2, '0')
  const mm = String(d.getMonth() + 1).padStart(2, '0')
  return language === 'en' ? `${d.getFullYear()}-${mm}-${dd}` : `${dd}.${mm}.${d.getFullYear()}`
}

export const sanitizeSoftwareQuoteFilename = (company: string, date?: string): string => {
  const safe = String(company || 'Kunde')
    .replace(/ä/g, 'ae').replace(/ö/g, 'oe').replace(/ü/g, 'ue')
    .replace(/Ä/g, 'Ae').replace(/Ö/g, 'Oe').replace(/Ü/g, 'Ue')
    .replace(/ß/g, 'ss')
    .replace(/[^A-Za-z0-9-_ ]/g, '')
    .trim()
    .replace(/\s+/g, '-') || 'Kunde'
  const d = date || new Date().toISOString().slice(0, 10)
  // Different prefix from the fulfillment quote on purpose: the contract package
  // picks "Angebot_LogYou_*.pdf" as Anlage 1 and must never grab a SaaS offer.
  return `Angebot_LogShip_${safe}_${d}.pdf`
}

export const buildSoftwareQuoteDocDefinition = (payload: any, logos: { light: string | null; dark: string | null }) => {
  const language = payload?.meta?.language === 'en' ? 'en' : 'de'
  const T = SOFTWARE_QUOTE_TEXTS[language]
  const c = payload.customer || {}
  const pl = payload.plan || {}
  const a = payload.assumptions || {}
  const catalog: SoftwareCatalog = normalizeSoftwareCatalog(payload?.catalog && Array.isArray(payload.catalog.plans) && payload.catalog.plans.length ? payload.catalog : DEFAULT_SOFTWARE_CATALOG)
  const plans = activePlans(catalog)
  const planKey = plans.some((p) => p.key === String(pl.key)) ? String(pl.key) : (plans[0]?.key || '')
  const planRow = plans.find((p) => p.key === planKey)
  const planName = String(pl.name || (planRow ? catalogLabel(planRow, language) : planKey))
  // Monthly software-fee line: name + subtext, each overridable per offer
  const feeLabel = String(pl.feeLabel || '').trim() || planName
  const planTagline = String(pl.description || planRow?.description || '').trim()
  const planIsPopular = pl.popular === true || planRow?.popular === true
  const yearly = pl.billing !== 'monthly'
  const on = (key: string) => payload?.sections?.[key] !== false
  const dateIso = payload?.meta?.date || new Date().toISOString().slice(0, 10)
  const { validDays, validUntil } = resolveQuoteValidity({ ...(payload?.meta || {}), date: dateIso })

  const visuals = payload?.visuals || {}
  const heroShot = visuals.includeScreenshot && visuals.screenshotDataUrl ? String(visuals.screenshotDataUrl) : null
  const customerLogo = visuals.includeLogo && visuals.logoDataUrl ? String(visuals.logoDataUrl) : null
  const websiteDomain = String(visuals.websiteUrl || '').replace(/^https?:\/\//i, '').replace(/\/.*$/, '')

  const locale = language === 'en' ? 'en-IE' : 'de-DE'
  const euro = (amount: any) => new Intl.NumberFormat(locale, { style: 'currency', currency: 'EUR' }).format(Number(amount) || 0)
  const int = (n: any) => new Intl.NumberFormat(locale).format(Math.round(Number(n) || 0))
  const n0 = (v: any) => (Number.isFinite(Number(v)) ? Number(v) : 0)

  const sectionTitle = (text: string) => ({
    stack: [
      { canvas: [{ type: 'rect', x: 0, y: 0, w: 26, h: 3, color: BRAND.orange }] },
      { text: String(text).toUpperCase(), fontSize: 11, bold: true, color: BRAND.navy, characterSpacing: 0.4, margin: [0, 5, 0, 0] }
    ],
    margin: [0, 16, 0, 8]
  })
  const zebraLayout = {
    hLineWidth: (i: number) => (i === 1 ? 0.75 : 0),
    vLineWidth: () => 0,
    hLineColor: () => BRAND.grayLine,
    fillColor: (rowIndex: number) => (rowIndex === 0 ? BRAND.navy : rowIndex % 2 === 0 ? BRAND.zebra : null),
    paddingLeft: () => 8,
    paddingRight: () => 8,
    paddingTop: () => 5,
    paddingBottom: () => 5
  }
  const th = (text: string, alignment: string = 'left', extra: any = {}) => ({ text, style: 'tableHeader', alignment, ...extra })
  const td = (text: any, opts: any = {}) => ({ text: String(text), fontSize: 9, ...opts })
  const tdPrice = (value: any) => td(typeof value === 'number' ? euro(value) : value, { alignment: 'right', bold: typeof value === 'number' })
  const priceTable = (widths: any[], headers: any[], rows: any[][]) => ({ table: { headerRows: 1, widths, body: [headers, ...rows] }, layout: zebraLayout })
  const kv = (label: string, value: any) => [td(label, { color: BRAND.grayText }), td(value || '—', { bold: true })]

  const supportLabel = (s: any) => (s === 'sla' ? T.supportSla : s === 'phone' ? T.supportPhone : T.supportTicket)
  const channelsLabel = n0(pl.salesChannels) > 0 ? int(pl.salesChannels) : T.unlimited
  const includedOrdersLabel = n0(pl.includedOrders) > 0 ? int(pl.includedOrders) : T.individual
  const includedUsersLabel = n0(pl.includedUsers) > 0 ? int(pl.includedUsers) : T.individual
  const priceMain = yearly ? n0(pl.priceYearly) : n0(pl.priceMonthly)
  const priceOther = yearly ? n0(pl.priceMonthly) : n0(pl.priceYearly)
  const isOnRequest = priceMain <= 0
  const minTerm = n0(pl.minTermMonths)
  const billingLabel = yearly ? T.billingYearly : T.billingMonthly

  // ===== Head: address + logo + meta card, intro, optional website hero =====
  const head: any[] = [
    {
      columns: [
        {
          width: '*',
          stack: [
            { text: c.company || '', bold: true, fontSize: 12, color: BRAND.navy },
            { text: c.contactName || '', margin: [0, 2, 0, 0] },
            ...(c.street ? [{ text: c.street, margin: [0, 1, 0, 0] }] : []),
            ...((c.zip || c.city) ? [{ text: [c.zip, c.city].filter(Boolean).join(' '), margin: [0, 1, 0, 0] }] : []),
            { text: c.country || 'Deutschland', margin: [0, 1, 0, 0] }
          ]
        },
        ...(customerLogo ? [{ width: 130, image: customerLogo, fit: [118, 54], alignment: 'center', margin: [0, 4, 6, 0] }] : []),
        {
          width: 210,
          table: {
            widths: [92, '*'],
            body: [
              kv(T.date, formatDateLocal(dateIso, language)),
              kv(T.validUntil, formatDateLocal(validUntil, language)),
              kv(T.plannedStart, formatDateLocal(pl.plannedStart, language)),
              kv(T.billing, billingLabel),
              kv(T.minTerm, minTerm > 0 ? fmt(T.months, { n: minTerm }) : T.noMinTerm)
            ]
          },
          layout: { hLineWidth: () => 0, vLineWidth: () => 0, fillColor: () => BRAND.zebra, paddingLeft: () => 8, paddingRight: () => 8, paddingTop: () => 4, paddingBottom: () => 4 }
        }
      ],
      columnGap: 24,
      margin: [0, 4, 0, 0]
    },
    { text: T.intro, color: BRAND.grayText, margin: [0, 14, 0, 0] },
    ...(heroShot
      ? [{
          unbreakable: true,
          margin: [0, 14, 0, 0],
          table: {
            widths: ['*'],
            body: [
              [{
                fillColor: BRAND.navy,
                margin: [10, 6, 10, 6],
                columns: [
                  { width: 34, canvas: [{ type: 'ellipse', x: 4, y: 5, r1: 3.2, r2: 3.2, color: '#FF5F57' }, { type: 'ellipse', x: 15, y: 5, r1: 3.2, r2: 3.2, color: '#FEBC2E' }, { type: 'ellipse', x: 26, y: 5, r1: 3.2, r2: 3.2, color: '#28C840' }] },
                  { text: websiteDomain || 'Website', color: BRAND.white, fontSize: 8.5, alignment: 'center', margin: [0, 1, 34, 0] }
                ]
              }],
              [{ image: heroShot, width: 513.8, margin: [0, 0, 0, 0] }]
            ]
          },
          layout: { hLineWidth: (i: number, node: any) => (i === 0 || i === node.table.body.length ? 0.75 : 0), vLineWidth: () => 0.75, hLineColor: () => BRAND.grayLine, vLineColor: () => BRAND.grayLine, paddingLeft: () => 0, paddingRight: () => 0, paddingTop: () => 0, paddingBottom: () => 0 }
        }]
      : [])
  ]

  const sections: Array<{ key: string; node: any }> = []

  // ===== ÜBERSICHT: package card =====
  if (on('overview')) {
    const fact = (label: string, value: string) => ({
      stack: [
        { text: value, bold: true, fontSize: 13, color: BRAND.navy },
        { text: label, fontSize: 7.5, color: BRAND.grayText, margin: [0, 1, 0, 0] }
      ]
    })
    sections.push({
      key: 'overview',
      node: {
        unbreakable: true,
        stack: [
          sectionTitle(T.overviewTitle),
          {
            table: {
              widths: [190, '*'],
              body: [[
                {
                  fillColor: BRAND.navy,
                  margin: [14, 14, 14, 14],
                  stack: [
                    ...(planIsPopular ? [{ text: T.popular.toUpperCase(), fontSize: 6.5, bold: true, color: BRAND.orange, characterSpacing: 0.6 }] : []),
                    { text: planName, fontSize: 20, bold: true, color: BRAND.white, margin: [0, 2, 0, planTagline ? 2 : 6] },
                    ...(planTagline ? [{ text: planTagline, fontSize: 7.5, italics: true, color: BRAND.headerSub, margin: [0, 0, 0, 6] }] : []),
                    isOnRequest
                      ? { text: T.onRequest, fontSize: 15, bold: true, color: BRAND.orange }
                      : { text: [{ text: euro(priceMain), fontSize: 19, bold: true, color: BRAND.orange }, { text: `  ${T.perMonth}`, fontSize: 8, color: BRAND.headerSub }] },
                    ...(!isOnRequest && priceOther > 0
                      ? [{ text: fmt(yearly ? T.priceYearlyNote : T.priceMonthlyNote, { price: euro(priceOther) }), fontSize: 7, color: BRAND.headerSub, margin: [0, 4, 0, 0] }]
                      : [])
                  ]
                },
                {
                  margin: [16, 12, 12, 12],
                  stack: [
                    { columns: [fact(T.ordersPerMonth, includedOrdersLabel), fact(T.users, includedUsersLabel)], columnGap: 10 },
                    { columns: [fact(T.channels, channelsLabel), fact(T.support, supportLabel(pl.support))], columnGap: 10, margin: [0, 10, 0, 0] }
                  ]
                }
              ]]
            },
            layout: { hLineWidth: () => 0.75, vLineWidth: () => 0.75, hLineColor: () => BRAND.grayLine, vLineColor: () => BRAND.grayLine, paddingLeft: () => 0, paddingRight: () => 0, paddingTop: () => 0, paddingBottom: () => 0 }
          }
        ]
      }
    })
  }

  // ===== PREISE & KONDITIONEN =====
  const overageTiers: Array<{ upTo: number | null; price: number }> = (Array.isArray(pl.overageTiers) ? pl.overageTiers : [])
    .map((t: any) => ({ upTo: t?.upTo === '' || t?.upTo == null ? null : n0(t.upTo), price: n0(t?.price) }))
    .filter((t: any) => t.price > 0)
    .sort((x: any, y: any) => (x.upTo == null ? 1 : y.upTo == null ? -1 : x.upTo - y.upTo))
  const onboardingOn = on('onboarding') && payload?.onboarding?.enabled === true
  const onboardingPrice = n0(payload?.onboarding?.price)
  const onboardingFrom = payload?.onboarding?.priceIsFrom !== false
  const onboardingTravel = payload?.onboarding?.travelExcluded !== false
  if (on('pricing')) {
    const autoIncl = isOnRequest ? T.baseFeeInclEnterprise : fmt(T.baseFeeIncl, { orders: includedOrdersLabel, users: includedUsersLabel, channels: n0(pl.salesChannels) > 0 ? fmt(T.channelsIncl, { n: int(pl.salesChannels) }) : T.channelsUnlimitedIncl, support: supportLabel(pl.support) })
    const feeSubtextCustom = String(pl.feeSubtext || '').trim()
    const feeSubLines: string[] = feeSubtextCustom ? [feeSubtextCustom] : [...(planTagline ? [planTagline] : []), autoIncl]
    const rows: any[][] = [[
      {
        stack: [
          td(`${feeLabel} — ${T.monthlyFee}`, { bold: true }),
          ...feeSubLines.map((line, i) => td(line, { fontSize: 7.5, color: BRAND.grayText, italics: i === 0 && !feeSubtextCustom && !!planTagline, margin: [0, 2, 0, 0] }))
        ]
      },
      td(T.perMonth, { alignment: 'center' }),
      isOnRequest ? td(T.onRequest, { alignment: 'right', italics: true }) : tdPrice(priceMain)
    ]]
    if (!isOnRequest && n0(pl.includedOrders) > 0) {
      if (overageTiers.length <= 1) {
        rows.push([td(fmt(T.extraOrdersFlat, { n: int(pl.includedOrders) })), td(T.perOrder, { alignment: 'center' }), overageTiers.length ? tdPrice(overageTiers[0].price) : td(T.onRequest, { alignment: 'right', italics: true })])
      } else {
        let prev = n0(pl.includedOrders)
        for (const t of overageTiers) {
          const range = t.upTo == null ? fmt(T.rangeFrom, { from: int(prev + 1) }) : fmt(T.rangeFromTo, { from: int(prev + 1), to: int(t.upTo) })
          rows.push([td(fmt(T.extraOrders, { range })), td(T.perOrder, { alignment: 'center' }), tdPrice(t.price)])
          if (t.upTo != null) prev = t.upTo
        }
      }
    }
    if (!isOnRequest) {
      rows.push([td(T.extraUser), td(T.perMonth, { alignment: 'center' }), n0(pl.extraUserPrice) > 0 ? tdPrice(n0(pl.extraUserPrice)) : td(T.onRequest, { alignment: 'right', italics: true })])
    }
    if (onboardingOn) {
      const obPriceCell = onboardingPrice > 0
        ? (onboardingFrom ? td(fmt(T.priceFrom, { price: euro(onboardingPrice) }), { alignment: 'right', bold: true }) : tdPrice(onboardingPrice))
        : td(T.included, { alignment: 'right', italics: true })
      const obLabel: any = onboardingTravel
        ? { stack: [td(T.onboardingRowOptional, { bold: true }), td(T.onboardingTravelNote, { fontSize: 7.5, color: BRAND.grayText, margin: [0, 2, 0, 0] })] }
        : td(T.onboardingRowOptional, { bold: true })
      rows.push([obLabel, td(T.once, { alignment: 'center' }), obPriceCell])
    } else if (on('onboarding')) {
      rows.push([td(T.onboardingSelfService), td(T.once, { alignment: 'center' }), td(T.free, { alignment: 'right', italics: true })])
    }
    sections.push({
      key: 'pricing',
      node: {
        unbreakable: true,
        stack: [
          sectionTitle(T.pricingTitle),
          priceTable(['*', 80, 110], [th(T.service), th(T.unit, 'center'), th(T.price, 'right')], rows),
          ...(!isOnRequest ? [{ text: T.overageNote, fontSize: 7.5, color: BRAND.grayText, margin: [0, 5, 0, 0] }] : [])
        ]
      }
    })
  }

  // ===== LEISTUNGSUMFANG: 4-column comparison (chosen column highlighted) or plain list =====
  const FEATURES = catalog.features.filter((f) => f.active !== false)
  const stateOf = (f: any, col: string): string => {
    if (col === planKey) {
      const v = payload?.features?.[f.key]
      if (v === 'included' || v === 'excluded' || v === 'addon' || v === 'onRequest') return v
    }
    return featureDefaultState(catalog, f, col)
  }
  const groupKeys = Array.from(new Set(['core', 'warehouse', 'advanced', 'enterprise', ...FEATURES.map((f) => f.group)]))
  const groupTitle = (g: string) => T['group_' + g] || g
  const stateCell = (state: string, highlight: boolean) => {
    const fill = highlight ? '#FDEEE7' : undefined
    if (state === 'included') return { svg: CHECK_SVG, width: 11, alignment: 'center', fillColor: fill }
    if (state === 'excluded') return { svg: DASH_SVG, width: 11, alignment: 'center', fillColor: fill }
    return { text: state === 'addon' ? T.stateAddon : T.stateOnRequest, fontSize: 6.8, italics: true, alignment: 'center', color: BRAND.grayText, fillColor: fill }
  }
  const customFeatures: any[] = Array.isArray(payload?.customFeatures) ? payload.customFeatures.filter((f: any) => String(f?.label || '').trim() !== '') : []
  if (on('features')) {
    // More than 5 packages don't fit as columns on A4 → plain list of the chosen package.
    const compare = payload?.featuresCompare !== false && plans.length >= 2 && plans.length <= 5
    let body: any
    if (compare) {
      const planKeys = plans.map((p) => p.key)
      const colW = plans.length <= 3 ? 70 : plans.length === 4 ? 58 : 48
      const headerRow = [th(T.featureCol), ...plans.map((p) => th(catalogLabel(p, language), 'center', p.key === planKey ? { fillColor: BRAND.orange } : {}))]
      const rows: any[][] = []
      const span = planKeys.length + 1
      for (const g of groupKeys) {
        const inGroup = FEATURES.filter((x) => x.group === g)
        if (!inGroup.length) continue
        rows.push([{ text: groupTitle(g), colSpan: span, bold: true, fontSize: 8, color: BRAND.navy, fillColor: BRAND.zebra, margin: [0, 1, 0, 1] }, ...planKeys.map(() => ({}))])
        for (const f of inGroup) {
          rows.push([td(catalogLabel(f, language)), ...planKeys.map((k) => stateCell(stateOf(f, k), k === planKey))])
        }
      }
      for (const cf of customFeatures) {
        rows.push([td(String(cf.label).trim()), ...planKeys.map((k) => (k === planKey ? stateCell(cf.state || 'included', true) : { text: '', fillColor: undefined }))])
      }
      body = {
        table: { headerRows: 1, widths: ['*', ...plans.map((p, i) => (i === plans.length - 1 ? colW + 6 : colW))], body: [headerRow, ...rows] },
        layout: {
          hLineWidth: (i: number) => (i === 1 ? 0.75 : 0.25),
          vLineWidth: () => 0,
          hLineColor: () => BRAND.grayLine,
          fillColor: (rowIndex: number) => (rowIndex === 0 ? BRAND.navy : null),
          paddingLeft: () => 6,
          paddingRight: () => 6,
          paddingTop: () => 3.5,
          paddingBottom: () => 3.5
        }
      }
    } else {
      const incl: string[] = []
      const rest: string[] = []
      for (const f of FEATURES) {
        const s = stateOf(f, planKey)
        const label = catalogLabel(f, language)
        if (s === 'included') incl.push(label)
        else if (s === 'onRequest') incl.push(`${label} (${T.stateOnRequest})`)
        else rest.push(s === 'addon' ? `${label} (${T.stateAddon})` : label)
      }
      for (const cf of customFeatures) (cf.state === 'excluded' || cf.state === 'addon' ? rest : incl).push(String(cf.label).trim())
      const half = Math.ceil(incl.length / 2)
      // Each row unbreakable: otherwise the badge can stay on one page while its text moves to the next.
      const list = (items: string[]) => ({ stack: items.map((t) => ({ unbreakable: true, columns: [{ width: 12, svg: CHECK_SVG, margin: [0, 1, 0, 0] }, { width: '*', text: t, fontSize: 9 }], columnGap: 5, margin: [0, 0, 0, 4] })) })
      body = {
        stack: [
          { text: fmt(T.featuresIncludedTitle, { plan: planName }), bold: true, fontSize: 9.5, color: BRAND.navy, margin: [0, 0, 0, 6] },
          { columns: [list(incl.slice(0, half)), list(incl.slice(half))], columnGap: 16 },
          ...(rest.length
            ? [
                { text: T.featuresUpgradeTitle, bold: true, fontSize: 9, color: BRAND.grayText, margin: [0, 8, 0, 4] },
                { text: rest.join(' · '), fontSize: 8, color: BRAND.grayText }
              ]
            : [])
        ]
      }
    }
    // Deliberately NOT unbreakable — the matrix is ~30 rows and pdfmake drops
    // whatever overflows an unbreakable block; the header row repeats instead.
    // The 4-column comparison fills most of a page, so it starts on a fresh
    // page (page 1 = address, package card, prices) unless the website hero
    // already pushed the price table down — then a forced break would leave a
    // half-empty page, so let it flow.
    sections.push({ key: 'features', node: { ...(compare && !heroShot ? { pageBreak: 'before' } : {}), stack: [sectionTitle(T.featuresTitle), body] } })
  }

  // ===== ONBOARDING-PAKET =====
  if (onboardingOn) {
    const ob = payload.onboarding || {}
    const highlights: any[] = (Array.isArray(ob.highlights) ? ob.highlights : []).filter((h: any) => String(h?.value || '').trim() !== '' || String(h?.label || '').trim() !== '')
    const phases: any[] = (Array.isArray(ob.phases) ? ob.phases : []).filter((p: any) => String(p?.title || '').trim() !== '' || String(p?.text || '').trim() !== '')
    const priceText = onboardingPrice > 0
      ? `${T.onboardingOptional} · ${fmt(T.onboardingPriceOnce, { price: onboardingFrom ? fmt(T.priceFrom, { price: euro(onboardingPrice) }) : euro(onboardingPrice) })}`
      : T.onboardingIncluded
    const obNotes = [
      ...(onboardingPrice > 0 && onboardingFrom ? [T.onboardingFromNote] : []),
      ...(onboardingTravel ? [T.onboardingTravelNote] : [])
    ]
    sections.push({
      key: 'onboarding',
      node: {
        stack: [
          {
            columns: [
              { width: '*', ...sectionTitle(T.onboardingTitle) },
              {
                width: 'auto',
                margin: [0, 18, 0, 0],
                table: { body: [[{ text: priceText, bold: true, fontSize: 9.5, color: BRAND.white, fillColor: BRAND.orange, margin: [8, 3, 8, 3] }]] },
                layout: 'noBorders'
              }
            ]
          },
          ...(String(ob.intro || '').trim() ? [{ text: String(ob.intro).trim(), fontSize: 9.5, color: BRAND.text, margin: [0, 0, 0, obNotes.length ? 4 : 10] }] : []),
          ...(obNotes.length ? [{ text: obNotes.join(' '), fontSize: 7.5, italics: true, color: BRAND.grayText, margin: [0, 0, 0, 10] }] : []),
          ...(highlights.length
            ? [{
                unbreakable: true,
                table: {
                  widths: highlights.map(() => '*'),
                  body: [highlights.map((h: any) => ({
                    fillColor: BRAND.zebra,
                    margin: [6, 8, 6, 8],
                    stack: [
                      { text: String(h.value || ''), bold: true, fontSize: 15, color: BRAND.orange, alignment: 'center' },
                      { text: String(h.label || ''), fontSize: 7.5, color: BRAND.grayText, alignment: 'center', margin: [0, 2, 0, 0] }
                    ]
                  }))]
                },
                layout: { hLineWidth: () => 0, vLineWidth: () => 2, vLineColor: () => BRAND.white, paddingLeft: () => 0, paddingRight: () => 0, paddingTop: () => 0, paddingBottom: () => 0 },
                margin: [0, 0, 0, 10]
              }]
            : []),
          ...(phases.length
            ? [
                { text: T.onboardingSteps, bold: true, fontSize: 9.5, color: BRAND.navy, margin: [0, 4, 0, 6] },
                ...phases.map((p: any, i: number) => ({
                  unbreakable: true,
                  columns: [
                    { width: 22, svg: numberBadge(i + 1), margin: [0, 1, 0, 0] },
                    {
                      width: '*',
                      stack: [
                        { text: String(p.title || ''), bold: true, fontSize: 9.5, color: BRAND.navy },
                        ...(String(p.text || '').trim() ? [{ text: String(p.text).trim(), fontSize: 8.5, color: BRAND.text, margin: [0, 1.5, 0, 0] }] : [])
                      ]
                    }
                  ],
                  columnGap: 8,
                  margin: [0, 0, 0, 7]
                }))
              ]
            : [])
        ]
      }
    })
  }

  // ===== ZUSATZLEISTUNGEN (all rows; chosen ones marked) =====
  const addons: any[] = (Array.isArray(payload?.addons) ? payload.addons : []).filter((x: any) => String(x?.label || '').trim() !== '')
  if (on('addons') && addons.length) {
    const unitLabel = (u: string) => (u === 'once' ? T.unitOnce : u === 'hour' ? T.unitHour : u === 'month' ? T.unitMonth : '—')
    const rows: any[][] = addons.map((x: any) => {
      const note = String(x.note || '').trim()
      const labelCell = note
        ? { stack: [td(x.label, { bold: x.enabled === true }), td(note, { fontSize: 7.5, italics: true, color: BRAND.grayText, margin: [0, 1, 0, 0] })] }
        : td(x.label, { bold: x.enabled === true })
      const priceCell = x.unit === 'text' || !(n0(x.price) > 0) ? td(note || T.onRequest, { alignment: 'right', italics: true }) : tdPrice(n0(x.price))
      return [
        { svg: x.enabled === true ? CHECK_SVG : DASH_SVG, width: 11, alignment: 'center', margin: [0, 1, 0, 0] },
        labelCell,
        td(x.unit === 'text' ? '—' : unitLabel(x.unit), { alignment: 'center' }),
        priceCell
      ]
    })
    // Legend line: check = chosen for this offer, dash = optional
    const legend = {
      columns: [
        { width: 10, svg: CHECK_SVG, margin: [0, 1, 0, 0] }, { width: 'auto', text: T.addonSelected, fontSize: 7.5, color: BRAND.grayText, margin: [3, 0, 10, 0] },
        { width: 10, svg: DASH_SVG, margin: [0, 1, 0, 0] }, { width: '*', text: T.addonOptional, fontSize: 7.5, color: BRAND.grayText, margin: [3, 0, 0, 0] }
      ],
      margin: [0, 5, 0, 0]
    }
    sections.push({
      key: 'addons',
      node: {
        unbreakable: true,
        stack: [
          sectionTitle(T.addonsTitle),
          priceTable([22, '*', 80, 110], [th(''), th(T.service), th(T.unit, 'center'), th(T.price, 'right')], rows),
          legend
        ]
      }
    })
  }

  // ===== Weitere Positionen / Kundenanforderungen (same look as the fulfillment quote) =====
  const customPositions = Array.isArray(payload?.customPositions) ? payload.customPositions.filter((r: any) => String(r?.description || '').trim() !== '') : []
  if (customPositions.length) {
    const posRows: any[][] = customPositions.map((r: any) => {
      const raw = String(r?.price ?? '').trim()
      const numeric = Number(raw.replace(/€/g, '').replace(/\s/g, '').replace(',', '.'))
      return [td(String(r.description).trim()), td(String(r?.dimension || '').trim() || '—', { alignment: 'center' }), raw !== '' && Number.isFinite(numeric) ? tdPrice(numeric) : td(raw || '—', { alignment: 'right', italics: raw !== '' })]
    })
    sections.push({ key: 'positions', node: { unbreakable: true, stack: [sectionTitle(T.extraPositions), priceTable(['*', 100, 120], [th(T.service), th(T.dimension, 'center'), th(T.price, 'right')], posRows)] } })
  }
  const requirements = Array.isArray(payload?.requirements) ? payload.requirements.filter((r: any) => String(r?.text || '').trim() !== '') : []
  if (requirements.length) {
    const reqRows: any[][] = requirements.map((r: any) => [
      td(String(r.text).trim()),
      r?.answerType === 'text' ? td(String(r?.answerText || '').trim() || '—', { alignment: 'right', italics: true }) : { svg: r?.possible !== false ? CHECK_SVG : CROSS_SVG, width: 16, alignment: 'center' }
    ])
    sections.push({ key: 'requirements', node: { unbreakable: true, stack: [sectionTitle(T.requirementsTitle), priceTable(['*', 90], [th(T.service), th(T.requirementsAnswer, 'center')], reqRows)] } })
  }

  // ===== BEISPIELRECHNUNG =====
  const fc = payload?.forecast || {}
  if (on('forecast') && fc.includeInPdf && Array.isArray(fc.lines) && fc.lines.length && !isOnRequest) {
    const label = (l: any) => {
      if (l.key === 'plan') return fmt(T.fcPlan, { plan: feeLabel, billing: yearly ? T.billingYearlyShort : T.billingMonthlyShort })
      if (l.key === 'overage') return fmt(T.fcOverage, { n: int(l.qty) })
      if (l.key === 'extraUsers') return fmt(T.fcExtraUsers, { n: int(l.qty) })
      if (l.key === 'addons') return T.fcAddons
      return String(l.key)
    }
    const rows: any[][] = fc.lines.map((l: any) => [td(label(l)), tdPrice(n0(l.monthly))])
    rows.push([
      { text: T.fcTotalMonthly, bold: true, fontSize: 10, color: BRAND.white, fillColor: BRAND.navy },
      { text: euro(n0(fc.totalMonthly)), bold: true, fontSize: 10, color: BRAND.orange, alignment: 'right', fillColor: BRAND.navy }
    ])
    const oneTime: any[] = Array.isArray(fc.oneTime) ? fc.oneTime : []
    for (const o of oneTime) {
      const isOb = o.key === 'onboarding'
      const label = `${T.fcOneTime}: ${isOb ? T.fcOnboarding : T.fcAddonsOnce}${isOb ? ` (${T.onboardingOptional})` : ''}`
      rows.push([td(label), isOb && onboardingFrom ? td(fmt(T.priceFrom, { price: euro(n0(o.amount)) }), { alignment: 'right', bold: true }) : tdPrice(n0(o.amount))])
    }
    rows.push([td(T.fcFirstYear, { bold: true }), tdPrice(n0(fc.firstYear))])
    if (n0(fc.perOrder) > 0) rows.push([td(T.fcPerOrder, { color: BRAND.grayText }), td(euro(n0(fc.perOrder)), { alignment: 'right', color: BRAND.grayText })])
    sections.push({
      key: 'forecast',
      node: {
        unbreakable: true,
        stack: [
          sectionTitle(T.forecastTitle),
          priceTable(['*', 130], [th(T.service), th(T.price, 'right')], rows),
          { text: `${fmt(T.fcAssumption, { orders: int(a.ordersPerMonth), users: int(a.users) })} ${T.fcDisclaimer}`, fontSize: 7.5, italics: true, color: BRAND.grayText, margin: [0, 5, 0, 0] }
        ]
      }
    })
  }

  // ===== Free text blocks anchored after sections =====
  const textBlocks: any[] = (Array.isArray(payload?.customTexts) ? payload.customTexts : []).filter((b: any) => String(b?.text || '').trim() !== '')
  const textBlockNode = (b: any) => {
    const title = String(b?.title || '').trim()
    const text = String(b.text).replace(/\r\n?/g, '\n').trim()
    return { unbreakable: text.length < 1500, stack: [...(title ? [sectionTitle(title)] : []), { text, fontSize: 9.5, margin: [0, title ? 2 : 16, 0, 0] }] }
  }
  const renderedKeys = new Set(sections.map((s) => s.key))
  const blocksByAnchor = new Map<string, any[]>()
  for (const b of textBlocks) {
    const anchor = renderedKeys.has(String(b?.after)) ? String(b.after) : 'end'
    blocksByAnchor.set(anchor, [...(blocksByAnchor.get(anchor) || []), textBlockNode(b)])
  }
  const body: any[] = []
  for (const s of sections) body.push(s.node, ...(blocksByAnchor.get(s.key) || []))
  body.push(...(blocksByAnchor.get('end') || []))

  // ===== Hinweise + closing =====
  const notes: any[] = [
    { text: T.noteVat },
    { text: fmt(T.noteValidity, { days: validDays, date: formatDateLocal(validUntil, language) }) },
    { text: yearly ? fmt(T.noteTermYearly, { months: minTerm > 0 ? minTerm : 12 }) : fmt(T.noteTermMonthly, { term: minTerm > 0 ? fmt(T.noteTermMonthlyMin, { months: minTerm }) : T.noteTermMonthlyNone }) }
  ]
  if (!isOnRequest) notes.push({ text: T.noteOverage })
  if (on('onboarding')) notes.push({ text: T.noteOnboarding })
  if (onboardingOn && onboardingTravel) notes.push({ text: T.noteOnboardingTravel })
  notes.push({ text: T.noteHosting })
  const notesNode: any = {
    unbreakable: true,
    stack: [
      sectionTitle(T.notesTitle),
      { ul: notes, fontSize: 8, color: BRAND.grayText, markerColor: BRAND.orange },
      {
        columns: [
          { width: 'auto', canvas: [{ type: 'rect', x: 0, y: 2, w: 3, h: 26, color: BRAND.orange }] },
          { width: '*', stack: [{ text: T.closing, bold: true, fontSize: 11, color: BRAND.navy }, { text: T.closingTeam, fontSize: 9.5, color: BRAND.grayText, margin: [0, 3, 0, 0] }], margin: [10, 0, 0, 0] }
        ],
        margin: [0, 18, 0, 0]
      }
    ]
  }

  return {
    pageSize: 'A4',
    pageMargins: [40, 106, 40, 86],
    info: { title: `${T.docTitle} – ${c.company || ''}`, author: 'LogYou GmbH', subject: T.docTitle },
    header: buildBrandHeader(T.docTitle, `app.logship.de  ·  ${T.dateWord} ${formatDateLocal(dateIso, language)}`, logos.dark),
    footer: buildBrandFooter(T.page, T.of),
    content: [...head, ...body, notesNode],
    styles: { tableHeader: { fontSize: 8.5, bold: true, color: BRAND.white } },
    defaultStyle: { font: 'Roboto', fontSize: 9.5, color: BRAND.text, lineHeight: 1.25 }
  }
}

/** Build the final PDF buffer + authoritative filename. */
export const generateSoftwareQuotePdf = async (payload: any): Promise<{ buffer: Buffer; filename: string }> => {
  // Same defensive vfs extraction as generateQuotePdf (export shape varies by bundler).
  const pdfMake = await import('pdfmake/build/pdfmake.js')
  const pdfFonts = await import('pdfmake/build/vfs_fonts.js')
  // @ts-ignore
  const pdfMakeInstance = pdfMake.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

  const logos = await getQuoteLogos()
  if (payload?.visuals && (payload.visuals.logoDataUrl || payload.visuals.screenshotDataUrl)) {
    const { normalizePdfImage } = await import('./pdfImages')
    const [logo, shot] = await Promise.all([
      payload.visuals.includeLogo && payload.visuals.logoDataUrl ? normalizePdfImage(payload.visuals.logoDataUrl) : Promise.resolve(null),
      payload.visuals.includeScreenshot && payload.visuals.screenshotDataUrl ? normalizePdfImage(payload.visuals.screenshotDataUrl) : Promise.resolve(null)
    ])
    payload = { ...payload, visuals: { ...payload.visuals, logoDataUrl: logo || '', includeLogo: !!logo && payload.visuals.includeLogo, screenshotDataUrl: shot || '', includeScreenshot: !!shot && payload.visuals.includeScreenshot } }
  }
  const docDefinition = buildSoftwareQuoteDocDefinition(payload, logos)
  const filename = sanitizeSoftwareQuoteFilename(payload?.customer?.company, payload?.meta?.date)
  const buffer = await new Promise<Buffer>((resolve, reject) => {
    try {
      pdfMakeInstance.createPdf(docDefinition).getBuffer((buf: Buffer) => resolve(buf))
    } catch (error) {
      reject(error)
    }
  })
  return { buffer, filename }
}

/** Persistable conditions of a software offer (offer_conditions.softwareQuote). */
export const conditionsFromSoftwareQuotePayload = (p: any) => ({
  offerType: 'software',
  language: p?.meta?.language === 'en' ? 'en' : 'de',
  customer: p?.customer || {},
  plan: p?.plan || {},
  assumptions: p?.assumptions || {},
  features: p?.features && typeof p.features === 'object' ? p.features : {},
  customFeatures: Array.isArray(p?.customFeatures) ? p.customFeatures : [],
  featuresCompare: p?.featuresCompare !== false,
  addons: Array.isArray(p?.addons) ? p.addons : [],
  onboarding: p?.onboarding || {},
  customPositions: Array.isArray(p?.customPositions) ? p.customPositions : [],
  requirements: Array.isArray(p?.requirements) ? p.requirements : [],
  sections: p?.sections && typeof p.sections === 'object' ? p.sections : {},
  customTexts: Array.isArray(p?.customTexts) ? p.customTexts : [],
  forecast: { includeInPdf: p?.forecast?.includeInPdf === true },
  validDays: resolveQuoteValidity(p?.meta).validDays,
  confirmation: { enabled: p?.confirmation?.enabled !== false, attachPdf: p?.confirmation?.attachPdf === true }
})
