// Bilingual PlausibilityIssue message support (KAR-906 / P3.2).
//
// Problem: `qaf_plausibility_issue.explanation` is a single TEXT column (see
// supabase-migration-qaf-differences.sql) — there is no `messageEn`/JSONB
// column to persist a second language into, and this task is explicitly
// scoped "KEIN Schema-Change". Several engine modules already compute a
// `messageDe`/`messageEn` pair internally (rule-engine.ts's RuleViolation,
// reconciliation.ts's ReconciliationResult, business-rules.ts's
// BusinessRuleResult, ...) but their `xToPlausibilityIssue` bridge functions
// historically dropped `messageEn` before persistence (see rule-engine.ts's
// KAR-889 "Persistence bridge" header comment: "messageEn stays available on
// the pure RuleViolation ... just not persisted today"). That meant the UI's
// language switch could never actually show an English plausibility message,
// even though the English text existed in memory for one request.
//
// This module closes that gap WITHOUT a migration: it encodes DE+EN into the
// same TEXT column using a marker prefix that cannot collide with any
// documented Fehlerreport message text, and decodes it back out at read
// time. Rows written before KAR-906 (or any row where only German is
// available) have no marker — `decodeBilingual` treats those as legacy plain
// text and falls back EN -> DE, per the task's explicit tolerance
// requirement ("Bestands-Issues ohne EN tolerant").
//
// Pure, no I/O.

const MARKER = '@@BI1@@'

/**
 * Encode a DE+EN message pair into the single string that
 * `qaf_plausibility_issue.explanation` persists. When `en` is absent or
 * identical to `de` (nothing gained from a second copy), returns `de`
 * unchanged — this keeps every existing DE-only issue (and every future one
 * a caller doesn't bother translating) byte-identical to pre-KAR-906
 * behavior, and keeps the encoded envelope only where it is load-bearing.
 */
export function encodeBilingual(de: string, en?: string): string {
  if (!en || en === de) return de
  return MARKER + JSON.stringify({ de, en })
}

export interface DecodedMessage {
  de: string
  en: string
}

/**
 * Decode a persisted `explanation` value back into { de, en }. Tolerant of:
 *   - null/undefined/empty (returns empty strings, never throws);
 *   - legacy plain-German text with no marker (both de and en resolve to the
 *     same string — the documented fallback for "Bestands-Issues ohne EN");
 *   - a corrupted/truncated envelope (falls back to treating the raw string
 *     as legacy DE text rather than throwing).
 */
export function decodeBilingual(raw: string | null | undefined): DecodedMessage {
  if (!raw) return { de: '', en: '' }
  if (raw.startsWith(MARKER)) {
    try {
      const parsed = JSON.parse(raw.slice(MARKER.length)) as { de?: unknown; en?: unknown }
      if (typeof parsed.de === 'string') {
        return { de: parsed.de, en: typeof parsed.en === 'string' ? parsed.en : parsed.de }
      }
    } catch {
      // Malformed envelope — fall through to legacy handling below.
    }
  }
  return { de: raw, en: raw }
}

/**
 * One (DE, EN) field-name-aware "missing input" reason fragment, used by the
 * reconciliation-family modules (reconciliation.ts, business-rules.ts,
 * rmr-parser.ts, logistics-parser.ts, lccn-parser.ts, co2e-parser.ts) to
 * report why a row-level/aggregate recomputation could not run
 * ("nicht_pruefbar"). Field names are supplied by the caller — normally
 * sourced from canonical-fields.ts's `labelEn` (task instruction: use the
 * canonical registry instead of ad-hoc translation) rather than invented
 * here.
 */
export function missingFieldReason(labelDe: string, labelEn: string, markedNotApplicable: boolean): DecodedMessage {
  return markedNotApplicable
    ? {
        de: `${labelDe} ist im Excel als "nicht anwendbar" markiert`,
        en: `${labelEn} is marked "not applicable" in the Excel file`,
      }
    : {
        de: `${labelDe} ist leer oder nicht als Zahl erkennbar`,
        en: `${labelEn} is empty or not recognizable as a number`,
      }
}

/** Join several `missingFieldReason` fragments into one bilingual
 * "nicht_pruefbar" reason sentence — mirrors the `${missing.join('; ')} —
 * Nachrechnung nicht moeglich.` shape every reconciliation-family module
 * already uses for the DE side. */
export function joinMissingReasons(parts: readonly DecodedMessage[]): DecodedMessage {
  return {
    de: `${parts.map((p) => p.de).join('; ')} — Nachrechnung nicht moeglich.`,
    en: `${parts.map((p) => p.en).join('; ')} — recomputation not possible.`,
  }
}
