// Unit tests for header-parser.ts (KAR-930 / Multi-QAF-Programm P1.2).
//
// FIXTURE-DATEN-REGEL (same discipline as synthetic-fixtures.ts's own header
// comment, KAR-929 adversarial review F4 — merge-blocker class): every code,
// number, formula text, and cell reference below is FREE INVENTION. Before
// adding any new literal here: grep -inE "<code-or-number>"
// /root/aria/brain/01-Projekte/supplierpulse-multi-qaf/10-analyse-*.md must
// be EMPTY. Vehicle/part codes deliberately avoid the real G/F/U/i-prefix +
// 2-digit BMW-series notation (reusing the same invented scheme // allow-customer-string
// synthetic-fixtures.ts already established: qx1/qx2/qx3, eng-a/eng-b,
// l-shape/i-shape, wx1/wx2/wx3, pfa/pfb, ...).
//
// These grids reproduce the STRUCTURAL patterns of the 4 real analyzed
// workbooks (10-analyse-clarwe-eu.md/-mx.md/-nafta.md/-ncar.md — column
// layout, header-row depth, index-row gaps/outliers, orphan/benchmark
// patterns) with entirely invented content, mirroring synthetic-fixtures.ts's
// own fixture-building discipline but at the RAW GRID level (this module
// parses grids, not already-built MultiQafContainer objects).

import { describe, it, expect } from 'vitest'
import { gridFromCells } from '../../summary-parser'
import { columnIndexToLetter } from '../types'
import {
  parseVariantHeaderBlock,
  locateSlotIndexRow,
  locateVolumeRows,
  DEFAULT_COL_FROM,
  type HeaderParserGridInput,
} from '../header-parser'

// ── Grid-building helpers ───────────────────────────────────────────────

/** 1-based column letter for a 0-based column index (test convenience). */
function col(col0: number): string {
  return columnIndexToLetter(col0 + 1)
}

function formulaGridFromCells(cells: Record<string, string>): (string | null)[][] {
  let maxRow = 0
  let maxCol = 0
  const parsed: Array<{ row: number; col: number; value: string }> = []
  for (const [addr, value] of Object.entries(cells)) {
    const m = /^([A-Za-z]+)(\d+)$/.exec(addr)
    if (!m) throw new Error(`bad A1 address: ${addr}`)
    let col0 = 0
    for (const ch of m[1].toUpperCase()) col0 = col0 * 26 + (ch.charCodeAt(0) - 64)
    col0 -= 1
    const row0 = Number(m[2]) - 1
    parsed.push({ row: row0, col: col0, value })
    if (row0 > maxRow) maxRow = row0
    if (col0 > maxCol) maxCol = col0
  }
  const grid: (string | null)[][] = Array.from({ length: maxRow + 1 }, () => new Array(maxCol + 1).fill(null))
  for (const { row, col: c, value } of parsed) grid[row][c] = value
  return grid
}

// ── Fixture 1: wide-slot pattern (MX-like — 10-analyse-mx.md C/D) ─────────
//
// Variant band starts at column Q (DEFAULT_COL_FROM). Row 2 = slot-index
// (Q..Z -> 1..10, AF/AG -> 11/12, AH..AT -> 13..25 reserved, AB -> 1
// out-of-order outlier). Row 3 = "BMW Variant" identity/code row (populated // allow-customer-string
// ONLY for genuine slots). Row 4 = "Antrieb" (driveType). Row 5 = "Shape"
// (custom, unrecognized dimension key). Row 6 = "LL/RL" (steeringSide).
// Row 7 = "Stückzahl Ø/Jahr" (annualVolume). Row 14 = classification-label
// annotation for AB (deliberately NOT a dimension row — see header-parser.ts
// module header). Row 15 = money row carrying the benchmark literal + delta/
// percentage/comparison/total formulas.
function buildWideSlotGrid(): HeaderParserGridInput {
  const Q = DEFAULT_COL_FROM // column Q, 0-based
  const activeCols = Array.from({ length: 10 }, (_, i) => Q + i) // Q..Z
  const inactiveCols = [Q + 15, Q + 16] // AF, AG
  const reservedCols = Array.from({ length: 13 }, (_, i) => Q + 17 + i) // AH..AT
  const benchmarkCol = Q + 11 // AB
  const deltaCol = Q + 12 // AC
  const percentCol = Q + 13 // AD
  const comparisonCol = Q + 32 // AV
  const totalCol = Q + 40

  const cells: Record<string, unknown> = {
    [`P2`]: 'Slot-Index',
    [`P3`]: 'BMW Variant', // allow-customer-string
    [`P4`]: 'Antrieb',
    [`P5`]: 'Shape',
    [`P6`]: 'LL/RL',
    [`P7`]: 'Stückzahl Ø/Jahr',
  }

  activeCols.forEach((c, i) => {
    cells[`${col(c)}2`] = i + 1
    cells[`${col(c)}3`] = `slot-${i + 1}`
    cells[`${col(c)}4`] = 'awd'
    cells[`${col(c)}5`] = i < 5 ? 'l-shape' : 'i-shape'
    cells[`${col(c)}6`] = i % 2 === 0 ? 'll' : 'rl'
    cells[`${col(c)}7`] = 1000 * (i + 1)
  })

  inactiveCols.forEach((c, i) => {
    cells[`${col(c)}2`] = 11 + i
    cells[`${col(c)}3`] = `slot-${11 + i}`
    cells[`${col(c)}4`] = 'awd'
    cells[`${col(c)}5`] = 'i-shape'
    cells[`${col(c)}6`] = 'll'
    cells[`${col(c)}7`] = 0 // identity present, zero volume -> inactive.
  })

  reservedCols.forEach((c, i) => {
    cells[`${col(c)}2`] = 13 + i // index present, everything else blank -> reserved.
  })

  // Benchmark pseudo-slot: out-of-order index value, no dimension-row
  // identity at all, a text annotation (row14, "vs"-hinting) plus a pure
  // literal money value (row15) with NO formula.
  cells[`${col(benchmarkCol)}2`] = 1 // outlier — appears after Z's value 10.
  cells[`${col(benchmarkCol)}14`] = 'other-programme vs benchmark comparison'
  cells[`${col(benchmarkCol)}15`] = 282.14

  // Delta helper: benchmark minus a real variant (S = Q+2, slot-3).
  cells[`${col(deltaCol)}15`] = 0
  // Percentage helper: delta / variant.
  cells[`${col(percentCol)}15`] = 0
  // Comparison scenario: two REAL variants (AF = inactiveCols[0], S = Q+2).
  cells[`${col(comparisonCol)}15`] = 0
  // Total: SUM across the 10 active columns.
  cells[`${col(totalCol)}15`] = 0

  const grid = gridFromCells(cells)
  const formulaGrid = formulaGridFromCells({
    [`${col(deltaCol)}15`]: `=${col(benchmarkCol)}15-${col(Q + 2)}15`,
    [`${col(percentCol)}15`]: `=${col(deltaCol)}15/${col(Q + 2)}15`,
    [`${col(comparisonCol)}15`]: `=${col(inactiveCols[0])}15-${col(Q + 2)}15`,
    [`${col(totalCol)}15`]: `=SUM(${col(activeCols[0])}15:${col(activeCols[9])}15)`,
  })

  return { sheet: 'Zusammenfassung', grid, formulaGrid }
}

// ── Fixture 2: multi-row header pattern, no slot-index row (deep dimension block —
// 10-analyse-ncar.md D) — 8 variants identified purely from dimension rows
// + a variant-code row + volume rows. ──────────────────────────────────────
function buildMultiRowHeaderGrid(): HeaderParserGridInput {
  const Q = DEFAULT_COL_FROM
  const specs = [
    { code: 'v201-lc1', motor: 'eng-a', axle: 'ax-n', steer: 'll', peak: 84000 },
    { code: 'v202-lc1', motor: 'eng-a', axle: 'ax-n', steer: 'rl', peak: 58500 },
    { code: 'v203-lc1', motor: 'eng-a', axle: 'ax-s', steer: 'll', peak: 96000 },
    { code: 'v204-lc1', motor: 'eng-a', axle: 'ax-s', steer: 'rl', peak: 31000 },
    { code: 'v205-lc2', motor: 'eng-b', axle: 'ax-t', steer: 'll', peak: 14000 },
    { code: 'v206-lc2', motor: 'eng-b', axle: 'ax-t', steer: 'rl', peak: 2200 },
    { code: 'v207-lc2', motor: 'eng-b', axle: 'ax-u', steer: 'll', peak: 17500 },
    { code: 'v208-lc2', motor: 'eng-b', axle: 'ax-u', steer: 'rl', peak: 6100 },
  ]
  const cells: Record<string, unknown> = {
    A5: 'BMW Variant ID', // allow-customer-string
    A6: 'Motor',
    A7: 'Achscode',
    A9: 'LL/RL',
    A15: 'Stückzahl peak',
  }
  specs.forEach((s, i) => {
    const c = Q + i
    cells[`${col(c)}5`] = s.code
    cells[`${col(c)}6`] = s.motor
    cells[`${col(c)}7`] = s.axle
    cells[`${col(c)}9`] = s.steer
    cells[`${col(c)}15`] = s.peak
  })
  return { sheet: 'Zusammenfassung', grid: gridFromCells(cells) }
}

// ── Fixture 3: split-column pattern (10-analyse-clarwe-eu.md
// C.2/C.3) — 6 linked slots via index row + 2 orphaned-but-real columns
// with FULL identity/volume but NO index value at all. ─────────────────────
function buildSplitColumnGrid(): HeaderParserGridInput {
  const Q = DEFAULT_COL_FROM
  const cells: Record<string, unknown> = {
    P2: 'Slot-Index',
    P3: 'Variantencode',
    P4: 'Fahrzeug',
    P5: 'LL/RL',
    P6: 'Stückzahl lifetime',
  }
  for (let i = 0; i < 6; i++) {
    const c = Q + i
    cells[`${col(c)}2`] = i + 1
    cells[`${col(c)}3`] = `code-${i + 1}`
    cells[`${col(c)}4`] = 'wx1'
    cells[`${col(c)}5`] = i % 2 === 0 ? 'll' : 'rl'
    cells[`${col(c)}6`] = 1000 + i * 500
  }
  // 2 orphaned-but-real columns: full identity + positive volume, but no
  // index-row value at all (not even an outlier).
  const orphanedCols = [Q + 8, Q + 9]
  orphanedCols.forEach((c, i) => {
    cells[`${col(c)}3`] = `other-code-${i + 1}`
    cells[`${col(c)}4`] = 'other-programme'
    cells[`${col(c)}5`] = i === 0 ? 'll' : 'rl'
    cells[`${col(c)}6`] = 220000 + i * 40000
  })
  return { sheet: 'Zusammenfassung', grid: gridFromCells(cells) }
}

// ── Tests ────────────────────────────────────────────────────────────────

describe('locateSlotIndexRow', () => {
  it('finds the primary ascending run and demotes an out-of-order value to an outlier', () => {
    const input = buildWideSlotGrid()
    const result = locateSlotIndexRow(input, DEFAULT_COL_FROM, DEFAULT_COL_FROM + 60, 1, 40)
    expect(result).not.toBeNull()
    expect(result!.row).toBe(2)
    expect(result!.primary.size).toBe(25) // 10 active + 2 inactive + 13 reserved.
    const benchmarkCol0 = DEFAULT_COL_FROM + 11
    expect(result!.outliers.get(benchmarkCol0)).toBe(1)
    expect(result!.primary.has(benchmarkCol0)).toBe(false)
  })

  it('returns null when no row reaches the minimum ascending run (deep dimension block, no index row)', () => {
    const input = buildMultiRowHeaderGrid()
    const result = locateSlotIndexRow(input, DEFAULT_COL_FROM, DEFAULT_COL_FROM + 20, 1, 40)
    expect(result).toBeNull()
  })

  it('still selects the true slot-index row over a shorter competing ascending data row (KAR-930 review F7)', () => {
    // The longest-strictly-increasing-run rule is intentional (module
    // header), but was untested against a competing ascending row sharing
    // the same scan window — a per-column cost/quantity row that also
    // happens to ascend left-to-right, just across FEWER columns than the
    // real slot-index row's primary run. This proves the rule still picks
    // correctly rather than being coincidentally right only in the
    // no-competitor fixtures.
    const input = buildWideSlotGrid()
    const competingGrid = input.grid.map((row) => [...row])
    while (competingGrid.length < 25) competingGrid.push([])
    const Q = DEFAULT_COL_FROM
    for (let i = 0; i < 15; i++) {
      competingGrid[24][Q + i] = (i + 1) * 10 // row 25 (1-based): ascending, but only 15 long — shorter than the real 25-long index row.
    }
    const result = locateSlotIndexRow({ ...input, grid: competingGrid }, DEFAULT_COL_FROM, DEFAULT_COL_FROM + 60, 1, 40)
    expect(result).not.toBeNull()
    expect(result!.row).toBe(2)
    expect(result!.primary.size).toBe(25)
  })
})

describe('locateVolumeRows', () => {
  it('locates the annual-volume row by DE label', () => {
    const input = buildWideSlotGrid()
    const result = locateVolumeRows(input, DEFAULT_COL_FROM, DEFAULT_COL_FROM + 60, 1, 40)
    expect(result.annualVolume?.row).toBe(7)
    expect(result.annualVolume?.values.get(DEFAULT_COL_FROM)).toBe(1000)
  })
})

describe('parseVariantHeaderBlock — wide-slot pattern (MX-like)', () => {
  const input = buildWideSlotGrid()
  const result = parseVariantHeaderBlock(input)

  it('produces exactly 25 variants (10 active + 2 inactive + 13 reserved) — benchmark/delta/percentage/comparison/total excluded', () => {
    expect(result.variants).toHaveLength(25)
  })

  it('classifies the 10 populated slots as active and the 2 zero-volume slots as inactive', () => {
    const active = result.variants.filter((v) => v.activeState === 'active')
    const inactive = result.variants.filter((v) => v.activeState === 'inactive')
    const reserved = result.variants.filter((v) => v.activeState === 'reserved')
    expect(active).toHaveLength(10)
    expect(inactive).toHaveLength(2)
    expect(reserved).toHaveLength(13)
  })

  it('classifies the benchmark literal column as benchmark_scenario, not a variant', () => {
    const benchmarkCol0 = DEFAULT_COL_FROM + 11
    const classification = result.columnClassifications.find((c) => c.columnIndex === benchmarkCol0 + 1)
    expect(classification?.kind).toBe('benchmark_scenario')
    expect(result.variants.some((v) => v.originalColumnIndex === benchmarkCol0 + 1)).toBe(false)
  })

  it('classifies the delta-helper column as delta_column (references the benchmark, not two variants)', () => {
    const deltaCol0 = DEFAULT_COL_FROM + 12
    const classification = result.columnClassifications.find((c) => c.columnIndex === deltaCol0 + 1)
    expect(classification?.kind).toBe('delta_column')
  })

  it('classifies the percentage-helper column as percentage_delta_column', () => {
    const percentCol0 = DEFAULT_COL_FROM + 13
    const classification = result.columnClassifications.find((c) => c.columnIndex === percentCol0 + 1)
    expect(classification?.kind).toBe('percentage_delta_column')
  })

  it('classifies the comparison column (two real variants) as comparison_scenario, not delta_column', () => {
    const comparisonCol0 = DEFAULT_COL_FROM + 32
    const classification = result.columnClassifications.find((c) => c.columnIndex === comparisonCol0 + 1)
    expect(classification?.kind).toBe('comparison_scenario')
    expect(classification?.relatedVariantIds).toHaveLength(2)
  })

  it('classifies the wide SUM column as total', () => {
    const totalCol0 = DEFAULT_COL_FROM + 40
    const classification = result.columnClassifications.find((c) => c.columnIndex === totalCol0 + 1)
    expect(classification?.kind).toBe('total')
  })

  it('gives every classification at least one evidence entry', () => {
    for (const c of result.columnClassifications) {
      expect(c.evidence.length).toBeGreaterThan(0)
    }
  })
})

describe('parseVariantHeaderBlock — multi-row header, no index row', () => {
  const input = buildMultiRowHeaderGrid()
  const result = parseVariantHeaderBlock(input)

  it('identifies all 8 variants purely via dimension rows (no slot-index row present)', () => {
    expect(result.slotIndexRow).toBeNull()
    expect(result.variants).toHaveLength(8)
    expect(result.variants.every((v) => v.activeState === 'active')).toBe(true)
  })

  it('reads the variant-code row into originalVariantNumber, not into dimensions', () => {
    const first = result.variants.find((v) => v.peakVolume === 84000)
    expect(first?.originalVariantNumber).toBe('v201-lc1')
    expect(first?.dimensions.variantCode).toBeUndefined()
  })

  it('captures the extensible "Achscode" dimension under its own key', () => {
    const first = result.variants.find((v) => v.peakVolume === 84000)
    expect(first?.dimensions.axleCode?.raw).toBe('ax-n')
  })
})

describe('parseVariantHeaderBlock — split-column pattern (orphaned-but-real)', () => {
  const input = buildSplitColumnGrid()
  const result = parseVariantHeaderBlock(input)

  it('includes the 2 orphaned-but-real columns as active variants distinct from the 6 linked ones', () => {
    expect(result.variants).toHaveLength(8)
    const orphaned = result.variants.filter((v) => v.dimensions.vehicle?.raw === 'other-programme')
    expect(orphaned).toHaveLength(2)
    expect(orphaned.every((v) => v.activeState === 'active')).toBe(true)
    // Full identity (own variant code, own dimensions) but NEVER part of
    // the primary slot-index run — 10-analyse-clarwe-eu.md C.3's real
    // finding for its two orphaned-but-real BOM columns.
    expect(orphaned.every((v) => v.originalVariantNumber?.startsWith('other-code-'))).toBe(true)
  })

  it('classifies the orphaned columns as active_product_variant in the column-classification report too', () => {
    const orphaned = result.variants.filter((v) => v.dimensions.vehicle?.raw === 'other-programme')
    for (const v of orphaned) {
      const classification = result.columnClassifications.find((c) => c.columnIndex === v.originalColumnIndex)
      expect(classification?.kind).toBe('active_product_variant')
    }
  })
})

describe('parseVariantHeaderBlock — reorder stability', () => {
  it('produces the same set of composite canonical keys regardless of column order', () => {
    const Q = DEFAULT_COL_FROM
    function buildGrid(order: number[]): HeaderParserGridInput {
      const cells: Record<string, unknown> = { P2: 'Slot-Index', P3: 'Variantencode', P4: 'Antrieb', P5: 'Stückzahl Ø/Jahr' }
      order.forEach((specIndex, position) => {
        const c = Q + position
        cells[`${col(c)}2`] = position + 1
        cells[`${col(c)}3`] = `code-${specIndex}`
        cells[`${col(c)}4`] = specIndex % 2 === 0 ? 'awd' : 'rwd'
        cells[`${col(c)}5`] = 1000 * specIndex
      })
      return { sheet: 'Zusammenfassung', grid: gridFromCells(cells) }
    }
    const gridA = buildGrid([1, 2, 3, 4])
    const gridB = buildGrid([4, 3, 2, 1]) // same 4 variants, reversed column order.
    const keysA = new Set(parseVariantHeaderBlock(gridA).variants.map((v) => v.compositeCanonicalKey))
    const keysB = new Set(parseVariantHeaderBlock(gridB).variants.map((v) => v.compositeCanonicalKey))
    expect(keysA).toEqual(keysB)
  })
})

describe('parseVariantHeaderBlock — DE/EN label variants', () => {
  it('maps DE and EN dimension labels onto the same known dimension key', () => {
    // No slot-index row in this fixture (identity-only detection) — a
    // non-indexed column needs >=2 independently-labeled dimension-row
    // matches to count as identity-bearing (header-parser.ts
    // MIN_NONINDEXED_KNOWN_DIMENSION_MATCHES; see its own doc for the
    // real-file false-positive it guards against), so this fixture carries
    // two recognized dimension rows (driveType + steeringSide), each in
    // matching DE/EN pairs.
    const Q = DEFAULT_COL_FROM
    const deGrid = gridFromCells({
      P4: 'Antrieb',
      P5: 'LL/RL',
      P7: 'Stückzahl Ø/Jahr',
      [`${col(Q)}4`]: 'awd',
      [`${col(Q)}5`]: 'll',
      [`${col(Q)}7`]: 5000,
    })
    const enGrid = gridFromCells({
      P4: 'Drive type',
      P5: 'Steering side',
      P7: 'Annual volume',
      [`${col(Q)}4`]: 'awd',
      [`${col(Q)}5`]: 'll',
      [`${col(Q)}7`]: 5000,
    })
    const deResult = parseVariantHeaderBlock({ sheet: 'Zusammenfassung', grid: deGrid })
    const enResult = parseVariantHeaderBlock({ sheet: 'Summary', grid: enGrid })
    expect(deResult.variants).toHaveLength(1)
    expect(enResult.variants).toHaveLength(1)
    expect(deResult.variants[0].dimensions.driveType?.raw).toBe('awd')
    expect(enResult.variants[0].dimensions.driveType?.raw).toBe('awd')
    expect(deResult.variants[0].dimensions.steeringSide?.raw).toBe('ll')
    expect(deResult.variants[0].compositeCanonicalKey).toBe(enResult.variants[0].compositeCanonicalKey)
  })
})

// ── Banner/dominance guard (KAR-930 review F1/F5) ───────────────────────────
//
// locateDimensionRows' banner-row guard used to drop an ENTIRE unrecognized
// dimension row whenever one value covered >=80% of its populated cells,
// even when the row was a genuine (if skewed) per-column dimension, not a
// merge-propagated title/footer banner. Dropping the whole row erases that
// dimension for EVERY variant column, which can silently collide two
// genuinely different variants onto the same compositeCanonicalKey. The 3
// tests below match the review's own scenario set: the exact skewed-but-real
// case (must now be KEPT), a true single-value banner (must still be
// DROPPED), and an unlabeled banner split across two differently-worded
// merge ranges (F5 — must still be DROPPED even without a single dominant
// value).

describe('parseVariantHeaderBlock — dimension-row banner/dominance guard (KAR-930 review F1/F5)', () => {
  const Q = DEFAULT_COL_FROM

  it('keeps a skewed-but-real unrecognized dimension row (8/2 split) instead of dropping it wholesale, preventing a key collision', () => {
    const cells: Record<string, unknown> = { P2: 'Slot-Index', P4: 'Antrieb', P5: 'Karosserieform' }
    for (let i = 0; i < 10; i++) {
      const c = Q + i
      cells[`${col(c)}2`] = i + 1
      cells[`${col(c)}4`] = 'awd' // identical across all 10 — the ONLY distinguishing signal is the skewed row below.
      cells[`${col(c)}5`] = i < 8 ? 'shape-x' : 'shape-y' // 8/2 split, 0.8 dominance — exactly the review's scenario.
    }
    const input: HeaderParserGridInput = { sheet: 'Zusammenfassung', grid: gridFromCells(cells) }
    const result = parseVariantHeaderBlock(input)

    expect(result.variants).toHaveLength(10)
    // Not silently merged: the 8 and the 2 must still end up with DIFFERENT
    // composite keys (old behavior collapsed all 10 onto the same key once
    // the row was dropped).
    const keys = new Set(result.variants.map((v) => v.compositeCanonicalKey))
    expect(keys.size).toBe(2)
    expect(result.dimensionDescriptors.some((d) => d.key === 'dim_karosserieform')).toBe(true)
    // Kept, but flagged: every variant leaning on this ambiguous row reports
    // a capped confidence rather than the usual clean-dimension confidence.
    expect(result.variants.every((v) => v.confidence <= 0.5)).toBe(true)
  })

  it('still drops a true single-value banner row (distinct value count 1)', () => {
    const cells: Record<string, unknown> = { P2: 'Slot-Index' }
    for (let i = 0; i < 10; i++) {
      const c = Q + i
      cells[`${col(c)}2`] = i + 1
      cells[`${col(c)}5`] = 'Formatvorlage QX Confidential Draft Notice' // invented banner text — same value everywhere, no row-label.
    }
    const input: HeaderParserGridInput = { sheet: 'Zusammenfassung', grid: gridFromCells(cells) }
    const result = parseVariantHeaderBlock(input)

    expect(result.dimensionDescriptors).toHaveLength(0)
    expect(result.variants.every((v) => Object.keys(v.dimensions).length === 0)).toBe(true)
    expect(result.variants.every((v) => v.activeState === 'reserved')).toBe(true)
  })

  it('drops an unlabeled banner split across two differently-worded merge ranges, even without a single dominant value (F5)', () => {
    const cells: Record<string, unknown> = { P2: 'Slot-Index' }
    for (let i = 0; i < 10; i++) {
      const c = Q + i
      cells[`${col(c)}2`] = i + 1
      // 6/10 vs 4/10 — NOT dominant (>=80%), and 2 distinct values, so the
      // old guard's two checks both pass; only the new "every present value
      // looks banner-shaped, and there's no row-label" rule catches it.
      cells[`${col(c)}5`] = i < 6 ? 'Musterkennung Alpha Entwurf' : 'Formatvorlage QX Draft' // invented banner text, split across two ranges.
    }
    const input: HeaderParserGridInput = { sheet: 'Zusammenfassung', grid: gridFromCells(cells) }
    const result = parseVariantHeaderBlock(input)

    expect(result.dimensionDescriptors).toHaveLength(0)
    // No dimension row survived at all, so no column can have gained
    // identity through it — every slot stays reserved.
    expect(result.variants.every((v) => v.activeState === 'reserved')).toBe(true)
  })
})

// ── Reserved-slot identity consistency (KAR-930 review F2) ──────────────────

describe('parseVariantHeaderBlock — reserved-slot identity consistency (KAR-930 review F2)', () => {
  it('a slot with ONLY a variant-code value materializes as activeState=reserved AND classifies as reserved_placeholder — never contradicting each other', () => {
    const Q = DEFAULT_COL_FROM
    const cells: Record<string, unknown> = { P2: 'Slot-Index', P3: 'Variantencode', P4: 'Antrieb' }
    // Q, R: genuine identity (Antrieb + code). S: code-only pre-assigned
    // placeholder, no other dimension data at all — the exact review
    // scenario (a not-yet-decided future variant with a pre-assigned
    // Sachnummer). T: fully empty reserved slot, for contrast.
    cells[`${col(Q)}2`] = 1
    cells[`${col(Q)}3`] = 'code-1'
    cells[`${col(Q)}4`] = 'awd'
    cells[`${col(Q + 1)}2`] = 2
    cells[`${col(Q + 1)}3`] = 'code-2'
    cells[`${col(Q + 1)}4`] = 'awd'
    cells[`${col(Q + 2)}2`] = 3
    cells[`${col(Q + 2)}3`] = 'code-3' // code-only — no Antrieb value.
    cells[`${col(Q + 3)}2`] = 4 // fully empty reserved slot.

    const input: HeaderParserGridInput = { sheet: 'Zusammenfassung', grid: gridFromCells(cells) }
    const result = parseVariantHeaderBlock(input)

    const codeOnlyColumnIndex = Q + 2 + 1
    const codeOnlyVariant = result.variants.find((v) => v.originalColumnIndex === codeOnlyColumnIndex)
    expect(codeOnlyVariant).toBeDefined()
    expect(codeOnlyVariant?.activeState).toBe('reserved')
    expect(codeOnlyVariant?.originalVariantNumber).toBe('code-3')

    const codeOnlyClassification = result.columnClassifications.find((c) => c.columnIndex === codeOnlyColumnIndex)
    expect(codeOnlyClassification?.kind).toBe('reserved_placeholder')

    const emptyColumnIndex = Q + 3 + 1
    const emptyClassification = result.columnClassifications.find((c) => c.columnIndex === emptyColumnIndex)
    expect(emptyClassification?.kind).toBe('reserved_placeholder')
    expect(result.variants.find((v) => v.originalColumnIndex === emptyColumnIndex)?.activeState).toBe('reserved')
  })
})

// ── Label-only column reachability (KAR-930 review F3) ──────────────────────

describe('parseVariantHeaderBlock — label-only column reachability (KAR-930 review F3)', () => {
  it('a column whose only signal anywhere is a free-text label reaches classifyColumn and is classified comment_column', () => {
    const Q = DEFAULT_COL_FROM
    const cells: Record<string, unknown> = { P2: 'Slot-Index', P4: 'Antrieb' }
    cells[`${col(Q)}2`] = 1
    cells[`${col(Q)}4`] = 'awd'
    cells[`${col(Q + 1)}2`] = 2
    cells[`${col(Q + 1)}4`] = 'awd'
    // A genuine comment/annotation column: no index value, no dimension-row
    // value, no formula, no numeric literal — ONLY a free-text label, deep
    // in the unclaimed body rows.
    cells[`${col(Q + 2)}30`] = 'Kommentar'

    const input: HeaderParserGridInput = { sheet: 'Zusammenfassung', grid: gridFromCells(cells) }
    const result = parseVariantHeaderBlock(input)

    const commentColumnIndex = Q + 2 + 1
    const classification = result.columnClassifications.find((c) => c.columnIndex === commentColumnIndex)
    expect(classification).toBeDefined()
    expect(classification?.kind).toBe('comment_column')
    expect(result.variants.some((v) => v.originalColumnIndex === commentColumnIndex)).toBe(false)
  })
})
