/**
 * Branded LogYou offer (Angebot) PDF builder — pdfmake, server-side.
 * Brand: orange #F15924 / navy #1C324B (from the official logo SVG).
 *
 * Exports the brand constants + header/footer builders separately so the
 * later contract (Vertrag) generator (server/api/offers/contract/*) can
 * reuse the same look without duplicating it.
 */

export const BRAND = {
  orange: '#F15924',
  navy: '#1C324B',
  navySoft: '#2A4560',
  headerSub: '#AFC0D0',
  grayLine: '#E3E7EC',
  grayText: '#5A6B7C',
  zebra: '#F6F8FA',
  text: '#33414E',
  white: '#FFFFFF'
}

export const LEGAL_FOOTER_LINES = [
  'LogYou GmbH · Mühlenweg 4 · 35510 Butzbach  |  Geschäftsführer: Dennis Montag  |  Amtsgericht Friedberg, HRB 10595',
  'Telefon: +49 60 33 / 91 60 57-0  |  Fax: +49 60 33 / 91 60 57-9  |  EMail: info@logyou.de  |  USt-IdNr.: DE 369380129',
  'IBAN: DE 0251 3900 0000 9558 8301  |  Volksbank Mittelhessen'
]

// Every customer-facing string of the quote PDF, per language.
export const QUOTE_TEXTS: any = {
  de: {
    docTitle: 'Fulfillment Angebot',
    dateWord: 'Angebot vom',
    date: 'Datum',
    validUntil: 'Gültig bis',
    plannedStart: 'Geplanter Start',
    version: 'Version',
    intro: 'Vielen Dank für Ihr Interesse. Gerne unterbreiten wir Ihnen folgendes Angebot für Ihr Fulfillment.',
    assumptions: 'Annahmen',
    businessModel: 'Geschäftsmodell',
    shopSystem: 'Shopsystem / Warenwirtschaft',
    avgPicks: 'Ø Picks pro Bestellung',
    ordersPerMonth: 'Ø Bestellungen pro Monat',
    skuCount: 'Ø Gelagerte Artikel (SKUs)',
    storageNeed: 'Lagerbedarf',
    onboarding: 'Onboarding & Setupkosten (IT-Support)',
    free: 'kostenfrei',
    inbound: 'Wareneingang',
    inboundIncluded: 'Warenannahme, Mengenkontrolle & Einlagerung (angemeldet, sortenrein, etikettiert)',
    included: 'im Fulfillment-Preis enthalten',
    inboundExtra: 'Nicht sortenrein / unangemeldet, Etikettierung, Inventur (zzgl. Material)',
    inboundByTime: 'Warenannahme, Mengenkontrolle & Einlagerung',
    inboundByTimePrice: 'nach tatsächlichem Zeitaufwand*',
    byEffort: 'nach Aufwand*',
    laborWork: 'Arbeitszeit Lagerarbeit (nach individuellem Aufwand)',
    perHour: 'pro Stunde',
    serviceGrading: 'Grading / Aufbereitung',
    serviceKitting: 'Konfektionierung',
    servicePreFba: 'Pre-FBA Vorbereitung (Amazon)',
    extraPositions: 'Weitere Positionen',
    fulfillmentReturns: 'Fulfillment & Retoure',
    volumeMatrixTitle: 'Preisstaffel nach Bestellvolumen',
    volumeMatrixHeader: 'Bestellungen pro Monat',
    service: 'Leistung',
    dimension: 'Dimension',
    price: 'Preis',
    piece: '1 St.',
    time: 'Zeit',
    perMonth: 'pro Monat',
    orderBaseprice: 'Basispreis pro Auftrag',
    orderBasepriceTier: 'Basispreis pro Auftrag (Volumenstufe {n})',
    pickPrice: 'Preis pro Pick',
    pickPriceFrom: 'Preis pro Pick (ab dem {n}. Artikel)',
    customs: 'Zoll-Aufträge (Ausland außerhalb EU) zusätzlich',
    extraWork: 'Zusatzarbeiten (Umpalettieren usw.)',
    palletMove: 'Palette Ein-/Auslagern',
    returnBase: 'Retoure Basispreis',
    returnPerItem: 'Retoure pro Artikel',
    hourlyNote: '*Preis pro Stunde für Arbeiten nach individuellem Aufwand: {rate} pro Stunde, Abrechnung im 5-Minuten-Takt.',
    storageTitle: 'Lagerung (sortenrein)',
    storageType: 'Lagertyp',
    storageSize: 'Größe der Lagerfläche (B×T×H)',
    monthlyCost: 'Monatspreis',
    shelfSmall: 'Fachbodenregal M',
    shelfLarge: 'Fachbodenregal L',
    palletSpace: 'Palettenstellplatz',
    volumeStorage: 'Volumenlagerung',
    perM3: 'pro m³',
    monthlyCosts: 'Monatliche Fulfillment Kosten',
    serviceFee: 'Servicepauschale',
    serviceFeeIncl: 'inkl. Online-Dashboard, Software-Anbindung, Abrechnung & Support',
    packaging: 'Verpackung',
    cartonSize: 'Kartongröße',
    volume: 'Volumen',
    cartonPrice: 'Preis pro Karton',
    paperPrice: 'Packpapier pro Karton',
    individually: 'individuell kalkuliert',
    onRequest: 'auf Anfrage',
    einwegpalette: 'Einwegpalette',
    packagingNote: 'Label und weiteres Verpackungsmaterial nach Verbrauch.',
    shippingTitle: 'Versandkosten National (Deutsche Post / DHL)***',
    shippingPartnerTitle: 'Versand',
    shippingPartnerNote: 'Der Versand erfolgt über den bestehenden Frachtvertrag des Kunden{carrierPart}.',
    shippingPartnerCarrierPart: ' mit {carrier}',
    shippingMethod: 'Versandart',
    tierKg1: 'Kleinpaket bis 1 kg**',
    tierKg2: 'Paket bis 2 kg*',
    tierKg5: 'Paket bis 5 kg*',
    tierKg10: 'Paket bis 10 kg*',
    tierKg20: 'Paket bis 20 kg*',
    tierKg30: 'Paket bis 31,5 kg*',
    spedition: 'Speditionsversand',
    speditionDefault: 'Tagespreis auf Anfrage',
    forecastTitle: 'Beispielrechnung — Ihre voraussichtlichen monatlichen Kosten',
    fcFulfillment: 'Fulfillment ({orders} Aufträge)',
    fcReturns: 'Retouren (ca. {rate} %)',
    fcStorage: 'Lagerung',
    fcServiceFee: 'Servicepauschale',
    fcPackaging: 'Verpackung (Schätzung Größe {carton})',
    fcShipping: 'Versand (DHL, Annahme bis 2 kg)',
    fcTotal: 'Gesamt pro Monat',
    fcFulfillmentPerOrder: 'Fulfillment-Preis pro Auftrag (inkl. Verpackung & Versand)',
    fcDisclaimer: 'Unverbindliche, beispielhafte Kalkulation auf Basis der genannten Annahmen — kein Vertragsbestandteil. Abgerechnet wird ausschließlich nach tatsächlichem Verbrauch. Versandkosten sind Tagespreise des Frachtdienstleisters, können variieren und sind hier nur beispielhaft mit DHL Paket bis 2 kg gerechnet.',
    fcDisclaimerNoShipping: 'Unverbindliche, beispielhafte Kalkulation auf Basis der genannten Annahmen — kein Vertragsbestandteil. Abgerechnet wird ausschließlich nach tatsächlichem Verbrauch. Versandkosten sind hier nicht enthalten, da der Versand über den bestehenden Frachtvertrag des Kunden erfolgt.',
    fcDisclaimerBase: 'Unverbindliche, beispielhafte Kalkulation auf Basis der genannten Annahmen — kein Vertragsbestandteil. Abgerechnet wird ausschließlich nach tatsächlichem Verbrauch.',
    notesTitle: 'Hinweise',
    noteVat: 'Alle Preise verstehen sich netto zzgl. 19 % MwSt.',
    noteValidity: 'Das Angebot ist {days} Tage gültig.',
    noteInsurance: '* Versichert bis 500 EUR inkl. Tracking   ** Versichert bis 20 EUR inkl. Tracking   *** Versandpreise sind abhängig vom Frachtdienstleister und können variieren.',
    closing: 'Wir freuen uns auf die Zusammenarbeit!',
    closingTeam: 'Ihr LogYou Team',
    page: 'Seite',
    of: 'von'
  },
  en: {
    docTitle: 'Fulfillment Quote',
    dateWord: 'Quote of',
    date: 'Date',
    validUntil: 'Valid until',
    plannedStart: 'Planned start',
    version: 'Version',
    intro: 'Thank you for your interest. We are pleased to present the following quote for your fulfillment.',
    assumptions: 'Assumptions',
    businessModel: 'Business model',
    shopSystem: 'Shop system / ERP',
    avgPicks: 'Avg. picks per order',
    ordersPerMonth: 'Avg. orders per month',
    skuCount: 'Avg. stored items (SKUs)',
    storageNeed: 'Storage requirement',
    onboarding: 'Onboarding & setup costs (IT support)',
    free: 'free of charge',
    inbound: 'Inbound / Goods receipt',
    inboundIncluded: 'Goods receipt, quantity check & put-away (announced, sorted, labelled)',
    included: 'included in the fulfillment price',
    inboundExtra: 'Unsorted / unannounced goods, labelling, stocktaking (plus material)',
    inboundByTime: 'Goods receipt, quantity check & put-away',
    inboundByTimePrice: 'by actual time spent*',
    byEffort: 'by effort*',
    laborWork: 'Warehouse labor time (by individual effort)',
    perHour: 'per hour',
    serviceGrading: 'Grading / refurbishment',
    serviceKitting: 'Kitting / assembly',
    servicePreFba: 'Pre-FBA preparation (Amazon)',
    extraPositions: 'Additional positions',
    fulfillmentReturns: 'Fulfillment & Returns',
    volumeMatrixTitle: 'Volume-based pricing tiers',
    volumeMatrixHeader: 'Orders per month',
    service: 'Service',
    dimension: 'Dimension',
    price: 'Price',
    piece: '1 pc.',
    time: 'time',
    perMonth: 'per month',
    orderBaseprice: 'Base price per order',
    orderBasepriceTier: 'Base price per order (volume tier {n})',
    pickPrice: 'Price per pick',
    pickPriceFrom: 'Price per pick (from item no. {n})',
    customs: 'Customs orders (outside the EU), additional',
    extraWork: 'Additional work (re-palletizing etc.)',
    palletMove: 'Pallet in-/outbound movement',
    returnBase: 'Return base price',
    returnPerItem: 'Return per item',
    hourlyNote: '*Hourly rate for work billed by individual effort: {rate} per hour, billed in 5-minute increments.',
    storageTitle: 'Storage (sorted by SKU)',
    storageType: 'Storage type',
    storageSize: 'Storage area size (W×D×H)',
    monthlyCost: 'Monthly price',
    shelfSmall: 'Shelf rack M',
    shelfLarge: 'Shelf rack L',
    palletSpace: 'Pallet space',
    volumeStorage: 'Volume storage',
    perM3: 'per m³',
    monthlyCosts: 'Monthly fulfillment costs',
    serviceFee: 'Service fee',
    serviceFeeIncl: 'incl. online dashboard, software integration, billing & support',
    packaging: 'Packaging',
    cartonSize: 'Carton size',
    volume: 'Volume',
    cartonPrice: 'Price per carton',
    paperPrice: 'Packing paper per carton',
    individually: 'calculated individually',
    onRequest: 'on request',
    einwegpalette: 'Disposable pallet',
    packagingNote: 'Labels and further packaging material by consumption.',
    shippingTitle: 'Domestic shipping costs (Deutsche Post / DHL)***',
    shippingPartnerTitle: 'Shipping',
    shippingPartnerNote: 'Shipping is handled via the customer\'s existing freight contract{carrierPart}.',
    shippingPartnerCarrierPart: ' with {carrier}',
    shippingMethod: 'Shipping method',
    tierKg1: 'Small parcel up to 1 kg**',
    tierKg2: 'Parcel up to 2 kg*',
    tierKg5: 'Parcel up to 5 kg*',
    tierKg10: 'Parcel up to 10 kg*',
    tierKg20: 'Parcel up to 20 kg*',
    tierKg30: 'Parcel up to 31.5 kg*',
    spedition: 'Freight forwarding',
    speditionDefault: 'daily rate on request',
    forecastTitle: 'Example calculation — your expected monthly costs',
    fcFulfillment: 'Fulfillment ({orders} orders)',
    fcReturns: 'Returns (approx. {rate} %)',
    fcStorage: 'Storage',
    fcServiceFee: 'Service fee',
    fcPackaging: 'Packaging (estimate size {carton})',
    fcShipping: 'Shipping (DHL, assumed up to 2 kg)',
    fcTotal: 'Total per month',
    fcFulfillmentPerOrder: 'Fulfillment price per order (incl. packaging & shipping)',
    fcDisclaimer: 'Non-binding example calculation based on the stated assumptions — not part of the contract. Billing is based solely on actual consumption. Shipping costs are the carrier\'s daily rates, may vary and are only exemplified here with DHL parcel up to 2 kg.',
    fcDisclaimerNoShipping: 'Non-binding example calculation based on the stated assumptions — not part of the contract. Billing is based solely on actual consumption. Shipping costs are not included here, as shipping is handled via the customer\'s existing freight contract.',
    fcDisclaimerBase: 'Non-binding example calculation based on the stated assumptions — not part of the contract. Billing is based solely on actual consumption.',
    notesTitle: 'Notes',
    noteVat: 'All prices are net plus 19 % German VAT.',
    noteValidity: 'This quote is valid for {days} days.',
    noteInsurance: '* Insured up to EUR 500 incl. tracking   ** Insured up to EUR 20 incl. tracking   *** Shipping prices depend on the carrier and may vary.',
    closing: 'We look forward to working with you!',
    closingTeam: 'Your LogYou team',
    page: 'Page',
    of: 'of'
  }
}

const A4_WIDTH = 595.28

// Sections that make up "page 2" of the quote (the first rendered one gets the
// page break). Keys must match QUOTE_SECTION_KEYS / QUOTE_TEXT_ANCHORS in
// app/composables/useOfferQuote.ts — keep in sync.
const PAGE2_KEYS = ['inbound', 'storage', 'monthly', 'packaging', 'shipping']

// Authoritative filename — never trust the client's.
export const sanitizeQuoteFilename = (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)
  return `Angebot_LogYou_${safe}_${d}.pdf`
}

// Logos bundled via Nitro server assets (server/assets/offers/*).
let logoCache: { light: string | null; dark: string | null } | null = null
export const getQuoteLogos = async (): Promise<{ light: string | null; dark: string | null }> => {
  if (logoCache) return logoCache
  const storage = useStorage('assets:server')
  const load = async (key: string): Promise<string | null> => {
    try {
      const raw: any = await storage.getItemRaw(key)
      if (!raw) return null
      const buf = Buffer.isBuffer(raw) ? raw : Buffer.from(raw)
      return 'data:image/png;base64,' + buf.toString('base64')
    } catch {
      return null
    }
  }
  logoCache = {
    light: await load('offers/logyou-logo.png'),
    dark: await load('offers/logyou-logo-dark.png')
  }
  return logoCache
}

const fmt = (template: string, vars: Record<string, any>): 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()}`
}

/** Shared branded page header (navy band + logo + orange rule). Reused by the future contract PDF. */
export const buildBrandHeader = (title: string, subtitle: string, logoDarkDataUrl: string | null) => {
  return () => ({
    stack: [
      {
        table: {
          widths: ['*'],
          body: [[{
            border: [false, false, false, false],
            fillColor: BRAND.navy,
            margin: [40, 20, 40, 16],
            columns: [
              logoDarkDataUrl
                ? { image: logoDarkDataUrl, fit: [128, 34], width: 150, margin: [0, 2, 0, 0] }
                : { text: 'LOGYOU', color: BRAND.white, bold: true, fontSize: 20, width: 150, margin: [0, 6, 0, 0] },
              {
                width: '*',
                stack: [
                  { text: title, color: BRAND.white, bold: true, fontSize: 15, alignment: 'right' },
                  { text: subtitle, color: BRAND.headerSub, fontSize: 8, alignment: 'right', margin: [0, 4, 0, 0] }
                ]
              }
            ]
          }]]
        },
        layout: {
          defaultBorder: false,
          paddingLeft: () => 0,
          paddingRight: () => 0,
          paddingTop: () => 0,
          paddingBottom: () => 0
        }
      },
      { canvas: [{ type: 'rect', x: 0, y: 0, w: A4_WIDTH, h: 3, color: BRAND.orange }] }
    ]
  })
}

/** Shared branded page footer (orange hairline + legal lines + page x of y). */
export const buildBrandFooter = (pageWord: string, ofWord: string) => {
  return (currentPage: number, pageCount: number) => ({
    margin: [40, 6, 40, 0],
    stack: [
      { canvas: [{ type: 'line', x1: 0, y1: 0, x2: A4_WIDTH - 80, y2: 0, lineWidth: 0.75, lineColor: BRAND.orange }] },
      ...LEGAL_FOOTER_LINES.map((line, i) => ({
        text: line,
        fontSize: 6.6,
        color: BRAND.grayText,
        alignment: 'center',
        margin: [0, i === 0 ? 5 : 2, 0, 0]
      })),
      { text: `${pageWord} ${currentPage} ${ofWord} ${pageCount}`, fontSize: 6.6, color: BRAND.grayText, alignment: 'right', margin: [0, 2, 0, 0] }
    ]
  })
}

export const buildQuoteDocDefinition = (payload: any, logos: { light: string | null; dark: string | null }) => {
  const language = payload?.meta?.language === 'en' ? 'en' : 'de'
  const T = QUOTE_TEXTS[language]
  const c = payload.customer || {}
  const a = payload.assumptions || {}
  const p = payload.pricing || {}
  const st = a.storage || {}
  const inb = payload.inbound || {}
  // Per-offer section on/off map from the modal (payload.sections). A missing
  // map — legacy payloads, direct API callers — renders everything, as before.
  const on = (key: string) => payload?.sections?.[key] !== false
  const validDays = Number(payload?.meta?.validDays) || 7
  const dateIso = payload?.meta?.date || new Date().toISOString().slice(0, 10)
  const validUntil = new Date(new Date(dateIso).getTime() + validDays * 86400000).toISOString().slice(0, 10)

  // Optional PDF visuals (independent, combinable): customer logo next to the
  // address block + website screenshot as a browser-framed hero under the intro.
  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 euro = (amount: any) =>
    new Intl.NumberFormat(language === 'en' ? 'en-IE' : 'de-DE', { style: 'currency', currency: 'EUR' }).format(Number(amount) || 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') => ({ text, style: 'tableHeader', alignment })
  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
  })

  // Storage-need summary string for the assumptions grid
  const storageParts: string[] = []
  if (Number(st.shelfSmallQty) > 0) storageParts.push(`${st.shelfSmallQty}× ${T.shelfSmall}`)
  if (Number(st.shelfLargeQty) > 0) storageParts.push(`${st.shelfLargeQty}× ${T.shelfLarge}`)
  if (Number(st.palletQty) > 0) storageParts.push(`${st.palletQty}× ${T.palletSpace}`)
  if (Number(st.volumeM3) > 0) storageParts.push(`${st.volumeM3} m³`)
  const storageSummary = storageParts.length ? storageParts.join(', ') : '—'

  const kv = (label: string, value: any) => [
    td(label, { color: BRAND.grayText }),
    td(value || '—', { bold: true })
  ]

  // FULFILLMENT & RETOURE rows (tier rows only when > 0)
  const pickfree = Number(p.qtyPickfree) || 0
  const pickLabel = pickfree > 0 ? fmt(T.pickPriceFrom, { n: pickfree + 1 }) : T.pickPrice
  const fulfillmentRows: any[][] = [
    [td(T.orderBaseprice), td(T.piece, { alignment: 'center' }), tdPrice(Number(p.orderBaseprice))]
  ]
  if (Number(p.orderBaseprice2) > 0) fulfillmentRows.push([td(fmt(T.orderBasepriceTier, { n: 2 })), td(T.piece, { alignment: 'center' }), tdPrice(Number(p.orderBaseprice2))])
  if (Number(p.orderBaseprice3) > 0) fulfillmentRows.push([td(fmt(T.orderBasepriceTier, { n: 3 })), td(T.piece, { alignment: 'center' }), tdPrice(Number(p.orderBaseprice3))])
  fulfillmentRows.push(
    [td(pickLabel), td(T.piece, { alignment: 'center' }), tdPrice(Number(p.orderPickprice))],
    [td(T.customs), td(T.piece, { alignment: 'center' }), tdPrice(Number(p.customsSurcharge))],
    [td(T.extraWork), td(T.time, { alignment: 'center' }), tdPrice(T.byEffort)],
    [td(T.laborWork), td(T.perHour, { alignment: 'center' }), tdPrice(Number(p.hourlyRate))]
  )
  // Optional by-effort services (toggled per offer)
  const sv = payload.services || {}
  if (sv.grading) fulfillmentRows.push([td(T.serviceGrading), td(T.time, { alignment: 'center' }), tdPrice(T.byEffort)])
  if (sv.kitting) fulfillmentRows.push([td(T.serviceKitting), td(T.time, { alignment: 'center' }), tdPrice(T.byEffort)])
  if (sv.preFba) fulfillmentRows.push([td(T.servicePreFba), td(T.time, { alignment: 'center' }), tdPrice(T.byEffort)])
  fulfillmentRows.push(
    [td(T.palletMove), td(T.piece, { alignment: 'center' }), tdPrice(Number(p.palletMovePrice))],
    [td(T.returnBase), td(T.piece, { alignment: 'center' }), tdPrice(Number(p.returnBaseprice))],
    [td(T.returnPerItem), td(T.piece, { alignment: 'center' }), tdPrice(Number(p.returnPickprice))]
  )

  // Optional volume-price matrix (order-volume staffel) — fully free-form
  // tier columns + price rows, entered in the modal. Rows/cells that don't
  // parse as a number are printed as-is (e.g. "auf Anfrage").
  const vm = payload?.volumeMatrix
  const volumeMatrixTierLabels: string[] = Array.isArray(vm?.tiers) ? vm.tiers.map((t: any) => String(t?.label || '').trim()) : []
  const volumeMatrixRows: any[][] = (Array.isArray(vm?.rows) ? vm.rows : [])
    .filter((r: any) => String(r?.label || '').trim() !== '' || (Array.isArray(r?.prices) && r.prices.some((v: any) => String(v ?? '').trim() !== '')))
    .map((r: any) => {
      const prices = Array.isArray(r?.prices) ? r.prices : []
      return [
        td(String(r?.label || '').trim() || '—'),
        ...volumeMatrixTierLabels.map((_l: string, i: number) => {
          const raw = String(prices[i] ?? '').trim()
          const numeric = Number(raw.replace(/€/g, '').replace(/\s/g, '').replace(',', '.'))
          return raw !== '' && Number.isFinite(numeric)
            ? tdPrice(numeric)
            : td(raw || '—', { alignment: 'right', italics: raw !== '' })
        })
      ]
    })

  // LAGERUNG rows
  const storageRows: any[][] = [
    [td(T.shelfSmall), td('45 × 60 × 40 cm', { alignment: 'center' }), tdPrice(Number(p.shelfSmallPrice))],
    [td(T.shelfLarge), td('90 × 60 × 40 cm', { alignment: 'center' }), tdPrice(Number(p.shelfLargePrice))],
    [td(T.palletSpace), td('120 × 80 × 150 cm', { alignment: 'center' }), tdPrice(Number(p.palletPrice))]
  ]
  if (Number(p.volumeM3Price) > 0) storageRows.push([td(T.volumeStorage), td(T.perM3, { alignment: 'center' }), tdPrice(Number(p.volumeM3Price))])

  // VERPACKUNG rows
  const cartons = p.cartons || {}
  const cartonRow = (label: string, vol: string, key: string) => [
    td(label),
    td(vol, { alignment: 'center' }),
    tdPrice(Number(cartons[key]?.price)),
    tdPrice(Number(cartons[key]?.paper))
  ]
  const packagingRows: any[][] = [
    cartonRow('S', '< 6 l', 's'),
    cartonRow('M', '< 15 l', 'm'),
    cartonRow('L', '< 30 l', 'l'),
    cartonRow('XL', '< 60 l', 'xl'),
    [td('XXL'), td('> 60 l', { alignment: 'center' }), td(T.individually, { alignment: 'right', italics: true, color: BRAND.grayText }), td(T.individually, { alignment: 'right', italics: true, color: BRAND.grayText })],
    [td(T.einwegpalette), td('—', { alignment: 'center' }), tdPrice(Number(p.einwegpalettePrice)), td('—', { alignment: 'right' })]
  ]

  // WARENEINGANG body — three modes: 'included' (classic 2-row table with per-row
  // pricing), 'hourly' (billed by actual time at an hourly rate), 'custom' (free text
  // replacing the section content). Absent/unknown config falls back to the classic look.
  const inboundPriceCell = (kind: string, fixed: any) => {
    if (kind === 'free') return td(T.free, { alignment: 'right', italics: true })
    if (kind === 'byEffort') return td(T.byEffort, { alignment: 'right', italics: true })
    if (kind === 'fixed') return tdPrice(Number(fixed))
    return td(T.included, { alignment: 'right', italics: true })
  }
  let inboundBody: any
  // Whether the Wareneingang cells carry a `*` mark (by-effort / by-time) — if
  // so the hourly-rate footnote is still needed when Fulfillment is switched off.
  let inboundHasEffortMark = false
  if (inb.mode === 'custom' && String(inb.customText || '').trim() !== '') {
    inboundBody = { text: String(inb.customText).trim(), fontSize: 9.5, margin: [0, 2, 0, 0] }
  } else if (inb.mode === 'hourly') {
    // No rate here on purpose — the hourly rate is stated by the Arbeitszeit
    // row + the * footnote of the Fulfillment & Retoure section.
    inboundHasEffortMark = true
    inboundBody = priceTable(
      ['*', 200],
      [th(T.service), th(T.price, 'right')],
      [[td(T.inboundByTime), td(T.inboundByTimePrice, { alignment: 'right', italics: true })]]
    )
  } else {
    inboundHasEffortMark = (inb.includedPricing || 'included') === 'byEffort' || (inb.extraPricing || 'byEffort') === 'byEffort'
    inboundBody = priceTable(
      ['*', 150],
      [th(T.service), th(T.price, 'right')],
      [
        [td(T.inboundIncluded), inboundPriceCell(inb.includedPricing || 'included', inb.includedFixedPrice)],
        [td(T.inboundExtra), inboundPriceCell(inb.extraPricing || 'byEffort', inb.extraFixedPrice)]
      ]
    )
  }

  // VERSAND rows — omitted entirely when the customer uses their own freight
  // contract (payload.pricing.showShippingFees === false); a short note is
  // printed instead (with the freight service name, when given).
  const showShipping = p.showShippingFees !== false
  const tiers = p.dhlTiers || {}
  const shippingRows: any[][] = [
    [td(T.tierKg1), tdPrice(Number(tiers.kg1))],
    [td(T.tierKg2), tdPrice(Number(tiers.kg2))],
    [td(T.tierKg5), tdPrice(Number(tiers.kg5))],
    [td(T.tierKg10), tdPrice(Number(tiers.kg10))],
    [td(T.tierKg20), tdPrice(Number(tiers.kg20))],
    [td(T.tierKg30), tdPrice(Number(tiers.kg30))],
    [td(T.spedition), td(p.speditionNote || T.speditionDefault, { alignment: 'right', italics: true })]
  ]
  const shippingCarrier = String(p.shippingCarrier || '').trim()
  const shippingPartnerNote = fmt(T.shippingPartnerNote, {
    carrierPart: shippingCarrier ? fmt(T.shippingPartnerCarrierPart, { carrier: shippingCarrier }) : ''
  })

  // Fixed head: address + meta card, intro, optional website hero.
  const head: any[] = [
    // Address + meta card
    {
      columns: [
        {
          width: '*',
          stack: [
            { text: c.company || '', bold: true, fontSize: 12, color: BRAND.navy },
            { text: c.contactName || '', margin: [0, 3, 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] }
          ]
        },
        // Customer logo (optional) — centered between address and meta card
        ...(customerLogo
          ? [{ width: 130, image: customerLogo, fit: [118, 54], alignment: 'center', margin: [0, 4, 6, 0] }]
          : []),
        {
          width: 200,
          table: {
            widths: [86, '*'],
            body: [
              kv(T.date, formatDateLocal(dateIso, language)),
              kv(T.validUntil, formatDateLocal(validUntil, language)),
              kv(T.plannedStart, formatDateLocal(a.plannedStart, language))
            ]
          },
          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] },

    // WEBSITE HERO (optional) — the customer's first page in a browser-style
    // frame: navy title bar with traffic-light dots + domain, banner image below
    // (server-cropped to 1200×500, so full content width ≈ 214 pt tall).
    ...(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
          }
        }]
      : [])
  ]

  // ===== Content sections — an ORDERED, KEYED list so that (a) the user can
  // switch individual sections off per offer (payload.sections; all on when
  // the map is missing) and (b) free text blocks can be anchored right after
  // any of them. Keys must match QUOTE_SECTION_KEYS / QUOTE_TEXT_ANCHORS in
  // app/composables/useOfferQuote.ts.
  const sections: Array<{ key: string; node: any }> = []

  // ANNAHMEN
  if (on('assumptions')) {
    sections.push({
      key: 'assumptions',
      node: {
        unbreakable: true,
        stack: [
          sectionTitle(T.assumptions),
          {
            table: {
              widths: [150, '*', 150, '*'],
              body: [
                [...kv(T.businessModel, a.businessModel), ...kv(T.plannedStart, formatDateLocal(a.plannedStart, language))],
                [...kv(T.shopSystem, a.shopSystem), ...kv(T.avgPicks, a.avgPicksPerOrder)],
                [...kv(T.ordersPerMonth, a.ordersPerMonth), ...kv(T.skuCount, a.skuCount)],
                [...kv(T.storageNeed, storageSummary), ...kv(T.onboarding, T.free)]
              ]
            },
            layout: {
              hLineWidth: (i: number) => (i > 0 && i < 4 ? 0.5 : 0),
              vLineWidth: () => 0,
              hLineColor: () => BRAND.grayLine,
              paddingLeft: () => 4,
              paddingRight: () => 8,
              paddingTop: () => 4,
              paddingBottom: () => 4
            }
          }
        ]
      }
    })
  }

  // FULFILLMENT & RETOURE — on page 1, directly below the assumptions
  if (on('fulfillment')) {
    sections.push({
      key: 'fulfillment',
      node: {
        unbreakable: true,
        stack: [
          sectionTitle(T.fulfillmentReturns),
          priceTable(['*', 70, 110], [th(T.service), th(T.dimension, 'center'), th(T.price, 'right')], fulfillmentRows),
          { text: fmt(T.hourlyNote, { rate: euro(Number(p.hourlyRate)) }), fontSize: 7.5, color: BRAND.grayText, margin: [0, 5, 0, 0] }
        ]
      }
    })
  }

  // PREISSTAFFEL NACH BESTELLVOLUMEN (optional, off by default)
  if (vm?.enabled && volumeMatrixTierLabels.length && volumeMatrixRows.length) {
    sections.push({
      key: 'volumeMatrix',
      node: {
        unbreakable: true,
        stack: [
          sectionTitle(T.volumeMatrixTitle),
          priceTable(
            ['*', ...volumeMatrixTierLabels.map(() => 62)],
            [th(T.volumeMatrixHeader), ...volumeMatrixTierLabels.map((l: string) => th(l || '—', 'right'))],
            volumeMatrixRows
          )
        ]
      }
    })
  }

  // WARENEINGANG (configurable via payload.inbound; defaults reproduce the
  // classic section). The page-2 break is applied further below, after filtering.
  if (on('inbound')) {
    sections.push({
      key: 'inbound',
      node: {
        unbreakable: true,
        stack: [
          sectionTitle(T.inbound),
          inboundBody
        ]
      }
    })
  }

  // LAGERUNG
  if (on('storage')) {
    sections.push({
      key: 'storage',
      node: {
        unbreakable: true,
        stack: [
          sectionTitle(T.storageTitle),
          priceTable(['*', 170, 110], [th(T.storageType), th(T.storageSize, 'center'), th(T.monthlyCost, 'right')], storageRows)
        ]
      }
    })
  }

  // MONATLICHE KOSTEN
  if (on('monthly')) {
    sections.push({
      key: 'monthly',
      node: {
        unbreakable: true,
        stack: [
          sectionTitle(T.monthlyCosts),
          priceTable(
            ['*', 110],
            [th(T.service), th(T.price, 'right')],
            [[
              { stack: [td(T.serviceFee, { bold: true }), td(T.serviceFeeIncl, { fontSize: 7.5, color: BRAND.grayText, margin: [0, 2, 0, 0] })] },
              tdPrice(Number(p.monthlyFee))
            ]]
          )
        ]
      }
    })
  }

  // VERPACKUNG
  if (on('packaging')) {
    sections.push({
      key: 'packaging',
      node: {
        unbreakable: true,
        stack: [
          sectionTitle(T.packaging),
          priceTable(
            ['*', 80, 110, 120],
            [th(T.cartonSize), th(T.volume, 'center'), th(T.cartonPrice, 'right'), th(T.paperPrice, 'right')],
            packagingRows
          ),
          { text: T.packagingNote, fontSize: 7.5, color: BRAND.grayText, margin: [0, 5, 0, 0] }
        ]
      }
    })
  }

  // VERSAND
  if (on('shipping')) {
    sections.push({
      key: 'shipping',
      node: {
        unbreakable: true,
        stack: showShipping
          ? [
              sectionTitle(T.shippingTitle),
              priceTable(['*', 130], [th(T.shippingMethod), th(T.price, 'right')], shippingRows)
            ]
          : [
              sectionTitle(T.shippingPartnerTitle),
              { text: shippingPartnerNote, fontSize: 9.5, margin: [0, 2, 0, 0] }
            ]
      }
    })
  }

  // Optional free-form extra positions (description + dimension + price-or-text)
  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(',', '.'))
      const priceCell = raw !== '' && Number.isFinite(numeric)
        ? tdPrice(numeric)
        : td(raw || '—', { alignment: 'right', italics: raw !== '' })
      return [
        td(String(r.description).trim()),
        td(String(r?.dimension || '').trim() || '—', { alignment: 'center' }),
        priceCell
      ]
    })
    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)
        ]
      }
    })
  }

  // Optional forecast section (skipped when every forecast-relevant section is
  // off — a table with only a 0,00 € total row would just confuse).
  if (payload?.forecast?.includeInPdf && Array.isArray(payload?.forecast?.lines) && payload.forecast.lines.length) {
    const fc = payload.forecast
    const fcLabel: Record<string, string> = {
      fulfillment: fmt(T.fcFulfillment, { orders: a.ordersPerMonth }),
      returns: fmt(T.fcReturns, { rate: a.returnRatePct }),
      storage: T.fcStorage,
      serviceFee: T.fcServiceFee,
      packaging: fmt(T.fcPackaging, { carton: String(a.typicalCarton || 'M').toUpperCase() }),
      shipping: T.fcShipping
    }
    const fcRows: any[][] = fc.lines.map((line: any) => [td(fcLabel[line.key] || line.key), tdPrice(Number(line.monthly))])
    fcRows.push([
      { text: T.fcTotal, bold: true, fontSize: 10, color: BRAND.white, fillColor: BRAND.navy },
      { text: euro(Number(fc.totalMonthly)), bold: true, fontSize: 10, color: BRAND.orange, alignment: 'right', fillColor: BRAND.navy }
    ])
    // The per-order price is fulfillment + packaging + shipping — meaningless
    // without the Fulfillment section.
    if (on('fulfillment')) {
      fcRows.push([td(T.fcFulfillmentPerOrder, { bold: true }), tdPrice(Number(fc.fulfillmentPerOrder ?? fc.perOrder))])
    }
    // Disclaimer variant: the DHL example sentence only when shipping fees are
    // printed; the "own freight contract" sentence only when that note printed.
    const fcDisclaimer = showShipping && on('shipping')
      ? T.fcDisclaimer
      : (!showShipping ? T.fcDisclaimerNoShipping : T.fcDisclaimerBase)
    sections.push({
      key: 'forecast',
      node: {
        unbreakable: true,
        stack: [
          sectionTitle(T.forecastTitle),
          priceTable(['*', 130], [th(T.service), th(T.price, 'right')], fcRows),
          { text: fcDisclaimer, fontSize: 7.5, italics: true, color: BRAND.grayText, margin: [0, 5, 0, 0] }
        ]
      }
    })
  }

  // Page break: the price sections (Wareneingang, …) start on page 2 because
  // the Fulfillment table fills page 1. With the website hero on page 1 the
  // Fulfillment table already flows to page 2 naturally, so forcing another
  // break would leave page 2 half-empty — let it flow instead. Applied to the
  // FIRST rendered page-2 section (so it survives Wareneingang being switched
  // off) and only when Fulfillment actually printed — otherwise page 1 would
  // hold little more than the Annahmen grid and stay two-thirds blank.
  if (!heroShot && sections.some((s) => s.key === 'fulfillment')) {
    const first = sections.find((s) => PAGE2_KEYS.includes(s.key))
    if (first) first.node.pageBreak = 'before'
  }

  // Free text blocks (optional heading + text), each printed right after its
  // anchor section — or at the end (before the Hinweise) when that anchor
  // isn't rendered (switched off / disabled / unknown). Blocks sharing an
  // anchor keep their modal order. Long texts must be allowed to flow:
  // pdfmake silently drops whatever overflows an `unbreakable` block.
  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') || []))

  // Notes + closing. Footnotes only when the section they explain printed:
  // the hourly-rate note backs the `*` marks of the Fulfillment table (or of
  // a by-effort Wareneingang), the insurance note the `*`/`**`/`***` of the
  // DHL tier rows.
  const notes: any[] = [
    { text: T.noteVat },
    { text: fmt(T.noteValidity, { days: validDays }) }
  ]
  if (on('fulfillment') || (on('inbound') && inboundHasEffortMark)) {
    notes.push({ text: fmt(T.hourlyNote, { rate: euro(Number(p.hourlyRate)) }).replace(/^\*/, '') })
  }
  if (on('shipping') && showShipping) notes.push({ text: T.noteInsurance })
  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]
      }
    ]
  }

  const content: any[] = [...head, ...body, notesNode]

  return {
    pageSize: 'A4',
    pageMargins: [40, 106, 40, 86],
    info: {
      title: `${T.docTitle} – ${c.company || ''}`,
      author: 'LogYou GmbH',
      subject: T.docTitle
    },
    header: buildBrandHeader(T.docTitle, `logyou.de  ·  ${T.dateWord} ${formatDateLocal(dateIso, language)}`, logos.dark),
    footer: buildBrandFooter(T.page, T.of),
    content,
    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 generateQuotePdf = async (payload: any): Promise<{ buffer: Buffer; filename: string }> => {
  // Import pdfmake with virtual file system for server-side use
  // (defensive vfs extraction copied from generate-fee-report.post.ts — the
  // export shape varies by pdfmake version/bundler, do not simplify).
  const pdfMake = await import('pdfmake/build/pdfmake.js')
  const pdfFonts = await import('pdfmake/build/vfs_fonts.js')

  // @ts-ignore - Set up virtual file system
  const pdfMakeInstance = pdfMake.default || pdfMake

  let vfs = 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) {
    const defaultKeys = Object.keys((pdfFonts as any).default)
    if (defaultKeys.some((key: string) => key.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()
  // Customer logo / website screenshot → guaranteed PNG/JPEG (or dropped):
  // pdfmake throws "Unknown image format" on anything else and that used to
  // fail the whole preview for a lead with e.g. a WebP avatar.
  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 = buildQuoteDocDefinition(payload, logos)
  const filename = sanitizeQuoteFilename(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 }
}
