// Loop 10 View-Spec-Adoption, Modul 3: buildAppendixRun — die aus den
// Anhangs-Renderern (anhang-sections.tsx) an die Build-Zeit gezogenen
// Ableitungen: Struktur-Gruppierung, Match-Unsicherheits-Zählung,
// Feld-Zuordnungs-Sichtbarkeit, Plausibilitäts-Schweregrade und -Badges.
//
// FIXTURE-DATEN-REGEL: alle Werte sind freie Erfindung, aus keiner realen
// Mappe entnommen.

import { describe, expect, it } from 'vitest'
import {
  buildAppendixRun,
  isUncertainMatch,
  plausibilityBadge,
  type AppendixPlausibility,
  type AppendixStepMatch,
  type AppendixStructureChange,
} from '../appendix-run'

const structureChange = (
  change_type: string,
  step_label = 'Station X',
): AppendixStructureChange => ({ change_type, step_label, detail: null })

const stepMatch = (
  over: Partial<AppendixStepMatch> = {},
): AppendixStepMatch => ({
  id: 'm-1',
  alt_step_id: 'a-1',
  neu_step_id: 'n-1',
  match_status: 'exact',
  confidence_score: 1,
  match_method: 'label',
  explanation: null,
  requires_review: null,
  ...over,
})

const issue = (
  severity: string,
  issue_type = 'freier_fixture_typ',
): AppendixPlausibility => ({
  issue_type,
  severity,
  step_label: null,
  field: null,
  explanation: 'Freier Fixture-Text.',
})

const EMPTY = {
  structureChanges: [],
  stepMatches: [],
  plausibility: [],
  stepsCount: 0,
  baselineFileId: null,
  comparisonFileId: null,
  baselineOverridesCount: 0,
  comparisonOverridesCount: 0,
}

describe('buildAppendixRun — Struktur-Änderungen (A2)', () => {
  it('gruppiert nach change_type und formuliert die Facts-Zeile', () => {
    const run = buildAppendixRun({
      ...EMPTY,
      structureChanges: [
        structureChange('new', 'Station 1'),
        structureChange('new', 'Station 2'),
        structureChange('removed', 'Station 3'),
        structureChange('possible_rename', 'Station 4'),
      ],
    })
    expect(run.structure.hidden).toBe(false)
    expect(run.structure.count).toBe(4)
    expect(run.structure.neu.map((s) => s.step_label)).toEqual([
      'Station 1',
      'Station 2',
    ])
    expect(run.structure.entfallen).toHaveLength(1)
    expect(run.structure.umstrukturiert).toHaveLength(1)
    expect(run.structure.factsDe).toEqual([
      '2 neu, 1 entfallen, 1 umstrukturiert',
    ])
  })

  it('lässt Null-Gruppen aus der Facts-Zeile weg', () => {
    const run = buildAppendixRun({
      ...EMPTY,
      structureChanges: [structureChange('removed')],
    })
    expect(run.structure.factsDe).toEqual(['1 entfallen'])
  })

  it('blendet die Sektion ohne Struktur-Änderungen aus', () => {
    const run = buildAppendixRun(EMPTY)
    expect(run.structure.hidden).toBe(true)
  })

  // Gepinnte Bestands-Kante (kein Neu-Verhalten): ein unbekannter
  // change_type zählt im `count`, landet aber in keiner Gruppe — und eine
  // Liste NUR aus solchen Zeilen blendet die Sektion aus. Ob diese Zeilen
  // einen eigenen Sammel-Eimer verdienen, ist eine offene Produktfrage
  // (Kommentar im Builder) — dieser Test dokumentiert den Ist-Stand.
  it('pinnt: unbekannter change_type zählt, wird aber nicht gruppiert', () => {
    const run = buildAppendixRun({
      ...EMPTY,
      structureChanges: [
        structureChange('new'),
        structureChange('voellig_unbekannt'),
      ],
    })
    expect(run.structure.count).toBe(2)
    expect(
      run.structure.neu.length +
        run.structure.entfallen.length +
        run.structure.umstrukturiert.length,
    ).toBe(1)

    const nurUnbekannt = buildAppendixRun({
      ...EMPTY,
      structureChanges: [structureChange('voellig_unbekannt')],
    })
    expect(nurUnbekannt.structure.hidden).toBe(true)
  })
})

describe('buildAppendixRun — Prozessschritt-Matching (A3)', () => {
  it('zählt unsichere Matches über beide Unsicherheits-Quellen', () => {
    const run = buildAppendixRun({
      ...EMPTY,
      stepMatches: [
        stepMatch({ id: 'm-1', requires_review: true }),
        stepMatch({ id: 'm-2', match_status: 'candidate_match' }),
        stepMatch({ id: 'm-3', match_status: 'possible_structure_change' }),
        stepMatch({ id: 'm-4' }),
      ],
    })
    expect(run.stepMatching.hidden).toBe(false)
    expect(run.stepMatching.count).toBe(4)
    expect(run.stepMatching.factsDe).toEqual([
      '3 unsicher — vor Eskalation prüfen',
    ])
  })

  it('meldet belastbare Zuordnungen, wenn nichts unsicher ist', () => {
    const run = buildAppendixRun({
      ...EMPTY,
      stepMatches: [stepMatch({ id: 'm-1' }), stepMatch({ id: 'm-2' })],
    })
    expect(run.stepMatching.factsDe).toEqual(['alle Zuordnungen belastbar'])
  })

  it('blendet die Sektion ohne Matches aus', () => {
    expect(buildAppendixRun(EMPTY).stepMatching.hidden).toBe(true)
  })

  it('isUncertainMatch: requires_review ODER unsicherer match_status', () => {
    expect(isUncertainMatch(stepMatch({ requires_review: true }))).toBe(true)
    expect(
      isUncertainMatch(stepMatch({ match_status: 'candidate_match' })),
    ).toBe(true)
    expect(isUncertainMatch(stepMatch())).toBe(false)
  })
})

describe('buildAppendixRun — Feld-Zuordnung (A3b)', () => {
  it('zeigt die Sektion nur mit Schritten UND mindestens einer Datei', () => {
    const base = { ...EMPTY, stepsCount: 3 }
    expect(
      buildAppendixRun({ ...base, baselineFileId: 'f-alt' }).fieldMapping
        .hidden,
    ).toBe(false)
    expect(
      buildAppendixRun({ ...base, comparisonFileId: 'f-neu' }).fieldMapping
        .hidden,
    ).toBe(false)
    expect(buildAppendixRun(base).fieldMapping.hidden).toBe(true)
    expect(
      buildAppendixRun({ ...EMPTY, baselineFileId: 'f-alt' }).fieldMapping
        .hidden,
    ).toBe(true)
  })

  it('summiert die Overrides beider Dateien', () => {
    const run = buildAppendixRun({
      ...EMPTY,
      stepsCount: 1,
      baselineFileId: 'f-alt',
      baselineOverridesCount: 2,
      comparisonOverridesCount: 3,
    })
    expect(run.fieldMapping.count).toBe(5)
  })
})

describe('buildAppendixRun — Plausibilität (A4)', () => {
  it('zählt Schweregrade in die severityPhrase-Facts-Zeile', () => {
    const run = buildAppendixRun({
      ...EMPTY,
      plausibility: [
        issue('kritisch'),
        issue('pruefen'),
        issue('pruefen'),
        issue('hinweis'),
      ],
    })
    expect(run.plausibility.hidden).toBe(false)
    expect(run.plausibility.count).toBe(4)
    expect(run.plausibility.factsDe).toEqual([
      '4 Befunde (1 hoch, 2 mittel, 1 Hinweis)',
    ])
  })

  it('trägt Badge-Label und -Art an jeder Zeile', () => {
    const run = buildAppendixRun({
      ...EMPTY,
      plausibility: [
        issue('kritisch', 'rule_r2_mandatory_blocked'),
        issue('hinweis', 'recon_summe_nicht_pruefbar'),
        issue('hinweis'),
      ],
    })
    expect(
      run.plausibility.rows.map((r) => [r.badgeLabel, r.badgeKind]),
    ).toEqual([
      ['Blockiert', 'blockiert'],
      ['Nicht prüfbar', 'nicht_pruefbar'],
      ['Hinweis', 'hinweis'],
    ])
  })

  it('blendet die Sektion ohne Befunde aus', () => {
    expect(buildAppendixRun(EMPTY).plausibility.hidden).toBe(true)
  })
})

// Master-Prompt §13 (aus qaf-diff-status.test.ts hierher gewandert, weil die
// Einordnungs-Regel jetzt hier lebt): Blockiert/Nicht prüfbar dürfen nie wie
// eine gewöhnliche Schwere aussehen; die Klassen-Seite derselben Regel prüft
// weiterhin qaf-diff-status.test.ts (plausibilityKindClass).
describe('plausibilityBadge — Master-Prompt §13, Einordnungs-Seite', () => {
  it("zeigt 'Blockiert' statt der bloßen Schwere", () => {
    expect(
      plausibilityBadge({
        severity: 'kritisch',
        issue_type: 'rule_r2_mandatory_blocked',
      }),
    ).toEqual({ label: 'Blockiert', kind: 'blockiert' })
  })

  it("hebt 'Nicht prüfbar' von einem gewöhnlichen Hinweis ab", () => {
    expect(
      plausibilityBadge({
        severity: 'hinweis',
        issue_type: 'recon_summe_nicht_pruefbar',
      }),
    ).toEqual({ label: 'Nicht prüfbar', kind: 'nicht_pruefbar' })
    expect(
      plausibilityBadge({ severity: 'hinweis', issue_type: 'recon_summe' }),
    ).toEqual({ label: 'Hinweis', kind: 'hinweis' })
  })

  it('fällt bei gewöhnlichen Befunden auf die Schwere zurück', () => {
    expect(plausibilityBadge({ severity: 'kritisch', issue_type: 'x' })).toEqual(
      { label: 'Kritisch', kind: 'kritisch' },
    )
    expect(plausibilityBadge({ severity: 'pruefen', issue_type: 'x' })).toEqual({
      label: 'Prüfen',
      kind: 'pruefen',
    })
  })

  it('reicht eine unbekannte Schwere roh durch, statt sie zu verlieren', () => {
    expect(
      plausibilityBadge({ severity: 'voellig_neu', issue_type: 'x' }),
    ).toEqual({ label: 'voellig_neu', kind: 'unbekannt' })
  })
})
