/** Canonical rule-id → display label + tooltip mapping (ML01 §6). */

export interface RuleLabel {
  label: string;
  tooltip: string;
  severity?: 'error' | 'warning' | 'info';
}

const RULE_LABELS: Record<string, RuleLabel> = {
  // ── Universal refinements ─────────────────────────────────────────────────
  'u-normalise-line-endings': {
    label: 'Line endings → LF',
    tooltip: 'Mixed CRLF / LF normalised. POSIX tools and git diffs are happier.',
  },
  'u-strip-trailing-whitespace': {
    label: 'Trailing whitespace stripped',
    tooltip: 'Editors / formatters disliked trailing spaces. The engine removed them line-by-line.',
  },
  'u-collapse-blank-lines': {
    label: 'Blank lines collapsed',
    tooltip: 'Runs of 3+ blank lines collapsed to 2. Two-blank-line paragraph breaks preserved.',
  },
  'u-dedupe-imports': {
    label: 'Duplicate imports removed',
    tooltip: 'Import / using / from-import lines that appeared twice were deduped, keeping the first.',
  },

  // ── Pair-specific refinements ─────────────────────────────────────────────
  'java-hungarian-prefix': {
    label: 'Hungarian prefix stripped',
    tooltip: 'COBOL WS-/SZ- storage prefix removed; identifiers renamed to camelCase.',
  },
  'java-package-after-import': {
    label: 'package before import',
    tooltip: 'Java grammar: package must precede import. Reordered.',
  },
  'java-display-as-log': {
    label: 'DISPLAY → Logger',
    tooltip: 'COBOL DISPLAY rewritten to java.util.logging.Logger.info. SLF4J swap is a one-liner downstream.',
  },
  'vb6-csharp-drop-set': {
    label: 'VB6 Set keyword dropped',
    tooltip: 'C# uses plain = for both reference and value assignment; the Set token is meaningless.',
  },
  'vb6-csharp-msgbox': {
    label: 'MsgBox → MessageBox.Show',
    tooltip: 'Idiomatic .NET WinForms call.',
  },
  'vb6-csharp-debug-print': {
    label: 'Debug.Print → Debug.WriteLine',
    tooltip: 'C# equivalent of the VB6 immediate-window writer.',
  },
  'php-array-short-syntax': {
    label: 'array(...) → [...]',
    tooltip: 'PHP 5.4+ short-array syntax.',
  },
  'php-constructor-modernise': {
    label: 'Constructor → __construct',
    tooltip: 'PHP 8 removed PHP-4 named-as-class constructors.',
  },
  'kt-drop-jvmstatic-toplevel': {
    label: '@JvmStatic dropped (top-level)',
    tooltip: 'Annotation only meaningful inside companion object.',
  },
  'kt-drop-trailing-semicolon': {
    label: 'Trailing ; dropped',
    tooltip: 'Kotlin idiom omits end-of-line semicolons.',
  },
  'ts-var-to-let': {
    label: 'var → let',
    tooltip: 'Block-scoped binding instead of function-scoped.',
  },
  'swift-nsstring-to-string': {
    label: 'NSString → String',
    tooltip: "Swift's bridged value type, no NS prefix needed.",
  },

  // ── Universal property checks ──────────────────────────────────────────────
  'u-prop-no-untracked-todo': {
    label: 'TODO without ticket reference',
    tooltip: 'A TODO comment without a tracking ticket id was found in the output.',
    severity: 'warning',
  },
  'u-prop-no-placeholder-secret': {
    label: 'Placeholder credential',
    tooltip: 'A placeholder credential pattern was detected. Replace before deploying.',
    severity: 'warning',
  },
  'u-prop-no-real-secret-pattern': {
    label: 'Suspected real credential leak',
    tooltip: 'A real-secret pattern was detected and the snippet was auto-redacted.',
    severity: 'error',
  },
  'u-prop-no-long-line': {
    label: 'Line longer than 200 chars',
    tooltip: 'One or more output lines exceed 200 characters. Consider reformatting.',
    severity: 'info',
  },
  'u-prop-no-lorem-ipsum': {
    label: 'Lorem-ipsum copy',
    tooltip: 'Placeholder lorem-ipsum text found in the output.',
    severity: 'info',
  },

  // ── cobol→java property checks (cj-prop-*) ───────────────────────────────
  'cj-prop-comp3-bigdecimal': {
    label: 'COMP-3 → BigDecimal',
    tooltip: 'Every COMP-3 source field maps to BigDecimal.',
    severity: 'warning',
  },
  'cj-prop-no-float-for-money': {
    label: 'No float/double for money',
    tooltip: 'No double/float on money-named fields. Use BigDecimal for currency.',
    severity: 'error',
  },
  'cj-prop-no-println': {
    label: 'No System.out.println in services',
    tooltip: 'No System.out.println in services — use a Logger instead.',
    severity: 'warning',
  },
  'cj-prop-no-system-exit': {
    label: 'No System.exit() in services',
    tooltip: 'No System.exit() in generated services. Terminating the JVM is rarely correct.',
    severity: 'warning',
  },
  'cj-prop-open-try-with': {
    label: 'OPEN/CLOSE → try-with-resources',
    tooltip: 'OPEN/CLOSE pairs rewritten to use try-with-resources for safe resource cleanup.',
    severity: 'info',
  },

  // ── vb6→csharp property checks (vc-prop-*) ───────────────────────────────
  'vc-prop-no-msgbox': {
    label: 'No MsgBox calls',
    tooltip: 'No MsgBox calls — use MessageBox.Show or a dialog service.',
    severity: 'warning',
  },
  'vc-prop-no-on-error-goto': {
    label: 'On Error GoTo → try/catch',
    tooltip: 'VB6 On Error GoTo must be rewritten as try/catch.',
    severity: 'error',
  },
  'vc-prop-no-variant-dynamic': {
    label: 'dynamic needs waiver',
    tooltip: 'dynamic declarations need an explicit SCRIBA-INTEROP-OK waiver.',
    severity: 'warning',
  },
  'vc-prop-no-debug-print': {
    label: 'Debug.Print → Debug.WriteLine',
    tooltip: 'VB6 Debug.Print must become Debug.WriteLine.',
    severity: 'info',
  },
  'vc-prop-no-vb-strcomp': {
    label: 'VB6 StrComp survived',
    tooltip: 'VB6 Strings.StrComp survived in C# output — replace with string.Compare.',
    severity: 'warning',
  },

  // ── java→kotlin property checks (jk-prop-*) ──────────────────────────────
  'jk-prop-no-java-accessors': {
    label: 'Java getX/setX → Kotlin property',
    tooltip: 'Java-style getX/setX functions instead of Kotlin properties.',
    severity: 'info',
  },
  'jk-prop-no-java-null-check': {
    label: 'if (x != null) → ?.let',
    tooltip: 'Java-style null guard instead of idiomatic Kotlin ?.let { … } / guard.',
    severity: 'info',
  },
  'jk-prop-no-string-format': {
    label: 'String.format → string template',
    tooltip: 'String.format(...) instead of Kotlin string template.',
    severity: 'info',
  },
  'jk-prop-no-lateinit-nullable': {
    label: 'lateinit var on nullable type',
    tooltip: 'lateinit var declared with a nullable type — will not compile.',
    severity: 'error',
  },
  'jk-prop-no-bangbang-abuse': {
    label: 'Excessive !! assertions',
    tooltip: 'Heavy use of the !! non-null assertion (≥5 per file). Use safe calls instead.',
    severity: 'warning',
  },

  // ── javascript→typescript property checks (jt-prop-*) ────────────────────
  'jt-prop-no-var': {
    label: 'var → let/const',
    tooltip: 'Use let / const instead of var.',
    severity: 'warning',
  },
  'jt-prop-no-loose-equality': {
    label: '== → ===',
    tooltip: 'Use === / !== instead of == / !=.',
    severity: 'warning',
  },
  'jt-prop-no-untagged-any': {
    label: 'Unwaived any cast',
    tooltip: 'Explicit any without a SCRIBA-TYPE-OK waiver.',
    severity: 'warning',
  },
  'jt-prop-no-require': {
    label: 'require → import',
    tooltip: 'Use ES import instead of CommonJS require.',
    severity: 'info',
  },
  'jt-prop-no-function-type': {
    label: 'Function type → call signature',
    tooltip: 'Use a specific call signature instead of the generic Function type.',
    severity: 'info',
  },

  // ── php5→php8 property checks (php-prop-*) ───────────────────────────────
  'php-prop-no-legacy-mysql': {
    label: 'Legacy mysql_*() removed',
    tooltip: 'Legacy mysql_*() calls (removed in PHP 7) must use PDO / mysqli.',
    severity: 'error',
  },
  'php-prop-no-each': {
    label: 'each() removed in PHP 8',
    tooltip: 'each() was removed in PHP 8 — rewrite as foreach.',
    severity: 'error',
  },
  'php-prop-no-short-open-tag': {
    label: 'Short open tag → <?php',
    tooltip: 'Short open tag — use <?php.',
    severity: 'warning',
  },
  'php-prop-no-php4-ctor': {
    label: 'PHP-4 constructor',
    tooltip: 'PHP-4 constructor function ClassName() — use __construct.',
    severity: 'warning',
  },
  'php-prop-no-error-suppression-abuse': {
    label: 'Excessive @ operator',
    tooltip: 'Heavy use of the @ error-suppression operator (≥5 per file).',
    severity: 'warning',
  },
  'php-prop-no-ereg': {
    label: 'ereg*() → preg_*()',
    tooltip: 'Legacy ereg*() family — use preg_* with PCRE.',
    severity: 'error',
  },
  'php-prop-no-split': {
    label: 'split() removed',
    tooltip: 'split() was removed — use explode for plain strings, preg_split for patterns.',
    severity: 'error',
  },

  // ── python→typescript property checks (pt-prop-*) ────────────────────────
  'pt-prop-no-python-print': {
    label: 'print() → console.log',
    tooltip: 'Python print(...) instead of console.log(...).',
    severity: 'warning',
  },
  'pt-prop-no-wildcard-import': {
    label: 'Wildcard import',
    tooltip: 'Wildcard import * as is rarely idiomatic in TypeScript.',
    severity: 'info',
  },
  'pt-prop-no-record-string-any': {
    label: 'Record<string, any>',
    tooltip: 'Record<string, any> — use a concrete type or unknown with a waiver.',
    severity: 'warning',
  },
  'pt-prop-no-snake-case-export': {
    label: 'snake_case export',
    tooltip: 'Exported snake_case identifier — TypeScript uses camelCase / PascalCase.',
    severity: 'info',
  },

  // ── objc→swift property checks (os-prop-*) ───────────────────────────────
  'os-prop-no-bracket-message': {
    label: 'ObjC bracket syntax survived',
    tooltip: 'Objective-C [receiver msg:arg] survived in Swift output.',
    severity: 'error',
  },
  'os-prop-no-nil-equality': {
    label: 'if x == nil → if let / guard',
    tooltip: 'if x == nil instead of idiomatic if let x = x { … } / guard let.',
    severity: 'info',
  },
  'os-prop-no-ns-type-annotation': {
    label: 'NS* type in Swift',
    tooltip: 'NSString / NSArray / NSDictionary in type position — use Swift native types.',
    severity: 'warning',
  },
  'os-prop-no-objc-macro': {
    label: 'ObjC macro leaked',
    tooltip: 'Objective-C macro (NS_OPTIONS, NS_ENUM) leaked into Swift output.',
    severity: 'warning',
  },

  // ── cpp→rust property checks (cr-prop-*) ─────────────────────────────────
  'cr-prop-unsafe-needs-safety-comment': {
    label: 'unsafe needs SAFETY comment',
    tooltip: 'unsafe block must carry a SAFETY: justification.',
    severity: 'error',
  },
  'cr-prop-no-raw-ptr-in-api': {
    label: 'Raw pointer in public API',
    tooltip: 'Raw pointer (*mut T / *const T) in public API.',
    severity: 'error',
  },
  'cr-prop-no-mem-transmute': {
    label: 'mem::transmute',
    tooltip: 'std::mem::transmute is almost always wrong.',
    severity: 'error',
  },
  'cr-prop-no-placeholder-macro': {
    label: 'unimplemented!()/todo!() in output',
    tooltip: 'unimplemented!() / todo!() left in shipped output.',
    severity: 'warning',
  },
  'cr-prop-no-box-from-raw': {
    label: 'Box::from_raw needs SAFETY',
    tooltip: 'Box::from_raw / Box::leak use needs a SAFETY: justification.',
    severity: 'error',
  },

  // ── delphi→csharp property checks (dc-prop-*) ────────────────────────────
  'dc-prop-no-begin-end-comments': {
    label: 'Pascal begin/end as comments',
    tooltip: 'Pascal begin / end left as comments in C# output.',
    severity: 'warning',
  },
  'dc-prop-no-inc-dec': {
    label: 'Inc()/Dec() → ++/--',
    tooltip: 'Pascal Inc(...) / Dec(...) calls instead of ++ / --.',
    severity: 'warning',
  },
  'dc-prop-no-result-var': {
    label: 'Pascal Result := survived',
    tooltip: 'Stray Result := … Pascal-style return assignment in C# code.',
    severity: 'error',
  },
  'dc-prop-no-application-run': {
    label: 'Application.Run carried over',
    tooltip: 'Delphi Application.Run / Application.Initialize carried into C# Main.',
    severity: 'warning',
  },
  'dc-prop-no-pascal-assign': {
    label: ':= survived in C#',
    tooltip: 'Pascal := assignment operator survived in C# code.',
    severity: 'error',
  },

  // ── bash→python property checks (bp-prop-*) ──────────────────────────────
  'bp-prop-no-os-system': {
    label: 'os.system → subprocess.run',
    tooltip: 'os.system(...) — use subprocess.run([...]) with structured args.',
    severity: 'warning',
  },
  'bp-prop-no-shell-true': {
    label: 'subprocess shell=True',
    tooltip: 'subprocess.*(shell=True) without a security waiver.',
    severity: 'error',
  },
  'bp-prop-no-eval-exec': {
    label: 'eval/exec without waiver',
    tooltip: 'eval(...) / exec(...) calls without a security waiver.',
    severity: 'error',
  },
  'bp-prop-no-fstring-in-subprocess': {
    label: 'f-string in subprocess args',
    tooltip: 'f-string interpolation inside subprocess.run(...) argument list.',
    severity: 'error',
  },

  // ── plsql→java property checks (pj-prop-*) ───────────────────────────────
  'pj-prop-no-sql-string-concat': {
    label: 'SQL string concatenation',
    tooltip: 'SQL built via + string concatenation — SQL injection risk. Use PreparedStatement.',
    severity: 'error',
  },
  'pj-prop-no-plain-statement': {
    label: 'Statement → PreparedStatement',
    tooltip: 'Statement use — prefer PreparedStatement / parameterised queries.',
    severity: 'error',
  },
  'pj-prop-no-select-star': {
    label: 'SELECT * — name columns',
    tooltip: 'SELECT * in source — name columns explicitly.',
    severity: 'warning',
  },
  'pj-prop-no-manual-tx': {
    label: 'Manual commit/rollback',
    tooltip: 'Manual connection.commit() / rollback() outside @Transactional.',
    severity: 'warning',
  },
  'pj-prop-no-plsql-type-keyword': {
    label: '%ROWTYPE/%TYPE as comment',
    tooltip: '%ROWTYPE / %TYPE left as comment — type mapping incomplete.',
    severity: 'warning',
  },

  // ── perl→python property checks (pp-prop-*) ──────────────────────────────
  'pp-prop-no-perl-die': {
    label: 'Perl die() → raise',
    tooltip: 'Perl die(...) survived — use raise SomeException(...).',
    severity: 'error',
  },
  'pp-prop-no-perl-scope-comment': {
    label: 'Perl my/our as comment',
    tooltip: 'Perl my / our keyword left as comment — scope not translated.',
    severity: 'warning',
  },
  'pp-prop-no-perl-special-var': {
    label: 'Perl special variable survived',
    tooltip: 'Perl special variable ($_, $1, @ARGV, @_) survived as identifier.',
    severity: 'error',
  },
  'pp-prop-no-perl-pragma': {
    label: 'Perl pragma leaked',
    tooltip: 'Perl pragma (use strict; / use warnings;) leaked into Python.',
    severity: 'error',
  },
  'pp-prop-no-perl-builtin': {
    label: 'Perl built-in carried over',
    tooltip: 'Perl built-in function (chomp, wantarray, length on hash) carried over.',
    severity: 'error',
  },

  // ── target-java checks (tg-java-*) ───────────────────────────────────────
  'tg-java-no-system-exit': {
    label: 'No System.exit() in services',
    tooltip: 'System.exit(...) outside public static void main — terminates the JVM.',
    severity: 'error',
  },
  'tg-java-no-bare-thread-sleep': {
    label: 'Thread.sleep needs waiver',
    tooltip: 'Thread.sleep(...) without a SCRIBA-SLEEP-OK justification.',
    severity: 'warning',
  },
  'tg-java-no-new-thread': {
    label: 'new Thread() → ExecutorService',
    tooltip: 'new Thread(...).start() — use ExecutorService / CompletableFuture.',
    severity: 'warning',
  },
  'tg-java-no-print-stack-trace': {
    label: 'printStackTrace → Logger',
    tooltip: '.printStackTrace() — use a Logger.',
    severity: 'warning',
  },
  'tg-java-no-swallowed-exception': {
    label: 'Swallowed exception',
    tooltip: 'catch (Exception … ) { } empty body — swallowed exception.',
    severity: 'error',
  },

  // ── target-typescript checks (tg-ts-*) ───────────────────────────────────
  'tg-ts-no-console-log-in-service': {
    label: 'console.log in service',
    tooltip: 'console.log in non-CLI file — use a logger.',
    severity: 'warning',
  },
  'tg-ts-no-ts-ignore': {
    label: '@ts-ignore → @ts-expect-error',
    tooltip: '@ts-ignore — use @ts-expect-error which forces a comment.',
    severity: 'warning',
  },
  'tg-ts-no-as-any': {
    label: 'as any without waiver',
    tooltip: 'as any cast without SCRIBA-TYPE-OK waiver.',
    severity: 'warning',
  },
  'tg-ts-no-process-exit-in-service': {
    label: 'process.exit() in service',
    tooltip: 'process.exit(...) outside CLI entrypoint.',
    severity: 'error',
  },
  'tg-ts-no-debugger': {
    label: 'debugger statement',
    tooltip: 'debugger; statement left in output.',
    severity: 'warning',
  },

  // ── target-python checks (tg-py-*) ───────────────────────────────────────
  'tg-py-no-bare-except': {
    label: 'Bare except',
    tooltip: 'Bare except: / except Exception: — catch a specific type.',
    severity: 'warning',
  },
  'tg-py-no-empty-except': {
    label: 'Swallowed exception',
    tooltip: 'except ...: with pass body — swallowed exception.',
    severity: 'error',
  },
  'tg-py-no-pickle-load': {
    label: 'pickle.load without waiver',
    tooltip: 'pickle.load(...) / pickle.loads(...) without a security waiver.',
    severity: 'error',
  },
  'tg-py-no-assert-for-validation': {
    label: 'assert for runtime validation',
    tooltip: 'assert used for non-test runtime validation — skipped under -O.',
    severity: 'warning',
  },
  'tg-py-no-mutable-default-arg': {
    label: 'Mutable default argument',
    tooltip: 'Mutable default argument (list/dict) in function signature.',
    severity: 'warning',
  },

  // ── target-csharp checks (tg-cs-*) ───────────────────────────────────────
  'tg-cs-no-environment-exit': {
    label: 'Environment.Exit() in service',
    tooltip: 'Environment.Exit(...) outside CLI entrypoint.',
    severity: 'error',
  },
  'tg-cs-no-console-write-in-service': {
    label: 'Console.Write in service',
    tooltip: 'Console.WriteLine / Console.Write in non-CLI file — use ILogger.',
    severity: 'warning',
  },
  'tg-cs-no-direct-thread-start': {
    label: 'new Thread().Start() → Task',
    tooltip: 'new Thread(...).Start() — use Task / IHostedService.',
    severity: 'warning',
  },
  'tg-cs-no-swallowed-exception': {
    label: 'Swallowed exception',
    tooltip: 'catch (Exception …) { } empty body — swallowed exception.',
    severity: 'error',
  },
  'tg-cs-no-pragma-disable-without-comment': {
    label: '#pragma warning disable without comment',
    tooltip: '#pragma warning disable without an explanation comment.',
    severity: 'info',
  },

  // ── target-rust checks (tg-rs-*) ─────────────────────────────────────────
  'tg-rs-no-bare-unwrap': {
    label: '.unwrap() without justification',
    tooltip: '.unwrap() without a comment justifying infallibility.',
    severity: 'warning',
  },
  'tg-rs-no-empty-expect': {
    label: '.expect("") empty message',
    tooltip: '.expect("") with empty message — panics without context.',
    severity: 'warning',
  },
  'tg-rs-no-println-in-lib': {
    label: 'println!() in library',
    tooltip: 'println!() in non-binary file — library crates use log / tracing.',
    severity: 'info',
  },
  'tg-rs-no-process-exit-in-lib': {
    label: 'process::exit() in library',
    tooltip: 'process::exit() in library crate.',
    severity: 'error',
  },
  'tg-rs-no-clone-chain': {
    label: 'Excessive .clone() chain',
    tooltip: 'Long chain of .clone() calls — consider borrowing instead.',
    severity: 'info',
  },

  // ── target-go checks (tg-go-*) ───────────────────────────────────────────
  'tg-go-no-panic-in-service': {
    label: 'panic() in service',
    tooltip: 'panic(...) outside main / test files.',
    severity: 'error',
  },
  'tg-go-no-fmt-println-in-service': {
    label: 'fmt.Println in service',
    tooltip: 'fmt.Println / fmt.Printf in service code — use log / slog.',
    severity: 'warning',
  },
  'tg-go-no-os-exit-in-service': {
    label: 'os.Exit() outside main',
    tooltip: 'os.Exit(...) outside main.',
    severity: 'error',
  },
  'tg-go-no-error-discard': {
    label: 'Error discarded',
    tooltip: 'Error discarded with _ = … or _, _ = … — handle or wrap.',
    severity: 'error',
  },

  // ── target-swift checks (tg-swift-*) ─────────────────────────────────────
  'tg-swift-no-fatal-error': {
    label: 'fatalError() without justification',
    tooltip: 'fatalError(...) without justification.',
    severity: 'error',
  },
  'tg-swift-no-force-try': {
    label: 'try! without waiver',
    tooltip: 'try! force-try without SCRIBA-TRY-OK waiver.',
    severity: 'error',
  },
  'tg-swift-no-force-cast': {
    label: 'as! force-cast',
    tooltip: 'as! force-cast in non-test code.',
    severity: 'error',
  },
  'tg-swift-no-print-in-service': {
    label: 'print() in service',
    tooltip: 'print(...) in non-CLI / non-test file — use os.Logger.',
    severity: 'warning',
  },
  'tg-swift-no-iuo-property': {
    label: 'Implicitly unwrapped optional',
    tooltip: 'Implicitly unwrapped optional (Type!) as stored property.',
    severity: 'warning',
  },

  // ── B3 KG cross-reference rules (cobol → java) ───────────────────────────
  'cj-prop-kg-comp3-mapping': {
    label: 'COMP-3 → BigDecimal precision',
    tooltip: 'COMP-3 packed-decimal fields must map to BigDecimal with the correct scale. Loss of scale is a silent numeric defect.',
    severity: 'error',
  },
  'cj-prop-kg-occurs-mapping': {
    label: 'OCCURS N → array / List',
    tooltip: 'OCCURS N TIMES table must map to a fixed-size array or List initialised to N elements.',
    severity: 'error',
  },
  'cj-prop-kg-88level-enum': {
    label: '88-level → enum constant',
    tooltip: '88-level condition names must map to Java enum values, not magic literals.',
    severity: 'warning',
  },
  'cj-prop-kg-picx-length': {
    label: 'PIC X(N) length constraint',
    tooltip: 'PIC X(N) character field must carry a @Size(max=N) or equivalent length constraint in Java.',
    severity: 'warning',
  },
  'cj-prop-kg-redefines-mapping': {
    label: 'REDEFINES → union / view',
    tooltip: 'REDEFINES storage aliasing must be represented as a union type or explicit view in Java.',
    severity: 'warning',
  },
  'cj-prop-kg-file-status-mapping': {
    label: 'FILE STATUS code mapping',
    tooltip: 'FILE STATUS codes (00, 10, 23, …) must map to named constants or checked exceptions in Java.',
    severity: 'warning',
  },

  // ── B3 KG cross-reference rules (plsql → java) ───────────────────────────
  'pj-prop-kg-number-mapping': {
    label: 'NUMBER(p,s) → BigDecimal',
    tooltip: 'PL/SQL NUMBER(p,s) must map to BigDecimal with matching precision and scale, not double or float.',
    severity: 'error',
  },
  'pj-prop-kg-varchar2-length': {
    label: 'VARCHAR2(n) length constraint',
    tooltip: 'VARCHAR2(n) must carry a matching length constraint on the Java field.',
    severity: 'warning',
  },
  'pj-prop-kg-exception-mapping': {
    label: 'EXCEPTION WHEN → specific exception',
    tooltip: 'PL/SQL named exceptions must map to specific Java exception classes, not a bare catch(Exception e).',
    severity: 'error',
  },

  // ── B3 KG cross-reference rules (vb6 → csharp) ───────────────────────────
  'vc-prop-kg-integer-width': {
    label: 'VB6 Integer/Long width',
    tooltip: 'VB6 Integer (16-bit) must map to short, not int. VB6 Long (32-bit) must map to int, not long.',
    severity: 'error',
  },
  'vc-prop-kg-fixed-string-length': {
    label: 'String * N length attribute',
    tooltip: 'VB6 fixed-length String * N declaration must carry a matching length constraint in C#.',
    severity: 'warning',
  },
  'vc-prop-kg-byref-semantics': {
    label: 'ByRef parameter semantics',
    tooltip: 'VB6 ByRef parameters must map to C# ref or out — ByVal-by-default in C# does not preserve mutation semantics.',
    severity: 'error',
  },

  // ── B3 KG cross-reference rules (perl → python) ──────────────────────────
  'pp-prop-kg-context-mapping': {
    label: '@array / %hash context',
    tooltip: 'Perl @arr must map to list, %hash to dict — not a generic variable whose type is inferred.',
    severity: 'error',
  },
  'pp-prop-kg-autovivification': {
    label: 'Autovivification → defaultdict',
    tooltip: 'Perl autovivification (nested hash auto-creation) must map to collections.defaultdict or setdefault().',
    severity: 'error',
  },

  // ── B3 KG cross-reference rules (java → kotlin) ──────────────────────────
  'jk-prop-kg-nullability-preservation': {
    label: '@Nullable / @NotNull preservation',
    tooltip: 'Java @Nullable must become T? in Kotlin; @NotNull must become T (not T?). Erasing nullability is a type-safety regression.',
    severity: 'warning',
  },
  'jk-prop-kg-generic-preservation': {
    label: 'Generic type preservation',
    tooltip: 'Java List<T> must remain List<T> in Kotlin — erasing to raw List is a type-safety regression.',
    severity: 'warning',
  },

  // ── B3 KG cross-reference rules (c → rust) ───────────────────────────────
  'cr-prop-kg-pointer-safety': {
    label: 'Pointer-safety preservation',
    tooltip: 'C pointer operations must map to safe Rust equivalents (references, slices). Raw pointer blocks in unsafe{} require explicit justification.',
    severity: 'error',
  },
  'cr-prop-kg-malloc-ownership': {
    label: 'malloc/free → owned Box/Vec',
    tooltip: 'C malloc/free pairs must lift to owned Rust types (Box<T>, Vec<T>). Leaking ownership is undefined behaviour in Rust.',
    severity: 'error',
  },

  // ── B3 KG cross-reference rules (php → typescript) ───────────────────────
  'pt-prop-kg-class-property-typing': {
    label: 'Class property type + nullability',
    tooltip: 'PHP class property types (including nullable ?) must be preserved as TypeScript property type annotations.',
    severity: 'warning',
  },
};

export type RuleCategory = 'universal' | 'target-language' | 'pair' | 'kg' | 'other';

export function categoryOf(ruleId: string): RuleCategory {
  if (ruleId.startsWith('u-prop-') || ruleId.startsWith('u-')) return 'universal';
  if (ruleId.startsWith('tg-')) return 'target-language';
  if (ruleId.includes('-prop-kg-')) return 'kg';
  if (/^[a-z]{2}-prop-/.test(ruleId)) return 'pair';
  return 'other';
}

export function getRuleLabel(ruleId: string): RuleLabel {
  return (
    RULE_LABELS[ruleId] ?? {
      label: ruleId.replace(/-/g, ' '),
      tooltip: `Engine rule: ${ruleId}`,
    }
  );
}

export function getRuleSeverityClass(ruleId: string): string {
  const rule = RULE_LABELS[ruleId];
  if (!rule?.severity) return 'text-muted';
  switch (rule.severity) {
    case 'error': return 'text-red-400';
    case 'warning': return 'text-amber-400';
    case 'info': return 'text-blue-400';
  }
}
