// Der MFG-Produzent: persistierte Fertigungs-Feld-Diffs → Differenzkatalog
// (Loop 3 Schritt 4).

import { describe, it, expect } from 'vitest'
import {
  differencesFromManufacturing,
  tableNamesByDifferenceId,
  type ManufacturingDiffInput,
} from '../differences-from-manufacturing'
import { buildCatalog } from '../all-differences'
import { rowsFromRecords } from '../table-specs'

const zeile = (over: Partial<ManufacturingDiffInput['diff']> = {}, station = 'Station 40'): ManufacturingDiffInput => ({
  diff: {
    field: 'zykluszeit',
    alt_value: 33.04,
    neu_value: 31.8,
    delta_absolute: -1.24,
    status: 'senkung',
    source_alt: 'F94',
    source_neu: 'F94',
    ...over,
  },
  stationDe: station,
  stationAnchorRow: 94,
})

describe('differencesFromManufacturing', () => {
  it('lässt unveränderte und beidseitig leere Felder satzlos', () => {
    const r = differencesFromManufacturing([
      zeile({ status: 'konstant', delta_absolute: 0 }),
      zeile({ status: 'nicht_berechenbar', alt_value: null, neu_value: null, delta_absolute: null }),
      zeile({ status: null }),
    ])
    expect(r.differences).toEqual([])
    expect(r.withheldDe).toEqual([])
    expect(r.skippedDe).toEqual([])
  })

  it('übersetzt Wertband, Zugang und Wegfall in die Katalog-Arten', () => {
    const r = differencesFromManufacturing([
      zeile({ status: 'kritisch_50' }),
      zeile({ field: 'mss', status: 'neu', alt_value: null, delta_absolute: null, source_alt: null }),
      zeile({ field: 'fek', status: 'entfallen', neu_value: null, delta_absolute: null, source_neu: null }),
    ])
    expect(r.differences.map((d) => d.kind)).toEqual(['changed', 'added', 'removed'])
    expect(r.differences.every((d) => d.area === 'MFG')).toBe(true)
  })

  it('rekonstruiert die zwei persistierten Formelbefunde aus dem Status', () => {
    const r = differencesFromManufacturing([
      zeile({ status: 'formel_geaendert', delta_absolute: 0 }),
      zeile({ field: 'fk', status: 'formel_zu_konstante' }),
    ])
    expect(r.differences.map((d) => d.formulaFindingKind)).toEqual([
      'formel_geaendert_wert_gleich',
      'formel_zu_konstante',
    ])
    expect(r.differences.every((d) => d.kind === 'changed')).toBe(true)
  })

  it('verweigert Sätze, wo die Engine die Aussage verweigert — benannt, nicht still', () => {
    // differ.ts: n.a. ist „not a structural add/removal"; blockiert rechnet
    // bewusst kein Delta. Der Katalog erfindet keine Aussage darüber hinaus.
    const r = differencesFromManufacturing([
      zeile({ field: 'ausschuss', status: 'nicht_anwendbar' }),
      zeile({ field: 'lohnkosten', status: 'blockiert', delta_absolute: null }),
    ])
    expect(r.differences).toEqual([])
    expect(r.withheldDe).toHaveLength(2)
    expect(r.withheldDe[0]).toContain('nicht_anwendbar')
    expect(r.withheldDe[1]).toContain('blockiert')
  })

  it('benennt einen unbekannten Status, statt ihn zu verschlucken', () => {
    const r = differencesFromManufacturing([zeile({ status: 'zukunftswert' })])
    expect(r.differences).toEqual([])
    expect(r.skippedDe).toHaveLength(1)
    expect(r.skippedDe[0]).toContain('zukunftswert')
  })

  it('beschreibt mit Station und Feld-Label und ankert auf der Quellzeile', () => {
    const [d] = differencesFromManufacturing([zeile({})]).differences
    expect(d.descriptionDe).toBe('Station 40 · Zykluszeit [s]: gesunken')
    expect(d.anchorRow).toBe(94)
    expect(d.cells).toEqual([])
    expect(d.deltaNullReason).toBeNull()
  })

  it('fällt ohne Quellzellen auf die Stations-Zeile zurück', () => {
    const [d] = differencesFromManufacturing([
      zeile({ source_alt: null, source_neu: null }),
    ]).differences
    expect(d.anchorRow).toBe(94)
  })

  it('bringt Station und Feld über die Namens-Zuordnung in die Tabelle — permutationsfest', () => {
    // TableRow kennt kein descriptionDe: ohne die names-Map wären die
    // Namensspalten leer. Die Zuordnung läuft über die Sortierschlüssel.
    const eingaben = [
      zeile({ field: 'zykluszeit', status: 'anstieg', delta_absolute: 1 }, 'Station 40'),
      zeile({ field: 'mss', status: 'senkung', delta_absolute: -2 }, 'Station 40'),
    ]
    for (const reihenfolge of [eingaben, [...eingaben].reverse()]) {
      const r = differencesFromManufacturing(
        reihenfolge.map((e) => ({ ...e, matchStatus: 'safe_match' })),
      )
      const records = buildCatalog(r.differences).records
      const rows = rowsFromRecords(records, tableNamesByDifferenceId(records, r.nameByTuple))
      const zyklus = rows.find((x) => x.namesAward.includes('Zykluszeit [s]'))
      expect(zyklus).toBeDefined()
      expect(zyklus!.namesAward[0]).toBe('Station 40')
      expect(zyklus!.matchType).toBe('safe_match')
      expect(rows.every((x) => x.namesAward.length === 2)).toBe(true)
    }
  })

  it('trennt zwei Felder derselben Station über den fünften Weg nicht — die Beschreibung trägt', () => {
    // Gleiche Station, gleiche Ankerzeile, gleiche Art, leere cells: die
    // Beschreibung (Feld-Label) ist der Schlüssel, der die Kennungen trennt —
    // je Feld verschieden, vom Produzenten garantiert.
    const r = differencesFromManufacturing([
      zeile({ field: 'zykluszeit', status: 'anstieg', delta_absolute: 1 }),
      zeile({ field: 'mss', status: 'anstieg', delta_absolute: 2 }),
    ])
    const records = buildCatalog(r.differences).records
    expect(new Set(records.map((x) => x.differenceId)).size).toBe(2)
    expect(records.every((x) => /^D-MFG-\d{3,}$/.test(x.differenceId))).toBe(true)
  })
})
