// Zellzustände (Kap. 6.3) — die Grundlage der zellgenauen Detektoren.

import { describe, it, expect } from 'vitest'
import {
  classifyCell,
  isErrorValue,
  isHardcodedComputation,
  looksLikeTextDate,
  referencesExternalWorkbook,
  VALUE_STATES,
  type CellSnapshot,
} from '../cell-state'

const cell = (over: Partial<CellSnapshot> = {}): CellSnapshot => ({
  sheet: 'Zusammenfassung',
  cell: 'C23',
  formula: null,
  value: 42,
  ...over,
})

describe('classifyCell', () => {
  it('unterscheidet Rechnung von fester Zahl', () => {
    expect(classifyCell(cell({ formula: 'C10*C11' }))).toBe('formula_and_cached')
    expect(classifyCell(cell({ formula: null }))).toBe('constant')
  })

  it('erkennt die leere Zelle', () => {
    expect(classifyCell(cell({ value: null }))).toBe('empty')
    expect(classifyCell(cell({ value: '   ' }))).toBe('empty')
  })

  it('erkennt den Fehlerwert auch hinter einer Formel', () => {
    // Für den Leser des Blatts zählt, dass dort kein Betrag steht — nicht, dass
    // eine Formel dahintersteht, die ihn liefern sollte.
    expect(classifyCell(cell({ formula: 'VLOOKUP(A1,X:Y,2,0)', value: '#N/A' }))).toBe('error')
  })

  it('stellt den externen Bezug über die Formel', () => {
    // Die einschneidendere Aussage: der Wert kommt aus einer Datei, die nicht
    // beiliegt.
    expect(classifyCell(cell({ formula: "'[Sub-QAF.xlsx]Blatt'!A1*2", value: 7 }))).toBe('external_link')
  })

  it('deckt genau die fünf Zustände der Spezifikation ab', () => {
    expect([...VALUE_STATES].sort()).toEqual(
      ['constant', 'empty', 'error', 'external_link', 'formula_and_cached'].sort(),
    )
  })
})

describe('isErrorValue', () => {
  it('kennt die Fehlerwerte, die in Rechenpfaden landen', () => {
    for (const v of ['#N/A', '#REF!', '#VALUE!', '#DIV/0!', '#NAME?', '#NUM!']) {
      expect(isErrorValue(v)).toBe(true)
    }
  })

  it('hält gewöhnliche Werte nicht für Fehler', () => {
    expect(isErrorValue(0)).toBe(false)
    expect(isErrorValue('n.a.')).toBe(false)
    expect(isErrorValue(null)).toBe(false)
  })
})

describe('referencesExternalWorkbook', () => {
  it('erkennt den Klammerbezug und den Pfadbezug', () => {
    expect(referencesExternalWorkbook('[1]Blatt!A1')).toBe(true)
    expect(referencesExternalWorkbook("'C:\\Ablage\\Sub.xlsx'!A1")).toBe(true)
  })

  it('hält den Bezug im selben Buch nicht für extern', () => {
    expect(referencesExternalWorkbook('Zusammenfassung!C23')).toBe(false)
    expect(referencesExternalWorkbook('SUM(A1:A9)')).toBe(false)
    expect(referencesExternalWorkbook(null)).toBe(false)
  })
})

describe('looksLikeTextDate', () => {
  it('erkennt das als Text abgelegte Datum', () => {
    expect(looksLikeTextDate(cell({ value: '07.04.2025' }))).toBe(true)
    expect(looksLikeTextDate(cell({ value: '2025-04-07' }))).toBe(true)
  })

  it('hält ein echtes Datum nicht für Text', () => {
    // Eine Zelle mit Datumswert trägt keine Zeichenkette — das ist der
    // Unterschied, um den es geht.
    expect(looksLikeTextDate(cell({ value: 45754 }))).toBe(false)
  })

  it('lässt sich vom Anzeigetext eines echten Datums nicht täuschen', () => {
    // Der Fall, an dem sich die Regel entscheidet: Ein richtig formatiertes
    // Datum zeigt „07.04.2025" an und trägt trotzdem einen Datumswert. Wer nur
    // den Anzeigetext prüft, meldet hier einen Befund, den es nicht gibt.
    expect(looksLikeTextDate(cell({ value: 45754, text: '07.04.2025' }))).toBe(false)
    expect(looksLikeTextDate(cell({ value: '07.04.2025', text: '07.04.2025' }))).toBe(true)
  })

  it('hält gewöhnlichen Text nicht für ein Datum', () => {
    expect(looksLikeTextDate(cell({ value: 'KW 14' }))).toBe(false)
    expect(looksLikeTextDate(cell({ value: '1/2 Zoll' }))).toBe(false)
  })
})

describe('isHardcodedComputation', () => {
  it('erkennt die Rechnung ohne jeden Zellbezug', () => {
    expect(isHardcodedComputation('0.31*1.11')).toBe(true)
    expect(isHardcodedComputation('12*0.76')).toBe(true)
  })

  it('lässt jede Formel mit Bezug stehen', () => {
    expect(isHardcodedComputation('C23*0.31')).toBe(false)
    expect(isHardcodedComputation('Prämissen!B4*1.11')).toBe(false)
    expect(isHardcodedComputation('SUM($A$1:$A$9)')).toBe(false)
  })

  it('hält reinen Text für keine Rechnung', () => {
    expect(isHardcodedComputation('"nicht anwendbar"')).toBe(false)
    expect(isHardcodedComputation(null)).toBe(false)
    expect(isHardcodedComputation('')).toBe(false)
  })
})
