// VirtualQafVariant <-> canonical comparison-engine bridge (KAR-929 /
// Multi-QAF-Programm P1.1).
//
// Master-Prompt §6: "The virtual variant must be compatible with the
// existing canonical standard QAF comparison engine wherever technically
// possible." — and the task instruction this module answers directly:
// "wo 1:1-Kompatibilität heute nicht sinnvoll ist, dokumentiere das Mapping
// explizit als Funktion toCanonicalInputs(virtualVariant) mit TODO-freiem,
// ehrlichem Scope."
//
// ../compare.ts's QafFileParsed is the canonical engine's actual input
// shape (ref/summary/steps mandatory, everything else — summaryMetrics,
// materialRows, sbmRows, ... — optional and independently tri-stateable, per
// that interface's own doc comments). This module maps a VirtualQafVariant
// onto a Partial<QafFileParsed> and is explicit, field by field, about
// which of the two mapped shapes (QafSummary, SummaryMetricsParse) get a
// REAL value and which stay honestly absent:
//
//   MAPPED (this PR):
//     - QafSummary.partNumber      <- definition.compositeCanonicalKey
//       (the deterministic, reorder/rename-stable identity from identity.ts
//       — the correct "same part number" comparison key for a virtual
//       variant, since a Multi-QAF workbook has no single customer part
//       number of its own the way a standard QAF file does).
//     - QafSummary.variant         <- definition.originalVariantNumber
//     - QafSummary.project         <- definition.dimensions.project (when
//       present — many real files DO carry a "project"-shaped dimension,
//       e.g. 10-analyse-ncar.md's Achscode/Baureihe columns; absent when the
//       container never populated that dimension key).
//     - QafSummary.partName        <- definition.originalLabels[0] (best
//       human-readable label available, NOT a real customer part name).
//     - QafSummary.peakVolumeYear  <- definition.peakVolume, stringified
//       (SummaryField is string-typed like every other QafSummary field —
//       see types.ts doc: "this module never types SUMMARY identity/premise
//       values, only the separate SummaryMetricKey money-metric block
//       does").
//     - SummaryMetricsParse.metrics.{materialCosts, manufacturingCosts,
//       totalProductionCosts, devicesAndTools, scrapMaterial,
//       otherSurcharges, quotationBasePrice, quotationPrice} <- the
//       matching VirtualVariantSummaryTotals field (direct 1:1 business-
//       meaning correspondence).
//     - SummaryMetricsParse.currency <- definition.currency.
//
//   DELIBERATELY LEFT ABSENT (not fabricated, not TODO — genuinely no
//   Multi-QAF-side source exists yet, or the canonical field's business
//   meaning does not reduce to anything this PR's domain model carries):
//     - QafSummary.quotationDate/supplier/requestVersion/changeIndex/
//       supplierNo/productionStartSop/deliverySite/shiftsPerWeek: no
//       per-variant source — these are workbook-level facts
//       (MultiQafContainer.sharedMetadata carries some of them at the
//       CONTAINER level, e.g. supplier; a P2.1 VirtualQafVariant-generation
//       pass, once it actually reads a real workbook, is the place that
//       decides whether/how to thread container-level metadata down into
//       each variant's canonical inputs — this module only defines the
//       function shape, it has no real container to read from yet since
//       P1.2 does not exist).
//     - SummaryMetricsParse.metrics.{packagingTransportIncluded,
//       customsIncluded, scrapManufacturing, totalCosts,
//       rawMaterialPriceShareMaterial, rawMaterialPriceShareEnergy,
//       customsSupplierToBMW, transportSupplierToBMW, oneTimeDevelopment,
//       oneTimeTools, totalOneTimePayment, costBreakdownAw1}: no
//       VirtualVariantSummaryTotals field carries these — Master-Prompt §13
//       lists a narrower "for every virtual variant, independently verify"
//       set than the full 20-key canonical registry, and this module does
//       not invent values for the gap. `scrapManufacturing` in particular is
//       NOT filled from VirtualVariantSummaryTotals.scrap (that single field
//       maps only onto `scrapMaterial` — see that field's own doc comment in
//       types.ts): splitting scrap by material/manufacturing origin needs
//       the manufacturing-profile parser (P1.4) this PR does not build, so
//       fabricating scrapManufacturing = 0 or = scrap would silently claim
//       false precision.
//     - VirtualVariantSummaryTotals.offerBasePriceInclAllocation (KAR-951 F1
//       fix, added after this module's original mapping list above): NOT
//       mapped onto `SummaryMetricsParse.metrics.quotationBasePrice` (the
//       canonical field `offerBasePrice` already maps onto) or onto
//       `quotationPrice` — the two are DIFFERENT Summary-sheet rows with
//       different business meaning (final base price including the
//       vorrichtungs-Umlage surcharge vs. the plain base/offer price; see
//       that field's own doc comment in types.ts) and the canonical 20-key
//       registry (../summary-metrics.ts) has no key for "base price
//       including allocation" to map it onto — inventing one, or silently
//       overwriting `quotationBasePrice` with it, would misrepresent which
//       of the two real numbers the canonical engine actually received.
//       Honestly absent from `toCanonicalInputs()`'s output, same as every
//       other field in this "no canonical slot exists" list.
//     - QafFileParsed.ref/steps: QafFileRef needs an id/fileName/
//       quotationDate this module has no authority to invent (a P1.2+
//       ingest caller assigns those once a real workbook exists); `steps`
//       (QAFRow[] Fertigungskosten process rows) has NO per-variant
//       equivalent in the Multi-QAF domain model at all — manufacturing
//       cost is a SHARED profile (SharedCostProfile) selected via a
//       VariantProfileBinding, not an independent per-variant process list
//       (Master-Prompt §12: "Multi-QAF manufacturing logic may use shared
//       profiles instead of one independent process list per variant").
//       Turning a SharedCostProfile into synthetic QAFRow entries needs the
//       manufacturing-profile parser (P1.4) plus a translation step neither
//       of which exist yet — returning `steps: []` here would be
//       indistinguishable from "this variant genuinely has zero
//       manufacturing steps", a false claim; omitting the key entirely
//       (Partial<QafFileParsed>) is the honest representation.
//     - QafFileParsed.materialRows/sbmRows/rmrRows/logisticsRows/
//       lccnValues/co2eMaterialRows: same reasoning as `steps` — these
//       require translating VariantMatrixRow/SharedCostProfile shapes into
//       the specific row types each detail-module parser (material-parser.ts
//       etc.) produces, which is P2.1's job once P1.2-P1.6 exist to feed it
//       real data.

import type { QafFileParsed } from '../compare'
import { SUMMARY_METRIC_KEYS, type SummaryMetricKey, type SummaryMetricValue, type SummaryMetricsParse } from '../summary-metrics'
import type { QafSummary, QafSummaryKey, SummaryField } from '../types'
import type { MultiQafMoneyAmount, VirtualQafVariant } from './types'

/**
 * Which `QafSummary` fields `virtualVariantSummary` below populates with a
 * SYNTHETIC placeholder rather than a genuine per-variant identity value —
 * the machine-readable form of this module's own "MAPPED" doc above (KAR-943
 * / Multi-QAF-Programm P3.2 adversarial-review F1). `partNumber` is
 * `compositeCanonicalKey`, a deterministic REORDER/RENAME-stable comparison
 * key (`region=eu|drivetype=awd`-shaped) — never a real customer part
 * number, since a Multi-QAF workbook has no single one of its own the way a
 * standard QAF file does (see `virtualVariantSummary`'s own `partNumber`
 * comment). `partName` is `originalLabels[0]`, "the best human-readable
 * label available, NOT a real customer part name" (see that field's own
 * comment above). A consumer that compares a bridged variant's identity
 * against a REAL standard QAF's identity (variant-vs-standard.ts) MUST
 * exclude these two fields from a 1:1 identity-mismatch check — comparing a
 * synthetic placeholder against a real value produces a false alarm on
 * practically every legitimate comparison, never a genuine finding. Fields
 * NOT in this list that `virtualVariantSummary` does populate (`variant` <-
 * `originalVariantNumber`, `project` <- `dimensions.project`,
 * `peakVolumeYear` <- `peakVolume`) carry genuine per-variant source data and
 * remain safe/meaningful to compare. The single source of truth for "is this
 * bridged field real" lives here, next to the mapping itself, so a consumer
 * never has to re-derive/guess it from field names or content shape. */
export const SYNTHETIC_IDENTITY_FIELDS: readonly QafSummaryKey[] = ['partNumber', 'partName']

function summaryField(value: string | null): SummaryField {
  // `cell` is always null here — see module header: no single summary-sheet
  // cell backs a virtual variant's canonical fields (its provenance is
  // spread across VariantDefinition.sourceReferences /
  // VariantMatrixRow.sourceCells instead, a different shape than the single
  // A1 address SummaryField.cell expects).
  return { value, cell: null }
}

/** Master-Prompt-mandated bridge step 1: QafSummary. See module header for
 * exactly which fields are mapped vs. deliberately left absent. */
export function virtualVariantSummary(v: VirtualQafVariant): QafSummary {
  const d = v.definition
  const project = d.dimensions.project?.raw ?? null
  return {
    // The empty string ('') is identity.ts buildCompositeCanonicalKey's
    // "zero dimensions and no fallback" sentinel, NOT a real part number —
    // stamping it through would let two identity-less variants collide on
    // partNumber === '' downstream (KAR-929 review F2c). An absent/null
    // value is the honest representation; this module never fabricates.
    partNumber: summaryField(d.compositeCanonicalKey !== '' ? d.compositeCanonicalKey : null),
    quotationDate: summaryField(null),
    supplier: summaryField(null),
    partName: summaryField(d.originalLabels[0] ?? null),
    variant: summaryField(d.originalVariantNumber),
    project: summaryField(project),
    requestVersion: summaryField(null),
    changeIndex: summaryField(null),
    supplierNo: summaryField(null),
    peakVolumeYear: summaryField(d.peakVolume !== null ? String(d.peakVolume) : null),
    productionStartSop: summaryField(null),
    deliverySite: summaryField(null),
    shiftsPerWeek: summaryField(null),
    // QVS-P4 (KAR-973): no Multi-QAF-side source — a container's "Allgemeine
    // Prämissen" fields (Plankapazität/Fertigungslosgröße) are workbook/file-
    // level facts the domain model does not carry per variant (same category
    // as quotationDate/supplier/... above) — honestly absent, never fabricated.
    plannedCapacity: summaryField(null),
    lotSize: summaryField(null),
  }
}

/** Same "absent, not fabricated" sentinel the standard-path parser already
 * uses for a metric with nothing to report (summary-metrics.ts's private
 * ABSENT constant) — duplicated here (not imported) because that constant
 * is module-private; the shape is the load-bearing contract, not the
 * specific object identity. */
const ABSENT_METRIC: SummaryMetricValue = {
  value: null,
  cell: null,
  howLocated: 'fixedRow',
  confidence: 0,
  labelFile: null,
  labelVerified: null,
}

/** Deliberately coarse (same "not calibrated" category as
 * VariantDefinition.confidence) — a real confidence would need the P2.6
 * reconciliation pass (formula vs. cached value agreement across the whole
 * variant) this PR does not build. Signals "present but unverified",
 * distinct from both 0 (ABSENT_METRIC) and a verified >0.8. Named per
 * CLAUDE.md "no magic numbers" rather than an inline literal (KAR-929
 * review F6). */
const UNVERIFIED_METRIC_CONFIDENCE = 0.5

function metricFromAmount(amount: MultiQafMoneyAmount | null): SummaryMetricValue {
  if (amount === null || amount.value === null) return { ...ABSENT_METRIC }
  return {
    value: amount.value,
    cell: null,
    // Virtuelle Variante: es gibt keine Blattzeile mit Label, die man
    // gegen die Registry prüfen könnte.
    labelFile: null,
    labelVerified: null,
    // 'aggregate' (not 'labelMatch'/'fixedRow') is the closest existing
    // MetricLocateMethod fit: summary-metrics.ts already uses it for a
    // value with no label of its own, derived from another located value
    // (scrapManufacturing = row below scrapMaterial) — a virtual variant's
    // totals are likewise never label-anchored, they are computed from the
    // container's shared material master + selected profiles.
    howLocated: 'aggregate',
    confidence: UNVERIFIED_METRIC_CONFIDENCE,
  }
}

/** Master-Prompt-mandated bridge step 2: SummaryMetricsParse. See module
 * header for exactly which of the 20 canonical metric keys are mapped. */
export function virtualVariantSummaryMetrics(v: VirtualQafVariant): SummaryMetricsParse {
  const t = v.summaryTotals
  const metrics = Object.fromEntries(SUMMARY_METRIC_KEYS.map((k) => [k, { ...ABSENT_METRIC }])) as Record<
    SummaryMetricKey,
    SummaryMetricValue
  >

  metrics.materialCosts = metricFromAmount(t.materialCosts)
  metrics.manufacturingCosts = metricFromAmount(t.manufacturingCosts)
  metrics.totalProductionCosts = metricFromAmount(t.totalProductionCosts)
  metrics.devicesAndTools = metricFromAmount(t.toolingAndFixtureCost)
  // t.scrap maps to scrapMaterial ONLY — see module header "DELIBERATELY
  // LEFT ABSENT" note on scrapManufacturing.
  metrics.scrapMaterial = metricFromAmount(t.scrap)
  metrics.otherSurcharges = metricFromAmount(t.otherSurcharges)
  metrics.quotationBasePrice = metricFromAmount(t.offerBasePrice)
  metrics.quotationPrice = metricFromAmount(t.offerPrice)

  return {
    // Neither QAF_LEGACY_DE_SUMMARY nor QAF_V9_SUMMARY genuinely describes a
    // virtual variant (both name STANDARD-QAF summary-sheet LAYOUTS this
    // object never had — a Multi-QAF container has no single Zusammenfassung
    // sheet-per-variant to lay out). QAF_V9_SUMMARY is picked as the fixed,
    // documented placeholder (all 4 real Multi-QAF files sit on top of a
    // QAF 8.1+ base, never the legacy DE template) purely so this field is
    // never `undefined` — no caller should branch on it for a virtual
    // variant's origin.
    template: 'QAF_V9_SUMMARY',
    currency: v.definition.currency,
    metrics,
  }
}

/**
 * The full bridge: VirtualQafVariant -> Partial<QafFileParsed>. See module
 * header for the complete, field-by-field mapped/absent breakdown — this
 * function is intentionally just a thin composition of the two mapping
 * functions above, so that breakdown stays the single source of truth
 * rather than being re-derived at each call site.
 */
export function toCanonicalInputs(v: VirtualQafVariant): Partial<QafFileParsed> {
  return {
    summary: virtualVariantSummary(v),
    summaryMetrics: virtualVariantSummaryMetrics(v),
  }
}
