// Tests for SemanticSheetResolver (KAR-959/P2). Synthetic sheet-name strings
// only, no real file content.

import { describe, expect, it } from 'vitest'
import { resolveSheetRole, resolveSheetRoles, sheetForRole, isPremiseSheetName, SHEET_RESOLVER_ASSUMPTIONS } from '../sheet-resolver'

describe('resolveSheetRole — module-sheet-names.ts registry signal', () => {
  it('classifies SUMMARY for both the V9 and legacy DE tab name', () => {
    expect(resolveSheetRole('SUMMARY').role).toBe('summary')
    expect(resolveSheetRole('Zusammenfassung').role).toBe('summary')
  })

  it('classifies MANUFACTURING for DE, EN, and the documented "Manufactering" typo', () => {
    expect(resolveSheetRole('Fertigungskosten').role).toBe('manufacturing')
    expect(resolveSheetRole('Manufacturing costs').role).toBe('manufacturing')
    expect(resolveSheetRole('Manufactering costs').role).toBe('manufacturing')
  })

  it('classifies MATERIAL, SBM, LOGISTICS, LC-CN, CO2E for their registry names', () => {
    expect(resolveSheetRole('MATERIAL').role).toBe('material')
    expect(resolveSheetRole('SBM-DEVICES-FWZ').role).toBe('sbm')
    expect(resolveSheetRole('LOGISTICS&CUSTOM').role).toBe('logistics')
    expect(resolveSheetRole('LC-CN').role).toBe('lccn')
    expect(resolveSheetRole('CO2e').role).toBe('co2e')
  })

  it('classifies RMR for both the EN and the real V8.8 DE tab name', () => {
    expect(resolveSheetRole('RAW MATERIAL RISKS').role).toBe('rmr')
    expect(resolveSheetRole('Rohstoffrisiken').role).toBe('rmr')
  })

  it('resolves the MATERIAL/RMR substring collision to RMR, not MATERIAL', () => {
    // "RAW MATERIAL RISK" contains the substring "material" — module-sheet-names.ts's
    // own MATERIAL alias would also match it without the fixed priority order.
    const r = resolveSheetRole('RAW MATERIAL RISK')
    expect(r.role).toBe('rmr')
    expect(r.signal).toBe('module-sheet-name-registry:RMR')
  })

  it('reports a registry-signal confidence and a non-empty reasoning string', () => {
    const r = resolveSheetRole('MATERIAL')
    expect(r.confidence).toBeGreaterThan(0)
    expect(r.reasoning.length).toBeGreaterThan(0)
    expect(r.signal).toBe('module-sheet-name-registry:MATERIAL')
  })
})

describe('resolveSheetRole — capability-layer corpus-alias extension', () => {
  it('classifies "LV Detail EU" as manufacturing (Master-Prompt §15 example, corpus finding)', () => {
    const r = resolveSheetRole('LV Detail EU')
    expect(r.role).toBe('manufacturing')
    expect(r.signal).toBe('corpus-alias:lv-detail')
  })

  it('classifies "Production Cost" as manufacturing (Master-Prompt §18 example)', () => {
    expect(resolveSheetRole('Production Cost').role).toBe('manufacturing')
  })

  it('classifies "Labor Value Detail" / "Labour Value Detail" as manufacturing', () => {
    expect(resolveSheetRole('Labor Value Detail').role).toBe('manufacturing')
    expect(resolveSheetRole('Labour Value Detail').role).toBe('manufacturing')
  })

  it('classifies Rüstkosten/Ruestkosten/Setup Cost as setup', () => {
    expect(resolveSheetRole('Rüstkosten EU').role).toBe('setup')
    expect(resolveSheetRole('Ruestkosten').role).toBe('setup')
    expect(resolveSheetRole('Setup Cost Detail').role).toBe('setup')
  })

  it('classifies a G60 INPUT-shaped sheet as input', () => {
    expect(resolveSheetRole('INPUT').role).toBe('input')
  })

  it('a registry-signal match always wins over the corpus-alias extension (Fertigungskosten is manufacturing, not accidentally matched by any extra alias)', () => {
    const r = resolveSheetRole('Fertigungskosten')
    expect(r.signal).toBe('module-sheet-name-registry:MANUFACTURING')
  })
})

describe('resolveSheetRole — unknown / foreign sheets', () => {
  it('classifies an unrelated sheet name as unknown with zero confidence and a non-empty reasoning', () => {
    const r = resolveSheetRole('Deckblatt')
    expect(r.role).toBe('unknown')
    expect(r.confidence).toBe(0)
    expect(r.reasoning.length).toBeGreaterThan(0)
  })

  it('classifies a generic Sheet1-style tab as unknown', () => {
    expect(resolveSheetRole('Sheet1').role).toBe('unknown')
  })

  it('classifies a foreign-form-looking tab name as unknown (not silently misclassified into a QAF module)', () => {
    expect(resolveSheetRole('WAF_de').role).toBe('unknown')
  })
})

describe('resolveSheetRoles — no sheet ever disappears', () => {
  it('returns exactly one assignment per input sheet, in order', () => {
    const sheets = [{ name: 'SUMMARY' }, { name: 'Fertigungskosten' }, { name: 'Deckblatt' }, { name: 'MATERIAL' }]
    const result = resolveSheetRoles(sheets)
    expect(result).toHaveLength(4)
    expect(result.map((r) => r.role)).toEqual(['summary', 'manufacturing', 'unknown', 'material'])
  })

  it('returns an empty array for an empty workbook (still no throw)', () => {
    expect(resolveSheetRoles([])).toEqual([])
  })
})

describe('sheetForRole', () => {
  it('picks the highest-confidence sheet when multiple match the same role', () => {
    const assignments = resolveSheetRoles([{ name: 'Fertigungskosten' }, { name: 'LV Detail EU' }])
    const best = sheetForRole(assignments, 'manufacturing')
    expect(best?.sheetName).toBe('Fertigungskosten')
  })

  it('returns undefined when no sheet carries the requested role', () => {
    const assignments = resolveSheetRoles([{ name: 'Deckblatt' }])
    expect(sheetForRole(assignments, 'sbm')).toBeUndefined()
  })
})

describe('SHEET_RESOLVER_ASSUMPTIONS', () => {
  it('is non-empty (Master-Prompt §15: ASSUMPTIONS explizit)', () => {
    expect(SHEET_RESOLVER_ASSUMPTIONS.length).toBeGreaterThan(0)
  })
})

// KAR-962/P5 §27 label-synonym-discovery (DuckDB dev-split sheet-name scan) —
// 'premise' role additions.
describe('resolveSheetRole — premise role (KAR-962/P5)', () => {
  it('classifies a "Prämissenblatt" tab as premise', () => {
    expect(resolveSheetRole('Prämissenblatt').role).toBe('premise')
  })

  it('classifies the RMR-specific "Prämissenblatt_Rohstoff" variant as premise too (no dedicated sub-role)', () => {
    expect(resolveSheetRole('Prämissenblatt_Rohstoff').role).toBe('premise')
  })

  it('classifies a plain "Assumptions sheet" (EN) tab as premise', () => {
    expect(resolveSheetRole('Assumptions sheet').role).toBe('premise')
  })

  it('classifies "Assumptions sheet raw material" as material, NOT premise — documented substring-priority collision', () => {
    // MODULE_PRIORITY's MATERIAL check ({name: 'material'}) runs BEFORE
    // EXTRA_ROLE_ALIASES (see resolveSheetRole), so any tab name containing
    // the substring "material" resolves to `material` first — including this
    // one, even though semantically it is an assumptions/premise sheet (43
    // dev-split hits, KAR-962/P5 §27 scan). Documented as an accepted
    // imprecision rather than reordering MODULE_PRIORITY ahead of the extra
    // aliases (that reordering risks new collisions for every OTHER
    // module-sheet-names.ts alias, well outside this PR's evidence-scoped
    // discipline) — the RMR-specific "Prämissenblatt_Rohstoff" DE variant
    // does not have this collision (see the test above) and is the more
    // common real-corpus form (166 vs. 43 dev-split hits).
    expect(resolveSheetRole('Assumptions sheet raw material').role).toBe('material')
  })

  it('classifies a dedicated "Lohnsatz" tab as premise', () => {
    expect(resolveSheetRole('Lohnsatz').role).toBe('premise')
  })

  it('a genuine "Rohstoffrisiken"/"raw material risk" tab still resolves to rmr, not premise (MODULE_PRIORITY runs first)', () => {
    expect(resolveSheetRole('Rohstoffrisiken').role).toBe('rmr')
    expect(resolveSheetRole('RAW MATERIAL RISKS').role).toBe('rmr')
  })
})

describe('isPremiseSheetName', () => {
  it('matches every premise alias substring', () => {
    expect(isPremiseSheetName('Prämissenblatt')).toBe(true)
    expect(isPremiseSheetName('Prämissenblatt_Rohstoff')).toBe(true)
    expect(isPremiseSheetName('Assumptions sheet')).toBe(true)
    expect(isPremiseSheetName('Lohnsatz')).toBe(true)
  })

  it('does not match an unrelated sheet name', () => {
    expect(isPremiseSheetName('Zusammenfassung')).toBe(false)
    expect(isPremiseSheetName('Deckblatt')).toBe(false)
  })

  it('is case-insensitive', () => {
    expect(isPremiseSheetName('PRÄMISSENBLATT')).toBe(true)
  })

  // PR #329 review fix (finding [4]): isPremiseSheetName must never disagree
  // with resolveSheetRole for the same sheet name — "Assumptions sheet raw
  // material" collides with the MATERIAL module alias (substring "material")
  // and resolveSheetRole's MODULE_PRIORITY resolves it to `material`, NOT
  // `premise` (documented, accepted imprecision — see module header
  // ASSUMPTION 2). A bare "premise"-substring-only check used to disagree
  // with that and independently also flag it as a premise sheet.
  it('agrees with resolveSheetRole on the documented MATERIAL/premise substring collision', () => {
    expect(resolveSheetRole('Assumptions sheet raw material').role).toBe('material')
    expect(isPremiseSheetName('Assumptions sheet raw material')).toBe(false)
  })
})
