// Loop 10 View-Spec-Adoption, Modul 3 (Anhang): Struktur-Änderungen,
// Prozessschritt-Matching, Feld-Zuordnung, Plausibilität.
//
// Muster wie overview-run.ts/negotiation-run.ts: ein pures Builder-Modul.
// Die Anhangs-Sektionen hatten als einzige der fünf Detail-Module gar keinen
// lib-Builder — Gruppierung (Struktur-Änderungen nach change_type),
// Unsicherheits-Zählung (Matches) und Schweregrad-Zählung (Plausibilität)
// liefen direkt im Renderer (view-spec-adoption-analyse.md, Abschnitt 5).
// Hier wandern genau diese Ableitungen an die Build-Zeit; die ZAHLEN und
// Zeilen selbst kommen unverändert aus denselben Rohzeilen wie bisher.
//
// Bewusst KEINE erfundenen SourceRefs: Struktur-, Match- und
// Plausibilitätszeilen sind keine Differenzen mit Zellbelegen — die
// Facts-Zahlen zählen die unmittelbar darunter gelisteten Zeilen. Ein
// Katalog-Beleg entstünde hier nur als Attrappe (U-02 verlangt Verweise für
// Anzeigezahlen aus dem Differenzkern, nicht für Listenlängen).
//
// Badge-Grenzfall (Analyse: „Regel bleibt, Ausführungszeitpunkt wandert"):
// die EINORDNUNG eines Befunds (Blockiert / Nicht prüfbar / Schweregrad)
// ist Fachregel und wird hier als semantische `badgeKind` + deutsches Label
// bestimmt; die Tailwind-Klasse dazu bleibt in qaf-diff-status.ts
// (`plausibilityKindClass`) — Klassennamen gehören laut dessen Kopfkommentar
// bewusst nicht in den portablen Kern. Gleiches Muster wie StatusChip →
// Renderer-Mapping in view-specs.ts.
//
// Pure Funktionen, keine I/O.

import { severityPhrase, unchangedPhrase } from './section-titles'

// ── Zeilentypen (aus components/detail-sections/shared.tsx hierher gezogen,
//    dort unter den alten Namen re-exportiert — der Builder ist jetzt der
//    kanonische Ort). `Appendix`-Präfix, weil `StepMatch` (Engine-Typ in
//    types.ts) und `StructureChangeRow` (Write-Shape im persistence-mapper)
//    bereits vergeben sind — dies sind die READ-Shapes der Seite. ──────────

export interface AppendixStructureChange {
  change_type: string
  step_label: string | null
  detail: string | null
}

export interface AppendixStepMatch {
  id: string
  alt_step_id: string | null
  neu_step_id: string | null
  match_status: string
  confidence_score: number | null
  match_method: string | null
  explanation: string | null
  requires_review: boolean | null
}

export interface AppendixPlausibility {
  issue_type: string
  severity: string
  step_label: string | null
  field: string | null
  explanation: string | null
  /** KAR-906/P3.2: only ever set for live-computed issues merged in by the
   * page (e.g. template_fingerprint_*) — qaf_plausibility_issue DB rows never
   * carry this column, `explanation` there is bilingual-encoded instead (see
   * PlausibilityMessage). */
  explanation_en?: string | null
}

// ── Match-Unsicherheit (Regel aus der Schale hierher gezogen) ───────────────

export const UNCERTAIN_MATCH: ReadonlySet<string> = new Set([
  'possible_structure_change',
  'candidate_match',
])

/** Ein unsicheres Match trägt seinen Betrag in A1 weiter, ohne dass man es
 *  dort noch sieht — diese Regel entscheidet über die Belastbarkeit aller
 *  Fertigungs-Deltas. */
export function isUncertainMatch(
  m: Pick<AppendixStepMatch, 'requires_review' | 'match_status'>,
): boolean {
  return Boolean(m.requires_review) || UNCERTAIN_MATCH.has(m.match_status)
}

// ── Plausibilitäts-Badge (Regel aus qaf-diff-status.ts hierher gezogen) ─────
//
// Fehlerreport-Regel-Engine R1-R6 (KAR-889/P0.4): rule violations share the
// same qaf_plausibility_issue row shape as the existing plausibility checks,
// namespaced via issue_type (`rule_r2_mandatory_warn|_blocked`, …). Only the
// badge differs when the central enforcement switch is 'block': it reads
// "Blockiert" instead of the plain severity, so a hard-blocked mandatory
// field is visually distinct from an ordinary warning.
//
// Summen-Rekonziliation (KAR-887/P0.2) reuses the same path, namespaced
// `recon_*` / `recon_*_nicht_pruefbar`. A "nicht_pruefbar" reconciliation is
// persisted as severity 'hinweis' but gets its own "Nicht prüfbar" badge —
// Master-Prompt §13: an unverifiable check must never look like a passed one
// nor an ordinary low-priority note.

export type PlausibilityBadgeKind =
  | 'blockiert'
  | 'nicht_pruefbar'
  | 'kritisch'
  | 'pruefen'
  | 'hinweis'
  | 'unbekannt'

const SEVERITY_LABEL: Record<string, string> = {
  kritisch: 'Kritisch',
  pruefen: 'Prüfen',
  hinweis: 'Hinweis',
}

export function plausibilityBadge(
  p: Pick<AppendixPlausibility, 'issue_type' | 'severity'>,
): { label: string; kind: PlausibilityBadgeKind } {
  if (p.issue_type.endsWith('_blocked')) return { label: 'Blockiert', kind: 'blockiert' }
  if (p.issue_type.endsWith('_nicht_pruefbar'))
    return { label: 'Nicht prüfbar', kind: 'nicht_pruefbar' }
  if (p.severity === 'kritisch' || p.severity === 'pruefen' || p.severity === 'hinweis')
    return { label: SEVERITY_LABEL[p.severity], kind: p.severity }
  // Unbekannter Schweregrad: rohen Wert zeigen statt raten — wie bisher
  // (`SEVERITY_LABEL[severity] ?? severity`), Farbe neutral-grau.
  return { label: p.severity, kind: 'unbekannt' }
}

// ── Run ─────────────────────────────────────────────────────────────────────

export interface PlausibilityRowSpec extends AppendixPlausibility {
  badgeLabel: string
  badgeKind: PlausibilityBadgeKind
}

export interface AppendixRun {
  structure: {
    hidden: boolean
    count: number
    factsDe: string[]
    neu: AppendixStructureChange[]
    entfallen: AppendixStructureChange[]
    umstrukturiert: AppendixStructureChange[]
  }
  stepMatching: {
    hidden: boolean
    count: number
    factsDe: string[]
  }
  fieldMapping: {
    hidden: boolean
    count: number
  }
  plausibility: {
    hidden: boolean
    count: number
    factsDe: string[]
    rows: PlausibilityRowSpec[]
  }
}

export function buildAppendixRun(input: {
  structureChanges: readonly AppendixStructureChange[]
  stepMatches: readonly AppendixStepMatch[]
  plausibility: readonly AppendixPlausibility[]
  stepsCount: number
  baselineFileId: string | null
  comparisonFileId: string | null
  baselineOverridesCount: number
  comparisonOverridesCount: number
}): AppendixRun {
  const neu = input.structureChanges.filter((s) => s.change_type === 'new')
  const entfallen = input.structureChanges.filter((s) => s.change_type === 'removed')
  const umstrukturiert = input.structureChanges.filter(
    (s) => s.change_type === 'possible_rename',
  )
  // Zeilen mit unbekanntem change_type zählen im `count`, erscheinen aber in
  // keiner Gruppe — und eine Liste NUR aus solchen Zeilen blendet die Sektion
  // aus. Das ist der Ist-Stand des Renderers, hier bewusst gepinnt statt
  // still verändert (Test dokumentiert die Kante; ob ein vierter Sammel-
  // eimer gezeigt werden soll, ist eine eigene Produktfrage).
  const structureHidden =
    neu.length === 0 && entfallen.length === 0 && umstrukturiert.length === 0

  const uncertainCount = input.stepMatches.filter(isUncertainMatch).length

  const kritisch = input.plausibility.filter((p) => p.severity === 'kritisch').length
  const pruefen = input.plausibility.filter((p) => p.severity === 'pruefen').length
  const hinweis = input.plausibility.filter((p) => p.severity === 'hinweis').length

  return {
    structure: {
      hidden: structureHidden,
      count: input.structureChanges.length,
      factsDe: [
        input.structureChanges.length === 0
          ? unchangedPhrase('Prozesskette')
          : [
              neu.length > 0 ? `${neu.length} neu` : null,
              entfallen.length > 0 ? `${entfallen.length} entfallen` : null,
              umstrukturiert.length > 0
                ? `${umstrukturiert.length} umstrukturiert`
                : null,
            ]
              .filter(Boolean)
              .join(', '),
      ],
      neu: [...neu],
      entfallen: [...entfallen],
      umstrukturiert: [...umstrukturiert],
    },
    stepMatching: {
      hidden: input.stepMatches.length === 0,
      count: input.stepMatches.length,
      factsDe: [
        uncertainCount > 0
          ? `${uncertainCount} unsicher — vor Eskalation prüfen`
          : 'alle Zuordnungen belastbar',
      ],
    },
    fieldMapping: {
      // Truthiness wie im bisherigen Renderer (nicht `!== null`) — ein
      // leerer String zählt als „keine Datei", exakt wie zuvor.
      hidden: !(
        input.stepsCount > 0 &&
        Boolean(input.baselineFileId || input.comparisonFileId)
      ),
      count: input.baselineOverridesCount + input.comparisonOverridesCount,
    },
    plausibility: {
      hidden: input.plausibility.length === 0,
      count: input.plausibility.length,
      factsDe: [severityPhrase({ high: kritisch, medium: pruefen, info: hinweis })],
      rows: input.plausibility.map((p) => {
        const badge = plausibilityBadge(p)
        return { ...p, badgeLabel: badge.label, badgeKind: badge.kind }
      }),
    },
  }
}
