// Loop 8 (Roadmap): Coverage-Sicht der Capability-Matrix für die V2-Seite.
//
// Die WorkbookCapabilityMatrix wird seit KAR-959/P2 beim Ingest je Datei in
// qaf_file.g60_meta.capability_matrix persistiert — bis Loop 8 hat sie kein
// UI je gelesen. Dieses Modul ist die Lese-Boundary: Es macht aus dem
// persistierten JSONB eine anzeigbare Modulliste, ohne je zu raten.
//
// Ehrlichkeits-Regeln:
// - Eine Datei OHNE persistierten Stand (Ingest vor KAR-959, oder Schlüssel
//   fehlt/malformed) ist „kein Capability-Stand" (null) — NIE als MISSING
//   umgedeutet: MISSING ist eine geprüfte Aussage („Blatt nicht da"), null
//   heißt „nie erhoben".
// - Unbekannte Status-Werte aus der DB passieren als eigener Zustand
//   (labelDe = Rohwert) statt still zu verschwinden oder zu werfen.
// - Exit-Kriterium „QAF-Nutzer erkennen ihre Excel-Terminologie wieder":
//   die DE-Anzeigenamen folgen den Ziel-Bereichen der Roadmap (06_Autonomous_
//   Loop_Roadmap §Loop 8) — es gab bisher keine Modul→Anzeigename-Map.
//
// Pure. Kein DB-/Framework-Zugriff (check:qaf-core-portability).

import { CAPABILITY_MODULES, type CapabilityStatus } from './types'
import type { QafModule } from '../canonical-fields.types'

/** DE-Anzeigenamen je Capability-Modul — die Excel-/Roadmap-Terminologie.
 * Bewusst Record<string, …> mit Fallback auf den Rohnamen im Mapping: die
 * Matrix deckt 8 der 12 QafModule ab (CAPABILITY_MODULES); ein künftig
 * ergänztes Modul erscheint mit Rohname statt Compile-Zwang hier. */
export const CAPABILITY_MODULE_LABELS_DE: Record<string, string> = {
  SUMMARY: 'Zusammenfassung',
  MANUFACTURING: 'Fertigungskosten',
  MATERIAL: 'Material',
  SBM: 'SBM / Vorrichtungen (FWZ)',
  LOGISTICS: 'Logistik und Zoll',
  RMR: 'Rohstoffrisiken',
  LC_CN: 'Local Content (LC-CN)',
  CO2E: 'CO₂e',
}

export const CAPABILITY_STATUS_LABELS_DE: Record<CapabilityStatus, string> = {
  AVAILABLE: 'vorhanden',
  PARTIAL: 'teilweise',
  DERIVABLE: 'ableitbar',
  MISSING: 'nicht enthalten',
  PARSE_FAILED: 'nicht lesbar',
}

export interface CoverageEntry {
  module: QafModule
  labelDe: string
  /** Bekannter Status — oder null, wenn die DB einen fremden Wert führt. */
  status: CapabilityStatus | null
  /** Anzeigetext: bekannter Status übersetzt, Fremdwert als Rohtext. */
  statusLabelDe: string
}

const KNOWN_STATUS = new Set<string>(['AVAILABLE', 'PARTIAL', 'DERIVABLE', 'MISSING', 'PARSE_FAILED'])

/**
 * Liest den persistierten capability_matrix-Wert einer Datei. `null` heißt:
 * kein Capability-Stand erhoben (Alt-Ingest/malformed) — der Aufrufer zeigt
 * das als eigenen Zustand. Eine gültige Matrix liefert IMMER alle 8 Module
 * in kanonischer Reihenfolge; Module, die im persistierten Stand fehlen,
 * erscheinen als „nicht erhoben"-Eintrag (status null) statt zu verschwinden.
 */
export function coverageFromPersistedMatrix(raw: unknown): CoverageEntry[] | null {
  if (raw === null || raw === undefined || typeof raw !== 'object') return null
  const modules = (raw as { modules?: unknown }).modules
  if (!Array.isArray(modules)) return null
  // modelVersion wird bewusst NICHT gegatet: diese Sicht gibt persistierte
  // Status wieder, sie interpretiert keine Detektions-Semantik — ein
  // Versions-Gate würde Alt-Stände ausblenden, was schlechter wäre als
  // ehrliche Wiedergabe (Review-Punkt e, 10.08.).

  const byModule = new Map<string, { status?: unknown }>()
  const duplicates = new Set<string>()
  for (const m of modules) {
    if (m !== null && typeof m === 'object' && typeof (m as { module?: unknown }).module === 'string') {
      const name = (m as { module: string }).module
      if (byModule.has(name)) duplicates.add(name)
      byModule.set(name, m as { status?: unknown })
    }
  }

  const canonical: CoverageEntry[] = CAPABILITY_MODULES.map((module) => {
    const labelDe = CAPABILITY_MODULE_LABELS_DE[module] ?? module
    const entry = byModule.get(module)
    // Drei getrennte Zustände (Review-Punkte a/c): Duplikat → widersprüchlich;
    // Eintrag mit unbrauchbarem status → benannt kaputt (NICHT „nicht
    // erhoben"); Eintrag fehlt ganz → nicht erhoben.
    if (duplicates.has(module)) {
      return { module, labelDe, status: null, statusLabelDe: 'widersprüchlich (mehrere Einträge)' }
    }
    const rawStatus = entry !== undefined && typeof entry.status === 'string' ? entry.status : null
    if (rawStatus !== null && KNOWN_STATUS.has(rawStatus)) {
      const status = rawStatus as CapabilityStatus
      return { module, labelDe, status, statusLabelDe: CAPABILITY_STATUS_LABELS_DE[status] }
    }
    if (rawStatus !== null) {
      return { module, labelDe, status: null, statusLabelDe: `unbekannt (${rawStatus})` }
    }
    if (entry !== undefined) {
      return { module, labelDe, status: null, statusLabelDe: 'unbekannt (Status fehlt/ungültig)' }
    }
    return { module, labelDe, status: null, statusLabelDe: 'nicht erhoben' }
  })

  // Fremde/künftige Modul-Schlüssel (z. B. ein später ergänztes G60) werden
  // als Zusatzzeilen angehängt statt still zu verschwinden (Review-Punkt b —
  // erst damit hält der Fallback-Anspruch der Labels-Map oben).
  const extras: CoverageEntry[] = [...byModule.keys()]
    .filter((name) => !(CAPABILITY_MODULES as readonly string[]).includes(name))
    .map((name) => {
      const entry = byModule.get(name)!
      const rawStatus = typeof entry.status === 'string' ? entry.status : null
      const known = rawStatus !== null && KNOWN_STATUS.has(rawStatus)
      return {
        module: name as CoverageEntry['module'],
        labelDe: CAPABILITY_MODULE_LABELS_DE[name] ?? name,
        status: known ? (rawStatus as CapabilityStatus) : null,
        statusLabelDe: known
          ? CAPABILITY_STATUS_LABELS_DE[rawStatus as CapabilityStatus]
          : rawStatus !== null
            ? `unbekannt (${rawStatus})`
            : 'unbekannt (Status fehlt/ungültig)',
      }
    })

  return [...canonical, ...extras]
}
