// A17 Export — Management-Report / PDF (execution-prompt §19.1/§19.2,
// Wertstrom P7, KAR-878/KAR-986). Pure `ExportConfig` assembly — the actual
// PDF rendering is the EXISTING `exportToPdf` (lib/export/export-service.ts,
// jsPDF, already used by the Visit-/Projekt-Export). No new PDF stack, per
// Brief. This module only builds the `sections` array from already-computed
// engine results — "UI/Export rendert, rechnet nicht selbst" gilt auch hier.
//
// §19.2-Abdeckung (Datenbasis-Realitätscheck, dokumentierte Lücken statt
// Fabrikation):
//   ✓ project context / supplier / customer demand / working-time model /
//     takt time / process table / inventory table / material flow /
//     information flow / timeline / bottleneck analysis / quality /
//     current-state KPIs / scenario comparison (wenn aktiv) / Kaizen
//     opportunities / measures / assumptions / data quality notes.
//     (C22-Fix, P7 fix-round: `supplierName` is now its own "Lieferant"-
//     Zeile in "Projekt-Kontext" — previously only reachable via the
//     pre-collapsed `projectLabel` = `project_code ?? supplier_name`, which
//     SILENTLY HID the guaranteed-present `supplier_name` whenever the
//     optional `project_code` was also set, contradicting this file's own
//     former claim that supplier_name "is surfaced".)
//   ✗ "product" / "value stream scope" as DISTINCT fields: `projects` has no
//     product/value-stream-scope column (lib/api/schemas.ts) — listed as an
//     explicit Nicht-Scope note in the Annahmen-Abschnitt rather than
//     fabricated from title/description.
//   ✗ "value stream visualization" embedded INLINE in the PDF: the shared
//     `ExportSection` shape (lib/export/export-service.ts, used by every
//     other PDF export in this repo) has no image-section type, and adding
//     one is a cross-cutting change to shared infra beyond this module's
//     scope — the visual map is its own export (vsm-export-svg.ts, "Visuelle
//     Karte"), not inlined here. Declared Nicht-Scope, see PR description.
//     (C16-Fix, P7 fix-round: this gap is now an explicit sentence in the
//     "Annahmen"-Section, same treatment as the Produkt/Scope gap above —
//     previously only documented in this source comment and PRODUCT_SPEC.md,
//     both invisible to a PDF reader.)

import type { VsmConnection, VsmNode } from '@/lib/vsm-types'
import {
  computeBottleneckV2,
  computeManagementAnalysis,
  computeTimelineLadder,
  type ManagementAnalysisContext,
  type NodeCapacityOverrides,
  type ScenarioEffectRow,
  type ShiftModelInput,
} from '@/lib/vsm-engine'
import type { ExportConfig, ExportSection } from '@/lib/export/export-service'
import { formatDurationDe } from './vsm-duration-format'
import { interpolate } from '@/lib/i18n/i18n-context'

// i18n (P8.2c, KAR-987): module-level, no hook access — the calling
// component (vsm-export-dialog.tsx) resolves+passes `t` from its own
// useI18n(). Trailing-defaulted so every existing (pre-i18n) caller/test
// keeps compiling and rendering the exact same German text unchanged; only
// a real caller that explicitly threads `t` through gets translated output.
type TFn = (key: string, fallback?: string) => string
const defaultT: TFn = (key, fallback) => fallback ?? key

// Mirrors `vsm-scenario-compare-view.tsx`'s own `KPI_LABEL_KEYS` (P8.2c) —
// deliberately duplicated rather than imported: that file is a React view
// component, this one is a pure ExportConfig-builder with no JSX/React
// dependency, and the map is a small closed Record over the same stable
// `KpiDeltaKey` enum (`vsm-scenario-compare.ts`) either way — plus a THIRD
// copy in vsm-presentation-mode.tsx (P8.2c review fix, K5: the Ist/Soll-
// Deltas row labels there were previously unwrapped raw text). All three
// copies must stay in sync if a 6th KPI is ever added — a minor, non-urgent
// consolidation opportunity, not fixed here (out of this pass's scope).
const KPI_LABEL_KEYS: Record<string, string> = {
  bottleneckUtilization: 'wertstrom.compare.kpi.bottleneckUtilization',
  leadTime: 'wertstrom.compare.kpi.leadTime',
  vaTime: 'wertstrom.compare.kpi.vaTime',
  pce: 'wertstrom.compare.kpi.pce',
  inventoryCoverage: 'wertstrom.compare.kpi.inventoryCoverage',
}

export interface WertstromReportInput {
  title: string
  description?: string | null
  /** project_code ?? supplier_name of the linked project, or null — same
   * resolved label vsm-editor.tsx already receives as `projectLabel`. */
  projectLabel?: string | null
  /** C22-Fix (P7 fix-round): the linked project's `supplier_name` — a
   * REQUIRED field on `projects` (lib/api/schemas.ts, unlike the optional
   * `project_code`), rendered as its own "Lieferant" row, distinct from
   * `projectLabel`. `null`/absent renders "—", never silently omitted (was
   * previously only reachable via `projectLabel`'s nullish-coalescing
   * fallback, which hid it whenever `project_code` was also set). */
  supplierName?: string | null
  nodes: VsmNode[]
  connections: VsmConnection[]
  taktTimeSec: number | null
  demandUnitsPerDay?: number
  shiftModel?: ShiftModelInput
  capacityOverridesByNodeId?: Record<string, NodeCapacityOverrides>
  scenarioComparison?: ManagementAnalysisContext['scenarioComparison']
  generatedAtLabel: string
  author?: string
}

// i18n (P8.2c) — Korrektur: dieses Modul hatte SCHON VOR P8.2c eigene,
// KÜRZERE Node-Typ-Bezeichnungen ('Prozess' statt 'Prozessschritt',
// 'Bestand' statt 'Lager/Puffer', 'Zeitwertlinie' statt 'Zeitwert') — eine
// bewusste, bereits bestehende Abweichung von vsm-config.ts's NODE_CONFIG
// (Editor-Palette/-Badges), nicht von P8.2c eingeführt. Erst mit einem
// eigenen `NODE_TYPE_LABEL_KEYS` unten (statt fälschlich vsm-config.ts's
// gleichnamige Keys wiederzuverwenden) bleibt dieser vorbestehende
// Wortlaut-Unterschied erhalten, statt zwei unterschiedliche deutsche
// Fallback-Werte demselben i18n-Key zuzuweisen (hätte den Key-Paritäts-/
// Fallback-Konsistenz-Test verletzt).
const NODE_TYPE_LABEL: Record<VsmNode['type'], string> = {
  process: 'Prozess',
  machine: 'Maschine',
  inventory: 'Bestand',
  transport: 'Transport',
  customer: 'Kunde',
  supplier: 'Lieferant',
  timevalue: 'Zeitwertlinie',
}
const PDF_NODE_TYPE_LABEL_KEYS: Record<VsmNode['type'], string> = {
  process: 'wertstrom.export.pdf.nodeType.process',
  machine: 'wertstrom.export.pdf.nodeType.machine',
  inventory: 'wertstrom.export.pdf.nodeType.inventory',
  transport: 'wertstrom.export.pdf.nodeType.transport',
  customer: 'wertstrom.export.pdf.nodeType.customer',
  supplier: 'wertstrom.export.pdf.nodeType.supplier',
  timevalue: 'wertstrom.export.pdf.nodeType.timevalue',
}

function fmtSec(value: number | null | undefined): string {
  return value == null ? '—' : formatDurationDe(value)
}

// C20-Fix (Wertstrom P7 fix-round): German decimal comma — was plain JS
// number-to-string ("82.5 %"), violating the "deutsches Zahlenformat"
// doctrine (same fix as vsm-presentation-mode.tsx's formatPercent).
function fmtPct(value: number | null | undefined): string {
  return value == null ? '—' : `${new Intl.NumberFormat('de-DE', { maximumFractionDigits: 1 }).format(value)} %`
}

// C20-Fix: same German-format doctrine for the Soll-KPIs/Szenario-Vergleich
// table below, which built its own `String(Math.round(value*10)/10)` cells
// (a second un-Germanized site in this same file, same defect class as fmtPct).
function fmtNum(value: number | null | undefined): string {
  return value == null ? '—' : new Intl.NumberFormat('de-DE', { maximumFractionDigits: 1 }).format(value)
}

export function buildWertstromManagementReportConfig(input: WertstromReportInput, t: TFn = defaultT): ExportConfig {
  const { nodes, connections } = input
  const bottleneck = computeBottleneckV2(nodes, input.taktTimeSec, input.capacityOverridesByNodeId)
  const ladder = computeTimelineLadder(nodes, input.demandUnitsPerDay, input.shiftModel)
  const analysis = computeManagementAnalysis(nodes, connections, {
    taktTimeSec: input.taktTimeSec,
    demandUnitsPerDay: input.demandUnitsPerDay,
    shiftModel: input.shiftModel,
    capacityOverridesByNodeId: input.capacityOverridesByNodeId,
    scenarioComparison: input.scenarioComparison,
  })
  const statementText = (category: string) => analysis.statements.filter((s) => s.category === category).map((s) => s.text)

  const sections: ExportSection[] = []

  const hint = t('wertstrom.export.pdf.colHint', 'Hinweis')
  const dash = '—'

  sections.push({
    title: t('wertstrom.export.pdf.sectionProjectContext', 'Projekt-Kontext'),
    items: [
      { label: t('wertstrom.export.pdf.fieldValueStream', 'Wertstrom'), value: input.title },
      { label: t('wertstrom.export.pdf.fieldDescription', 'Beschreibung'), value: input.description ?? dash },
      { label: t('wertstrom.export.pdf.fieldLinkedProject', 'Verknüpftes Projekt'), value: input.projectLabel ?? t('wertstrom.export.pdf.noProjectLinked', '— (kein Projekt verknüpft)') },
      // C22-Fix: own row, distinct from "Verknüpftes Projekt" — see
      // `supplierName`'s doc comment above for why this used to be silently
      // masked whenever a project_code was also set.
      { label: t('wertstrom.export.pdf.fieldSupplier', 'Lieferant'), value: input.supplierName ?? dash },
      { label: t('wertstrom.export.pdf.fieldGeneratedAt', 'Erzeugt am'), value: input.generatedAtLabel },
    ],
  })

  sections.push({
    title: t('wertstrom.export.pdf.sectionCustomerDemand', 'Kundenbedarf, Arbeitszeitmodell & Kundentakt'),
    items: [
      {
        label: t('wertstrom.export.pdf.fieldCustomerDemand', 'Kundenbedarf'),
        value: input.demandUnitsPerDay != null ? interpolate(t('wertstrom.export.pdf.unitsPerDay', '{count} Stück/Tag'), { count: String(input.demandUnitsPerDay) }) : t('wertstrom.export.notAssessable', 'nicht bewertbar'),
      },
      {
        label: t('wertstrom.export.pdf.fieldWorkingTimeModel', 'Arbeitszeitmodell'),
        value: input.shiftModel
          ? interpolate(t('wertstrom.export.pdf.workingTimeModelValue', '{hours} Std./Schicht · {shifts} Schicht(en)/Tag · {breakMin} Min. Pause/Schicht'), {
              hours: String(input.shiftModel.hoursPerShift),
              shifts: String(input.shiftModel.shiftsPerDay),
              breakMin: String(input.shiftModel.breakMinPerShift),
            })
          : t('wertstrom.export.pdf.notConfigured', 'nicht hinterlegt'),
      },
      { label: t('wertstrom.export.kpiTakt', 'Kundentakt'), value: input.taktTimeSec != null ? `${Math.round(input.taktTimeSec)} s` : t('wertstrom.export.notAssessable', 'nicht bewertbar') },
    ],
  })

  const processNodes = nodes.filter((n) => n.type === 'process' || n.type === 'machine')
  sections.push({
    title: t('wertstrom.export.pdf.sectionProcessTable', 'Prozess-Tabelle'),
    table:
      processNodes.length > 0
        ? {
            headers: [
              t('wertstrom.export.pdf.colName', 'Name'),
              t('wertstrom.export.pdf.colType', 'Typ'),
              t('wertstrom.export.pdf.colCycleTimeSec', 'Zykluszeit (s)'),
              t('wertstrom.export.pdf.colNumWorkers', 'Mitarbeiter'),
              t('wertstrom.export.pdf.colVaClass', 'VA-Klasse'),
              t('wertstrom.export.pdf.colProvenance', 'Herkunft'),
            ],
            rows: processNodes.map((n) => [
              n.name || t('wertstrom.export.unnamed', '(unbenannt)'),
              t(PDF_NODE_TYPE_LABEL_KEYS[n.type], NODE_TYPE_LABEL[n.type]),
              n.cycleTimeSec != null ? String(n.cycleTimeSec) : dash,
              n.numWorkers != null ? String(n.numWorkers) : dash,
              n.vaClass ?? (n.isValueAdded ? t('wertstrom.export.pdf.vaDerived', 'va (abgeleitet)') : dash),
              n.provenance ?? dash,
            ]),
          }
        : { headers: [hint], rows: [[t('wertstrom.export.pdf.hintNoProcessNodes', 'Keine Prozess-/Maschinen-Nodes erfasst.')]] },
  })

  const inventoryNodes = nodes.filter((n) => n.type === 'inventory')
  sections.push({
    title: t('wertstrom.export.pdf.sectionInventoryTable', 'Bestands-Tabelle'),
    table:
      inventoryNodes.length > 0
        ? {
            headers: [t('wertstrom.export.pdf.colName', 'Name'), t('wertstrom.export.pdf.colQuantityPcs', 'Menge (Stk.)'), t('wertstrom.export.pdf.colStorageKind', 'Lager-Art'), t('wertstrom.export.pdf.colMaxCapacity', 'Max. Kapazität')],
            rows: inventoryNodes.map((n) => [n.name || t('wertstrom.export.unnamed', '(unbenannt)'), n.quantity != null ? String(n.quantity) : dash, n.inventoryKind ?? dash, n.inventoryMaxQuantity != null ? String(n.inventoryMaxQuantity) : dash]),
          }
        : { headers: [hint], rows: [[t('wertstrom.export.pdf.hintNoInventoryNodes', 'Keine Bestands-/Puffer-Nodes erfasst.')]] },
  })

  const nodeNameById = new Map(nodes.map((n) => [n.id, n.name || t('wertstrom.export.unnamed', '(unbenannt)')]))
  const materialFlow = connections.filter((c) => (c.kind ?? 'materialFlow') === 'materialFlow')
  const informationFlow = connections.filter((c) => c.kind === 'information')
  sections.push({
    title: t('wertstrom.export.pdf.sectionMaterialFlow', 'Materialfluss'),
    table:
      materialFlow.length > 0
        ? {
            headers: [t('wertstrom.export.pdf.colFrom', 'Von'), t('wertstrom.export.pdf.colTo', 'Nach'), t('wertstrom.export.pdf.colLabel', 'Label'), t('wertstrom.export.pdf.colTransportTimeSec', 'Transportzeit (s)'), t('wertstrom.export.pdf.colBatchSize', 'Losgröße')],
            rows: materialFlow.map((c) => [nodeNameById.get(c.fromNodeId) ?? c.fromNodeId, nodeNameById.get(c.toNodeId) ?? c.toNodeId, c.label ?? dash, c.transportTimeSec != null ? String(c.transportTimeSec) : dash, c.batchSize != null ? String(c.batchSize) : dash]),
          }
        : { headers: [hint], rows: [[t('wertstrom.export.pdf.hintNoMaterialFlow', 'Keine Materialfluss-Verbindungen erfasst.')]] },
  })
  sections.push({
    title: t('wertstrom.export.pdf.sectionInformationFlow', 'Informationsfluss'),
    table:
      informationFlow.length > 0
        ? {
            headers: [t('wertstrom.export.pdf.colFrom', 'Von'), t('wertstrom.export.pdf.colTo', 'Nach'), t('wertstrom.export.pdf.colLabel', 'Label'), t('wertstrom.export.pdf.colFrequency', 'Frequenz')],
            rows: informationFlow.map((c) => [nodeNameById.get(c.fromNodeId) ?? c.fromNodeId, nodeNameById.get(c.toNodeId) ?? c.toNodeId, c.label ?? dash, c.frequency ?? dash]),
          }
        : { headers: [hint], rows: [[t('wertstrom.export.pdf.hintNoInformationFlow', 'Keine Informationsfluss-Verbindungen erfasst.')]] },
  })

  sections.push({
    // P8.2a (Baustein 1 "isVaNode-unknown-Alignment"): "Ungeklärt" added to
    // both the title and its own item row below — vaClass='unknown' time
    // used to be silently counted inside "Nicht wertschöpfende Prozesszeit"
    // (the engine's old binary isVaNode); now it is its own, honestly
    // labeled line, same "n von m Nodes gemessen"/"nicht bewertbar" fallback
    // convention every other row here already uses.
    title: t('wertstrom.export.pdf.sectionTimeline', 'Timeline (Wertschöpfend / Nicht wertschöpfend / Ungeklärt / Warten / Bestand / Transport)'),
    items: [
      { label: t('wertstrom.export.pdf.fieldVaTime', 'Wertschöpfende Zeit'), value: ladder.vaTimeSec.measuredSec != null ? fmtSec(ladder.vaTimeSec.measuredSec) : t('wertstrom.export.notAssessable', 'nicht bewertbar') },
      { label: t('wertstrom.export.pdf.fieldNonVaProcessTime', 'Nicht wertschöpfende Prozesszeit'), value: ladder.nonVaProcessTimeSec.measuredSec != null ? fmtSec(ladder.nonVaProcessTimeSec.measuredSec) : t('wertstrom.export.notAssessable', 'nicht bewertbar') },
      { label: t('wertstrom.export.pdf.fieldUnknownProcessTime', 'Ungeklärte Prozesszeit'), value: ladder.unknownTimeSec.measuredSec != null ? fmtSec(ladder.unknownTimeSec.measuredSec) : t('wertstrom.export.notAssessable', 'nicht bewertbar') },
      { label: t('wertstrom.export.pdf.fieldWaitingTime', 'Wartezeit'), value: ladder.waitingTimeSec.measuredSec != null ? fmtSec(ladder.waitingTimeSec.measuredSec) : t('wertstrom.export.notAssessable', 'nicht bewertbar') },
      { label: t('wertstrom.export.pdf.fieldInventoryTime', 'Bestandszeit'), value: ladder.inventoryTime.seconds != null ? fmtSec(ladder.inventoryTime.seconds) : t('wertstrom.export.notAssessable', 'nicht bewertbar') },
      { label: t('wertstrom.export.pdf.fieldTransportTime', 'Transportzeit'), value: ladder.transportTimeSec.measuredSec != null ? fmtSec(ladder.transportTimeSec.measuredSec) : t('wertstrom.export.notAssessable', 'nicht bewertbar') },
      { label: t('wertstrom.export.pdf.fieldTotalLeadTime', 'Gesamte Durchlaufzeit'), value: `${fmtSec(ladder.totalLeadTimeSec)}${ladder.totalLeadTimeIncludesInventory ? '' : ` ${t('wertstrom.export.pdf.withoutInventoryTimeSuffix', '(ohne Bestandszeit)')}`}` },
      { label: t('wertstrom.export.pdf.fieldPce', 'Wertschöpfungsanteil (PCE)'), value: fmtPct(ladder.processCycleEfficiencyPct) },
    ],
  })

  // i18n-Grenze (P8.2c, KAR-987): `bottleneck.reasonDe`,
  // `statementText(...)` (`.text`) UND — unten in der Annahmen-Section —
  // `bottleneck.explain.exclusions`/`ladder.explain.exclusions` sind
  // Engine-komponierter Fließtext aus `computeManagementAnalysis`/
  // `computeBottleneckV2`/`computeTimelineLadder` (`lib/vsm-engine`, das
  // ganze `MetricExplain`-System: `formula`/`dataBasis`/`exclusions` sind in
  // JEDER lib/vsm-engine-Funktion hartkodierte deutsche Sätze) — dieselbe
  // bereits deklarierte Grenze wie in vsm-management-analysis-view.tsx,
  // vsm-timeline-ladder.tsx und vsm-quick-start-wizard.tsx (dort ausführlich
  // begründet: Grenze "Engine emittiert stabile Codes, UI übersetzt" gilt
  // für einzelne Labels, nicht für vollständig zusammengesetzte Analyse-
  // Sätze — deren Rework wäre eine Kernänderung an der ganzen Engine,
  // außerhalb des Briefs). Bleibt Deutsch.
  sections.push({
    title: t('wertstrom.export.pdf.sectionBottleneckAnalysis', 'Engpass-Analyse'),
    text: [bottleneck.reasonDe, ...statementText('bottleneck-secondary')].filter(Boolean).join(' '),
  })

  sections.push({ title: t('wertstrom.export.pdf.sectionQuality', 'Qualität'), text: statementText('quality').join(' ') || t('wertstrom.export.pdf.qualityNotAssessable', 'Qualitätsverluste sind nicht bewertbar.') })

  sections.push({
    title: t('wertstrom.export.pdf.sectionCurrentKpis', 'Ist-KPIs'),
    items: [
      { label: t('wertstrom.export.kpiTakt', 'Kundentakt'), value: input.taktTimeSec != null ? `${Math.round(input.taktTimeSec)} s` : t('wertstrom.export.notAssessable', 'nicht bewertbar') },
      { label: t('wertstrom.export.pdf.fieldBottleneckUtilization', 'Auslastung Engpass'), value: fmtPct(bottleneck.primary?.utilizationPct) },
      { label: t('wertstrom.export.pdf.fieldLeadTime', 'Durchlaufzeit'), value: fmtSec(ladder.totalLeadTimeSec) },
      { label: t('wertstrom.export.pdf.fieldPce', 'Wertschöpfungsanteil (PCE)'), value: fmtPct(ladder.processCycleEfficiencyPct) },
    ],
  })

  if (input.scenarioComparison) {
    sections.push({
      title: interpolate(t('wertstrom.export.pdf.scenarioComparisonTitle', 'Soll-KPIs & Szenario-Vergleich — „{scenarioLabel}"'), { scenarioLabel: input.scenarioComparison.scenarioLabel }),
      table: {
        headers: [
          t('wertstrom.export.pdf.colKpi', 'Kennzahl'),
          t('wertstrom.export.pdf.colCurrent', 'Ist'),
          t('wertstrom.export.pdf.colTarget', 'Soll'),
          t('wertstrom.export.pdf.colDeltaAbsolute', 'Delta absolut'),
          t('wertstrom.export.pdf.colDeltaPercent', 'Delta %'),
        ],
        rows: input.scenarioComparison.rows.map((r: ScenarioEffectRow) => [
          t(KPI_LABEL_KEYS[r.key] ?? '', r.label),
          fmtNum(r.currentValue),
          fmtNum(r.scenarioValue),
          r.absoluteDelta != null ? fmtNum(r.absoluteDelta) : t('wertstrom.export.pdf.notComparable', 'nicht vergleichbar'),
          r.percentDelta != null ? `${fmtNum(r.percentDelta)} %` : dash,
        ]),
      },
    })
  }

  sections.push({ title: t('wertstrom.export.pdf.sectionKaizen', 'Verbesserungshebel (Kaizen)'), text: statementText('improvement').join(' ') })
  sections.push({ title: t('wertstrom.export.pdf.sectionMeasures', 'Maßnahmen'), text: statementText('open-measures').join(' ') })

  const assumptions = Array.from(new Set([...bottleneck.explain.exclusions, ...ladder.explain.exclusions])).slice(0, 12)
  sections.push({
    title: t('wertstrom.export.pdf.sectionAssumptions', 'Annahmen'),
    text: [
      t(
        'wertstrom.export.pdf.assumptionProductScope',
        'Produkt und Wertstrom-Scope sind im heutigen Datenmodell keine eigenen Felder (nur Titel/Beschreibung) — nicht Teil dieses Reports (dokumentierte Lücke, siehe PRODUCT_SPEC.md P7-Abschnitt).',
      ),
      // C16-Fix: explicit, in-document disclosure of the missing
      // visualization — was previously only a source comment/PRODUCT_SPEC.md
      // note, invisible to anyone actually reading the PDF. Same treatment
      // as the Produkt/Scope gap above, not a silent omission.
      t(
        'wertstrom.export.pdf.assumptionNoEmbeddedGraphic',
        'Die Wertstrom-Grafik ist aus technischen Gründen nicht in dieses PDF eingebettet (der Report-Baustein unterstützt keine Bilder) — siehe den separaten Export "Visuelle Karte (SVG + PNG)" im selben Dialog.',
      ),
      ...assumptions,
    ].join(' '),
  })

  const notComputableStatements = analysis.statements.filter((s) => s.confidence === 'not-computable')
  sections.push({
    title: t('wertstrom.export.pdf.sectionDataQualityNotes', 'Datenqualitäts-Hinweise'),
    text: [
      interpolate(t('wertstrom.export.pdf.dataQualityNotComputable', '{notComputableCount} von {totalCount} Analyse-Aussagen sind mit der aktuellen Datenbasis nicht bewertbar.'), {
        notComputableCount: String(notComputableStatements.length),
        totalCount: String(analysis.statements.length),
      }),
      interpolate(t('wertstrom.export.pdf.dataQualityKnownGaps', '{count} dokumentierte Modellierungslücken der Validierungs-Engine (siehe lib/vsm-engine KNOWN_VALIDATION_GAPS).'), { count: String(analysis.knownGaps.length) }),
    ].join(' '),
  })

  return {
    title: t('wertstrom.export.pdf.reportTitle', 'Wertstrom-Management-Report'),
    subtitle: input.title,
    author: input.author,
    date: input.generatedAtLabel,
    sections,
    fileName: `Wertstrom_Management_Report_${input.title.replace(/[^0-9A-Za-z_-]+/g, '_')}.pdf`,
  }
}
