// Der MAT-Produzent: persistierte Material-Zuordnungen → Differenzkatalog
// (Loop 3 / Block 2d).

import { describe, it, expect } from 'vitest'
import {
  differencesFromMaterial,
  materialTableNamesByDifferenceId,
  type MaterialDiffRowData,
} from '../differences-from-material'
import { buildCatalog } from '../all-differences'
import { rowsFromRecords } from '../table-specs'

const zeile = (over: Partial<MaterialDiffRowData> = {}): MaterialDiffRowData => ({
  mapping_id: 'MAT-M-001',
  award_rows: [13],
  current_rows: [13],
  award_names: ['Dichtring'],
  current_names: ['Dichtring'],
  match_type: 'exact',
  cost_award: 1.02,
  cost_current: 0.44,
  delta: -0.58,
  effect: 'price_change',
  zero_cost: false,
  ...over,
})

describe('differencesFromMaterial', () => {
  it('lässt Unverändertes satzlos — auch die Nullkosten-Positionen (D02-Kanal)', () => {
    const r = differencesFromMaterial([
      zeile({ effect: 'unchanged', delta: 0 }),
      zeile({ match_type: 'added', effect: 'unchanged', zero_cost: true, cost_award: null, delta: null }),
    ])
    expect(r.differences).toEqual([])
    expect(r.skippedDe).toEqual([])
  })

  it('übersetzt matchType × Wirkungsart in die nativen Katalog-Arten', () => {
    const r = differencesFromMaterial([
      zeile({ match_type: 'added', effect: 'scope_added', cost_award: null, delta: 2.2, award_rows: [], award_names: [] }),
      zeile({ match_type: 'removed', effect: 'price_removed', cost_current: null, delta: -0.9, current_rows: [], current_names: [], award_rows: [20] }),
      zeile({ match_type: 'removed', effect: 'scope_removed', cost_award: null, cost_current: null, delta: 0, current_rows: [], current_names: [], award_rows: [21] }),
      zeile({ match_type: 'renamed', award_rows: [22], current_rows: [22] }),
      zeile({ match_type: 'merged', effect: 'consolidation', award_rows: [23, 24], current_rows: [30] }),
      zeile({ match_type: 'split', effect: 'consolidation', award_rows: [25], current_rows: [31, 32] }),
      zeile({ effect: 'scope_activated', cost_award: null, delta: 6.0, award_rows: [26], current_rows: [26] }),
      zeile({ effect: 'price_removed', cost_current: null, delta: -1.1, award_rows: [27], current_rows: [27] }),
    ])
    expect(r.differences.map((d) => d.kind)).toEqual([
      'added',
      'removed',
      'removed',
      'renamed',
      'merged',
      'split',
      'newly_filled',
      'cleared',
    ])
    expect(r.differences.every((d) => d.area === 'MAT')).toBe(true)
    // Der ersatzlose und der bepreiste Wegfall teilen `removed` — die
    // Beschreibung trägt die Unterscheidung deterministisch.
    expect(r.differences[1].descriptionDe).toContain('entfallen')
    expect(r.differences[2].descriptionDe).toContain('ersatzlos entfallen')
  })

  it('benennt Kombinationen ohne Ziel, statt sie zu verschlucken', () => {
    const r = differencesFromMaterial([zeile({ match_type: 'zukunftswert' })])
    expect(r.differences).toEqual([])
    expect(r.skippedDe).toHaveLength(1)
    expect(r.skippedDe[0]).toContain('zukunftswert')
  })

  it('ankert auf der aktuellen Blattzeile, sonst Vergabestand — und bleibt ohne Zellen (Weg 4)', () => {
    const [d] = differencesFromMaterial([zeile({})]).differences
    expect(d.anchorRow).toBe(13)
    expect(d.cells).toEqual([])
    expect(d.deltaNullReason).toBeNull()
    const [nurAward] = differencesFromMaterial([
      zeile({ match_type: 'removed', effect: 'price_removed', current_rows: [], current_names: [], award_rows: [40] }),
    ]).differences
    expect(nurAward.anchorRow).toBe(40)
  })

  it('bringt die echten Namen je Stand in die Tabelle — permutationsfest', () => {
    const eingaben = [
      zeile({ mapping_id: 'M-1', award_names: ['Alt-Name'], current_names: ['Neu-Name'], match_type: 'renamed', award_rows: [13], current_rows: [13] }),
      zeile({ mapping_id: 'M-2', award_names: ['Gehäuse'], current_names: ['Gehäuse'], award_rows: [15], current_rows: [15] }),
    ]
    for (const reihenfolge of [eingaben, [...eingaben].reverse()]) {
      const r = differencesFromMaterial(reihenfolge)
      const records = buildCatalog(r.differences).records
      const rows = rowsFromRecords(records, materialTableNamesByDifferenceId(records, r.nameByTuple))
      const umbenannt = rows.find((x) => x.matchType === 'renamed')
      expect(umbenannt).toBeDefined()
      expect(umbenannt!.namesAward).toEqual(['Alt-Name'])
      expect(umbenannt!.namesCurrent).toEqual(['Neu-Name'])
      expect(records.every((x) => /^D-MAT-\d{3,}$/.test(x.differenceId))).toBe(true)
    }
  })
})
