// vsm-table-paste.ts tests (A14, Wertstrom P4, KAR-878/KAR-986) —
// parseClipboardTableText / mapClipboardRowsToPreview. Covers the
// nullish-vs-falsy discipline (0 vs empty vs text) via numericCellOrUndefined
// (vsm-excel.ts), reused verbatim, plus the header-detection heuristic.
//
// FIXTURE-DATEN-REGEL: every name/number below is FREE INVENTION.

import { describe, it, expect } from 'vitest'
import { parseClipboardTableText, mapClipboardRowsToPreview } from '../vsm-table-paste'

describe('parseClipboardTableText', () => {
  it('returns an empty array for empty/whitespace-only text', () => {
    expect(parseClipboardTableText('')).toEqual([])
    expect(parseClipboardTableText('   \n  \n')).toEqual([])
  })

  it('drops blank lines (trailing newline, stray whitespace lines)', () => {
    const rows = parseClipboardTableText('Pressen\t30\t2\n\n\nMontieren\t45\t1\n')
    expect(rows).toHaveLength(2)
  })

  it('splits cells on tab (Excel/Sheets clipboard format)', () => {
    const rows = parseClipboardTableText('Pressen\t30\t2')
    expect(rows[0]).toEqual({ Name: 'Pressen', Zykluszeit: '30', 'Anzahl Mitarbeiter': '2' })
  })

  it('uses the fixed default column order (Name, Zykluszeit, Anzahl Mitarbeiter) when no header is detected', () => {
    const rows = parseClipboardTableText('Schweißen\t12\t3')
    expect(Object.keys(rows[0])).toEqual(['Name', 'Zykluszeit', 'Anzahl Mitarbeiter'])
  })

  it('detects and consumes a header row when the first line matches a known column name', () => {
    const rows = parseClipboardTableText('Prozessschritt\tZykluszeit\nBohren\t8')
    expect(rows).toEqual([{ Prozessschritt: 'Bohren', Zykluszeit: '8' }])
  })

  it('treats every line as data (no header consumed) when the first line matches no known column name', () => {
    const rows = parseClipboardTableText('Fräsen\t20\nBohren\t8')
    expect(rows).toHaveLength(2)
    // Only 2 cells on a line but 3 default headers -> the missing 3rd cell is
    // `null` (nullish placeholder), same convention as a short line's other
    // missing trailing cells (see "pads a short line" test below).
    expect(rows[0]).toEqual({ Name: 'Fräsen', Zykluszeit: '20', 'Anzahl Mitarbeiter': null })
  })

  it('pads a short line with null for missing trailing cells', () => {
    const rows = parseClipboardTableText('Lackieren')
    expect(rows[0]).toEqual({ Name: 'Lackieren', Zykluszeit: null, 'Anzahl Mitarbeiter': null })
  })

  // Review-Fix F8 (adversarial review, PR #355): header detection was
  // case-sensitive-exact — a differently-cased (but otherwise valid) header
  // row was misread as a DATA row, creating a phantom process step from the
  // column-header text itself.
  describe('Review-Fix F8: case-insensitive header detection', () => {
    it('detects an all-lowercase / all-uppercase header row and canonicalizes the casing for downstream column lookup', () => {
      const rows = parseClipboardTableText('prozessschritt\tZYKLUSZEIT\nBohren\t8')
      expect(rows).toEqual([{ Prozessschritt: 'Bohren', Zykluszeit: '8' }])
    })

    it('detects "Mitarbeiter" and "Anzahl Mitarbeiter" case-insensitively too', () => {
      const rows = parseClipboardTableText('name\tmitarbeiter\nBohren\t3')
      expect(rows).toEqual([{ Name: 'Bohren', Mitarbeiter: '3' }])
    })

    it('an unmatched header cell (a column this feature does not know) keeps its own text verbatim, case unchanged', () => {
      const rows = parseClipboardTableText('Name\tKommentar\nBohren\tSchnell')
      expect(rows).toEqual([{ Name: 'Bohren', Kommentar: 'Schnell' }])
    })
  })
})

describe('mapClipboardRowsToPreview', () => {
  it('maps Name/Zykluszeit/Anzahl Mitarbeiter via numericCellOrUndefined (0 vs empty vs text stay distinguishable)', () => {
    const rows = mapClipboardRowsToPreview([
      { Name: 'Kleben', Zykluszeit: '0', 'Anzahl Mitarbeiter': '2' }, // real 0
      { Name: 'Verpacken', Zykluszeit: '', 'Anzahl Mitarbeiter': null }, // genuinely empty
      { Name: 'Prüfen', Zykluszeit: 'n.a.', 'Anzahl Mitarbeiter': '1' }, // non-numeric text
    ])
    expect(rows[0]).toEqual({ name: 'Kleben', cycleTimeSec: 0, numWorkers: 2 })
    expect(rows[1]).toEqual({ name: 'Verpacken', cycleTimeSec: undefined, numWorkers: undefined })
    expect(rows[2]).toEqual({ name: 'Prüfen', cycleTimeSec: undefined, numWorkers: 1 })
  })

  it('trims and drops rows without a name', () => {
    const rows = mapClipboardRowsToPreview([{ Name: '  Bohren  ', Zykluszeit: '5' }, { Name: '   ', Zykluszeit: '9' }, { Name: '', Zykluszeit: '9' }])
    expect(rows).toEqual([{ name: 'Bohren', cycleTimeSec: 5, numWorkers: undefined }])
  })

  it('accepts "Prozessschritt" as an alternative name column, and "Mitarbeiter" as an alternative worker-count column', () => {
    const rows = mapClipboardRowsToPreview([{ Prozessschritt: 'Schleifen', Zykluszeit: '15', Mitarbeiter: '4' }])
    expect(rows).toEqual([{ name: 'Schleifen', cycleTimeSec: 15, numWorkers: 4 }])
  })

  it('end-to-end: parse + map a realistic multi-row paste', () => {
    const text = 'Prozessschritt\tZykluszeit\tAnzahl Mitarbeiter\nPressen\t30\t2\nMontieren\t45\t1\nLackieren\t\t1'
    const preview = mapClipboardRowsToPreview(parseClipboardTableText(text))
    expect(preview).toEqual([
      { name: 'Pressen', cycleTimeSec: 30, numWorkers: 2 },
      { name: 'Montieren', cycleTimeSec: 45, numWorkers: 1 },
      { name: 'Lackieren', cycleTimeSec: undefined, numWorkers: 1 },
    ])
  })

  // Review-Fix F1+F6 (adversarial review, PR #355): Excel-DE/Google-Sheets-DE
  // put comma-decimal numbers on the clipboard ("45,5", not "45.5") — the
  // reused numericCellOrUndefined only understands '.', so a real,
  // user-pasted measurement used to silently become "keine Angabe". Exact
  // acceptance cases from the fix brief.
  describe('Review-Fix F1+F6: German decimal comma tolerance (paste path only)', () => {
    it('"45,5" (comma, no dot) parses to 45.5', () => {
      const rows = mapClipboardRowsToPreview([{ Name: 'Kleben', Zykluszeit: '45,5' }])
      expect(rows[0].cycleTimeSec).toBe(45.5)
      expect(rows[0].cycleTimeUnrecognized).toBeUndefined()
    })

    it('"45.5" (dot, no comma) still parses to 45.5 — unchanged, existing behavior', () => {
      const rows = mapClipboardRowsToPreview([{ Name: 'Kleben', Zykluszeit: '45.5' }])
      expect(rows[0].cycleTimeSec).toBe(45.5)
    })

    it('"1.234,56" (both comma AND dot — thousands-separator ambiguity) is unparseable, surfaced via cycleTimeUnrecognized rather than collapsed to the same undefined an empty cell gets', () => {
      const rows = mapClipboardRowsToPreview([{ Name: 'Kleben', Zykluszeit: '1.234,56' }])
      expect(rows[0].cycleTimeSec).toBeUndefined()
      expect(rows[0].cycleTimeUnrecognized).toBe('1.234,56')
    })

    it('"0" still parses to a real 0 (leer ≠ 0 discipline unaffected by the comma fix)', () => {
      const rows = mapClipboardRowsToPreview([{ Name: 'Kleben', Zykluszeit: '0' }])
      expect(rows[0].cycleTimeSec).toBe(0)
      expect(rows[0].cycleTimeUnrecognized).toBeUndefined()
    })

    it('"" (genuinely empty cell) stays undefined with NO unrecognized flag — distinguishable from the ambiguous-comma case', () => {
      const rows = mapClipboardRowsToPreview([{ Name: 'Kleben', Zykluszeit: '' }])
      expect(rows[0].cycleTimeSec).toBeUndefined()
      expect(rows[0].cycleTimeUnrecognized).toBeUndefined()
    })

    it('the same tolerance applies to Anzahl Mitarbeiter, not just Zykluszeit', () => {
      const rows = mapClipboardRowsToPreview([{ Name: 'Kleben', 'Anzahl Mitarbeiter': '2,5' }])
      expect(rows[0].numWorkers).toBe(2.5)
    })

    it('non-numeric text ("n.a.") stays undefined with no unrecognized flag — not a locale-ambiguity case, genuinely unparseable', () => {
      const rows = mapClipboardRowsToPreview([{ Name: 'Kleben', Zykluszeit: 'n.a.' }])
      expect(rows[0].cycleTimeSec).toBeUndefined()
      expect(rows[0].cycleTimeUnrecognized).toBeUndefined()
    })
  })
})
