/**
 * Diagnostic message catalog for Acurity Architect.
 * 
 * All user-facing diagnostic messages are assigned stable IDs (ACU-DOMAIN-NNN).
 * This enables:
 * - Robust test matching (ID + params instead of substring)
 * - Future localization without code changes
 * - Per-diagnostic suppression and configuration
 * 
 * Format: ACU-<domain>-<nnn>
 * Domains: INT, KWD, TYPE, FNC, BLK, NMG, VAR, INC, SYN, PAT, TODO, ARG
 *
 * Each entry carries a `category` field:
 *   "validity"  — will fail or produce wrong results at runtime
 *   "standards" — interpreter-indifferent; pure coding convention
 *   "advisory"  — likely problematic in practice but not guaranteed to error
 */

const DIAGNOSTIC_CATALOG = {
  // ============================================================================
  // ACU-INT: Interpolation validation (^variable^ syntax in output lines)
  // ============================================================================
  "ACU-INT-001": {
    ruleId: "interpolationUndefined",
    message: "Variable '{variableName}' is not defined.",
    paramKeys: ["variableName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-INT-002": {
    ruleId: "interpolationCaseMismatch",
    message: "Variable '{variableName}' capitalisation differs from declared variable '{declaredName}'.",
    paramKeys: ["variableName", "declaredName"],
    surfaces: ["ide", "cicd"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-INT-003": {
    ruleId: "interpolationUnclosed",
    message: "Unclosed string interpolation for '{variableName}'. Expected '^{variableName}^'.",
    paramKeys: ["variableName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },

  // ============================================================================
  // ACU-KWD: Keyword capitalization (IF, VAR, SET, etc. must be uppercase)
  // ============================================================================
  "ACU-KWD-001": {
    ruleId: "keywordCapitalization",
    message: "Keyword '{keyword}' should be capitalized as '{uppercase}'.",
    paramKeys: ["keyword", "uppercase"],
    surfaces: ["ide"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "error"
    }
  },

  // ============================================================================
  // ACU-TYPE: Type validation (type keywords and type checking)
  // ============================================================================
  "ACU-TYPE-001": {
    ruleId: "typeKeywordCapitalization",
    message: "Type keyword '{keyword}' should be capitalized as '{uppercase}'.",
    paramKeys: ["keyword", "uppercase"],
    surfaces: ["ide"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-TYPE-002": {
    ruleId: "typeMismatch",
    message: "Type mismatch: Cannot assign {sourceType} to {targetType} variable '{target}'.",
    paramKeys: ["sourceType", "targetType", "target"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-TYPE-003": {
    ruleId: "discouragedIntegerType",
    message: "Type 'INTEGER' is discouraged. Use 'LONG' instead.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "advisory",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "error"
    }
  },

  // ============================================================================
  // ACU-FNC: Function validation (calls, declarations, signatures)
  // ============================================================================
  "ACU-FNC-001": {
    ruleId: "functionUndefined",
    message: "Function '{functionName}' is not defined.",
    paramKeys: ["functionName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-FNC-002": {
    ruleId: "functionOutOfScope",
    message: "Function '{functionName}' is not yet defined at this point.",
    paramKeys: ["functionName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-FNC-003": {
    ruleId: "functionRedeclared",
    message: "Function '{functionName}' is declared multiple times. Functions cannot be redeclared.",
    paramKeys: ["functionName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-FNC-004": {
    ruleId: "functionConstantUsage",
    message: "'{functionName}' is a system constant and should be used without parentheses.",
    paramKeys: ["functionName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-FNC-005": {
    ruleId: "functionCallUnclosed",
    message: "Unclosed function call for '{functionName}'.",
    paramKeys: ["functionName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-FNC-006": {
    ruleId: "functionSignature",
    message: "Function signature error: {errorDetail}",
    paramKeys: ["errorDetail"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-FNC-007": {
    ruleId: "functionCaseMismatch",
    message: "Function '{functionName}' capitalisation differs from declared function '{declaredName}'.",
    paramKeys: ["functionName", "declaredName"],
    surfaces: ["ide", "cicd"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-FNC-008": {
    ruleId: "functionReturnValueUnused",
    message: "Function '{functionName}' return value is unused. Consider assigning function return to a variable.",
    paramKeys: ["functionName"],
    surfaces: ["ide", "cicd"],
    category: "advisory",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-FNC-009": {
    ruleId: "functionArgumentType",
    message: "Argument {n} to '{function}': expected {expectedType}, got {actualType}.",
    paramKeys: ["n", "function", "expectedType", "actualType"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-FNC-010": {
    ruleId: "functionReturnTypeMismatch",
    message: "Return type mismatch in '{functionName}': declared return type is {expectedType}, but '{expression}' is {actualType}.",
    paramKeys: ["functionName", "expectedType", "expression", "actualType"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-FNC-011": {
    ruleId: "functionMissingReturn",
    message: "Function '{functionName}' declares return type {returnType} but contains no RETURN statement.",
    paramKeys: ["functionName", "returnType"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-FNC-012": {
    ruleId: "functionCallWhitespace",
    message: "Whitespace is not allowed between '{name}' and '('.",
    paramKeys: ["name"],
    surfaces: ["ide", "cicd"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "off",
      strict: "error"
    }
  },

  "ACU-FNC-013": {
    ruleId: "functionBuiltinCollision",
    message: "'{functionName}' is a built-in function. User-defined functions cannot shadow built-in names. Wrap in IF NOT SYMBOL('{functionName}') ... ENDIF to define a conditional polyfill.",
    paramKeys: ["functionName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },

  "ACU-FNC-014": {
    ruleId: "functionDeclarationMissingParamType",
    message: "Parameter '{paramName}' in function '{functionName}' is missing a type annotation. Each parameter must be declared as 'paramName : TYPE'.",
    paramKeys: ["paramName", "functionName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },

  "ACU-FNC-015": {
    ruleId: "functionDeclarationMissingReturnType",
    message: "Function '{functionName}' is missing a return type. Add ': TYPE' after the closing parenthesis (e.g., ': STRING').",
    paramKeys: ["functionName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },

  "ACU-FNC-016": {
    ruleId: "functionDeprecated",
    message: "'{functionName}' is a deprecated/legacy function.",
    paramKeys: ["functionName"],
    surfaces: ["ide", "cicd"],
    category: "advisory",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },

  // ============================================================================
  // ACU-OUT:Output-formatting directives (CENTER, COL, FROM, TAB, TO) and
  //          OPTION directive output configuration (OICC, CICC)
  // ============================================================================
  "ACU-OUT-001": {
    ruleId: "outputDirectiveInCodeContext",
    message: "'{name}' is an output-positioning directive, not a function. It can only be used between OICC/CICC delimiters on output lines (e.g., >>@{name}(...)@).",
    paramKeys: ["name"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-OPT-001": {
    ruleId: "invalidOptionCharacter",
    message: "'{char}' is not a valid ICC/OICC/CICC character. Allowed characters are: {allowed}.",
    paramKeys: ["char", "allowed"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-OUT-002": {
    ruleId: "outputDirectiveArgumentCount",
    message: "'{name}' expects {expected} argument(s) but was called with {actual}.",
    paramKeys: ["name", "expected", "actual"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-OUT-003": {
    ruleId: "outputDirectiveColumnRange",
    message: "Column value {value} is out of the valid range (1 to 16000).",
    paramKeys: ["value"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-OPT-002": {
    ruleId: "nonDefaultIccCharacter",
    message: "Custom ICC character '{char}' — IDE interpolation support requires the default '#'.",
    paramKeys: ["char"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },

  // ============================================================================
  // ACU-MAC: Macro validation (declarations, invocations, parameter counts)
  // ============================================================================
  "ACU-MAC-001": {
    ruleId: "macroRedeclared",
    message: "Macro '{macroName}' is declared multiple times. Macros cannot be redeclared.",
    paramKeys: ["macroName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-MAC-002": {
    ruleId: "macroCaseMismatch",
    message: "Macro '{usedName}' capitalisation differs from declared macro '{declaredName}'.",
    paramKeys: ["usedName", "declaredName"],
    surfaces: ["ide", "cicd"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-MAC-003": {
    ruleId: "macroUnexpectedParameters",
    message: "Macro '{macroName}' expects no parameters but was called with {actualCount}.",
    paramKeys: ["macroName", "actualCount"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-MAC-004": {
    ruleId: "macroMissingParameters",
    message: "Macro '{macroName}' expects {expectedCount} parameter(s) but was called without parentheses.",
    paramKeys: ["macroName", "expectedCount"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-MAC-005": {
    ruleId: "macroParameterCountMismatch",
    message: "Macro '{macroName}' expects {expectedCount} parameter(s) but was called with {actualCount}.",
    paramKeys: ["macroName", "expectedCount", "actualCount"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },

  // ============================================================================
  // ACU-REQ: REQUIRE statement validation (file existence)
  // ============================================================================
  "ACU-INC-004": {
    ruleId: "requireFileNotFound",
    message: "REQUIRE: File '{filename}' not found.",
    paramKeys: ["filename"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },

  // ============================================================================
  "ACU-BLK-001": {
    ruleId: "blockStructure",
    message: "Unexpected {closeKeyword} without matching {openKeyword}.",
    paramKeys: ["closeKeyword", "openKeyword"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-BLK-002": {
    ruleId: "blockStructure",
    message: "{closeKeyword} does not match {openKeyword} at line {lineNumber}.",
    paramKeys: ["closeKeyword", "openKeyword", "lineNumber"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-BLK-003": {
    ruleId: "blockStructure",
    message: "'{openKeyword}' at line {lineNumber} is not closed with '{closeKeyword}'.",
    paramKeys: ["openKeyword", "lineNumber", "closeKeyword"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-BLK-004": {
    ruleId: "blockIndentation",
    message: "'{keyword}' indentation ({column}) does not match '{openKeyword}' at line {lineNumber} (indentation {openColumn}).",
    paramKeys: ["keyword", "column", "openKeyword", "lineNumber", "openColumn"],
    surfaces: ["ide"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "off",
      strict: "warning"
    }
  },
  "ACU-BLK-005": {
    ruleId: "elseIfMisuse",
    message: "'ELSE IF' is not a conditional — the IF condition is ignored and the ELSE branch always executes. Restructure with nested IF or CASE.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-BLK-006": {
    ruleId: "duplicateElse",
    message: "Duplicate ELSE branch — an IF block may only have one ELSE.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },

  // ============================================================================
  // ACU-NAM: Naming conventions (prefix validation)
  // ============================================================================
  "ACU-NAM-001": {
    ruleId: "namingDatabaseFieldDeclaration",
    message: "ERROR: Declaring variables with an 'h' prefix is strongly discouraged, as variables may be confused with database fields.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "standards",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-NAM-002": {
    ruleId: "namingPrefixScope",
    message: "Naming convention (scope): '{variableName}' has scope prefix '{actualPrefix}' ({actualScope}) but is {expectedScope}. Consider '{recommendedName}'.",
    paramKeys: ["variableName", "actualPrefix", "actualScope", "expectedScope", "recommendedName"],
    surfaces: ["ide"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "off",
      strict: "error"
    }
  },
  "ACU-NAM-003": {
    ruleId: "namingPrefixType",
    message: "Naming convention (type): '{variableName}' has type prefix suggesting {expectedType} but is declared as {actualType}. Consider '{recommendedName}'.",
    paramKeys: ["variableName", "expectedType", "actualType", "recommendedName"],
    surfaces: ["ide"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "off",
      strict: "error"
    }
  },

  // ============================================================================
  // ACU-VAR: Variable validation (declaration, usage, scope)
  // ============================================================================
  "ACU-VAR-001": {
    ruleId: "undefinedVariable",
    message: "{variableName} is not defined.",
    paramKeys: ["variableName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-VAR-002": {
    ruleId: "unsetVariable",
    message: "{variableName} is not set.",
    paramKeys: ["variableName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "off",
      strict: "error"
    }
  },
  "ACU-VAR-003": {
    ruleId: "unclosedString",
    message: "String is unclosed.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-VAR-004": {
    ruleId: "pseudoConstantMultipleAssignment",
    message: "Pseudo-constant '{variableName}' is assigned multiple times. First assigned on line {firstLine}.",
    paramKeys: ["variableName", "firstLine"],
    surfaces: ["ide", "cicd"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-VAR-005": {
    ruleId: "nearMissConstantName",
    message: "'{variableName}' is not defined. Did you mean '{suggestion}'?",
    paramKeys: ["variableName", "suggestion"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-VAR-006": {
    ruleId: "readOnlyGlobalVariableAssignment",
    message: "'{variableName}' is a read-only built-in global variable and cannot be assigned.",
    paramKeys: ["variableName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-VAR-007": {
    ruleId: "declaredButNeverUsedVariable",
    message: "'{variableName}' is declared but never used.",
    paramKeys: ["variableName"],
    surfaces: ["ide", "cicd"],
    category: "standards",
    severity: {
      default: "off",
      legacy: "off",
      strict: "warning"
    }
  },

  // ============================================================================
  // ACU-INC: Include resolution diagnostics
  // ============================================================================
  "ACU-INC-001": {
    ruleId: "includeCircular",
    message: "Circular include detected for '{includeFilename}'.",
    paramKeys: ["includeFilename"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-INC-002": {
    ruleId: "includeFilenameCaseMismatch",
    message: "Include filename case differs from actual file '{actualFilename}' for '{includeFilename}'.",
    paramKeys: ["includeFilename", "actualFilename"],
    surfaces: ["ide", "cicd"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-INC-003": {
    ruleId: "includeFileNotFound",
    message: "INCLUDE: File '{filename}' not found.",
    paramKeys: ["filename"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },

  // ============================================================================
  // ACU-SYN: Syntax and style recommendations
  // ============================================================================
  "ACU-SYN-001": {
    ruleId: "redundantHashOn",
    message: "Redundant '#HASH-ON' directive. Reports are HASH-ON by default until HASH-OFF is encountered.",
    paramKeys: [],
    surfaces: ["ide"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "off",
      strict: "error"
    }
  },
  "ACU-SYN-002": {
    ruleId: "invalidExecutableLine",
    message: "Unexpected token or unrecognized statement in executable context. Use '//' for comments or '>>' for output text.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-SYN-003": {
    ruleId: "preferSlashSlashComments",
    message: "Prefer '//' comments over legacy '#NOTE' directives.",
    paramKeys: [],
    surfaces: ["ide"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-SYN-004": {
    ruleId: "doIteratorMissingClause",
    message: "DO iterator '{variable}' has no TO or BY clause. Use 'DO {variable} = start TO end' or 'DO {variable} = start TO end BY step'.",
    paramKeys: ["variable"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-SYN-005": {
    ruleId: "doIteratorNoUpperBound",
    message: "DO {variable}: This loop runs indefinitely (no BREAK / TO / UNTIL / WHILE).",
    paramKeys: ["variable"],
    surfaces: ["ide", "cicd"],
    category: "advisory",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-SYN-006": {
    ruleId: "incompleteAssignment",
    message: "Assignment statement is incomplete — no value on the right-hand side.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-SYN-007": {
    ruleId: "invalidContinuation",
    message: "The continuation operator '...' must appear at the end of a line, before any comment. Code cannot follow '...' on the same line.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-SYN-008": {
    ruleId: "unexpectedTokensAfterStatement",
    message: "Unexpected tokens after {keyword} statement — expected end of line.",
    paramKeys: ["keyword"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },

  "ACU-SYN-009": {
    ruleId: "untilMissingCondition",
    message: "'UNTIL' requires a condition expression. Unexpected end of line.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },

  // ============================================================================
  // ACU-RBD: ROBODOC validation
  // ============================================================================
  "ACU-RBD-001": {
    ruleId: "robodocMissingStart",
    message: "Missing ROBODOC start marker. Expected '//@docStart*...* ...'.",
    paramKeys: [],
    surfaces: ["ide"],
    category: "standards",
    severity: {
      default: "information",
      legacy: "off",
      strict: "warning"
    }
  },
  "ACU-RBD-002": {
    ruleId: "robodocMissingEnd",
    message: "Missing ROBODOC end marker. Expected '//@docEnd'.",
    paramKeys: [],
    surfaces: ["ide"],
    category: "standards",
    severity: {
      default: "information",
      legacy: "off",
      strict: "warning"
    }
  },
  "ACU-RBD-003": {
    ruleId: "robodocKindMismatch",
    message: "ROBODOC kind mismatch. Expected {expectedKind} tag '{expectedTag}' but found '{actualTag}'.",
    paramKeys: ["expectedKind", "expectedTag", "actualTag"],
    surfaces: ["ide"],
    category: "standards",
    severity: {
      default: "information",
      legacy: "off",
      strict: "warning"
    }
  },
  "ACU-RBD-004": {
    ruleId: "robodocTargetMismatch",
    message: "ROBODOC header target mismatch. Expected '{expectedTarget}' but found '{actualTarget}'.",
    paramKeys: ["expectedTarget", "actualTarget"],
    surfaces: ["ide"],
    category: "standards",
    severity: {
      default: "information",
      legacy: "off",
      strict: "warning"
    }
  },
  "ACU-RBD-005": {
    ruleId: "robodocMissingSection",
    message: "ROBODOC section '{sectionName}' is missing from the doc block.",
    paramKeys: ["sectionName"],
    surfaces: ["ide"],
    category: "standards",
    severity: {
      default: "information",
      legacy: "off",
      strict: "warning"
    }
  },

  // ============================================================================
  // ACU-PAT: Higher-level usage patterns
  // ============================================================================
  "ACU-PAT-001": {
    ruleId: "indexUsageMissingClose",
    message: "Index cursor operations detected without an INDEX_CLOSE call in the same scope.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "advisory",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-002": {
    ruleId: "indexUsageSaveRestorePair",
    message: "INDEX_SAVE_POSITION and INDEX_RESTORE_POSITION should be used as a pair in the same scope.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "advisory",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-003": {
    ruleId: "indexUsageSaveRestoreRecommendation",
    message: "Consider wrapping index cursor movement with INDEX_SAVE_POSITION and INDEX_RESTORE_POSITION.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "advisory",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-004": {
    ruleId: "indexReadBeforeLoad",
    message: "INDEX_READ_BY_KEY should occur after INDEX_LOAD_KEY_* in standard flow.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-005": {
    ruleId: "indexReadBeforeOpen",
    message: "INDEX_READ_BY_KEY should occur after INDEX_OPEN_* in standard flow.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-006": {
    ruleId: "indexCloseBeforeRead",
    message: "INDEX_CLOSE should occur after cursor operations (LOAD/READ) in standard flow.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-007": {
    ruleId: "arrayOpsWithoutInit",
    message: "Array record operations on '{handle}' detected without an INIT_SORTED_ARRAY call in the same scope.",
    paramKeys: ["handle"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-008": {
    ruleId: "arrayInitWithoutFree",
    message: "INIT_SORTED_ARRAY called without a corresponding FREE_SORTED_ARRAY or FIN_SORTED_ARRAY.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "advisory",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-009": {
    ruleId: "arrayFreeBeforeOps",
    message: "Array record operation on '{handle}' after FREE_SORTED_ARRAY. Free the handle only after all operations are complete.",
    paramKeys: ["handle"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-010": {
    ruleId: "staleIndexHandle",
    message: "'{handle}' was passed to INDEX_CLOSE and may no longer refer to a valid index cursor.",
    paramKeys: ["handle"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },

  "ACU-PAT-011": {
    ruleId: "getUnusedHandleOverwrite",
    message: "'{variable}' is used as both the assignment target and the argument to GET_UNUSED_ARRAY_HANDLE, overwriting the handle with the status code.",
    paramKeys: ["variable"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-012": {
    ruleId: "consecutiveGetUnusedHandle",
    message: "GET_UNUSED_ARRAY_HANDLE called again before INIT_SORTED_ARRAY was called for the previous handle. Both calls will return the same handle value.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },

  "ACU-PAT-013": {
    ruleId: "indexRestoreWithoutSave",
    message: "INDEX_RESTORE_POSITION('{tableCode}'): no active save for this table code.",
    paramKeys: ["tableCode"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-014": {
    ruleId: "indexLoadKeyTypeMismatch",
    message: "Index key at position {pos} expects type {expected} but '{callName}' loads a {actual} value.",
    paramKeys: ["pos", "expected", "callName", "actual"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-015": {
    ruleId: "indexLoadKeyCountExceeded",
    message: "Index '{indexName}' on table '{tableCode}' has {total} key segment(s). This load exceeds that count.",
    paramKeys: ["indexName", "tableCode", "total"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-016": {
    ruleId: "indexHandleOverwrite",
    message: "Assigning to '{handle}' will overwrite an active index cursor handle.",
    paramKeys: ["handle"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-PAT-017": {
    ruleId: "indexLoadKeyStringTooLong",
    message: "Length argument {length} exceeds the maximum width of segment {pos} ('{field}', max: {maxWidth}).",
    paramKeys: ["length", "pos", "field", "maxWidth"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },

  // ============================================================================
  // ACU-STY: Strict coding-style rules (active only in // @mode:strict files)
  // ============================================================================
  "ACU-STY-003": {
    ruleId: "emptyDateLiteral",
    message: "Date literal '00/00/0000' is discouraged. Use MIN_DATE instead.",
    paramKeys: [],
    surfaces: ["ide", "cicd"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "off",
      strict: "error"
    }
  },
  "ACU-STY-004": {
    ruleId: "operatorSpacing",
    message: "Operator '{operator}' must have a single space on each side.",
    paramKeys: ["operator"],
    surfaces: ["ide", "cicd"],
    category: "standards",
    severity: {
      default: "off",
      legacy: "off",
      strict: "warning"
    }
  },
  "ACU-STY-005": {
    ruleId: "discouragedHardcodedArrayHandle",
    message: "Hard-coded integer '{handle}' used as an array handle. Obtain the handle via GET_UNUSED_ARRAY_HANDLE() instead.",
    paramKeys: ["handle"],
    surfaces: ["ide", "cicd"],
    category: "advisory",
    severity: {
      default: "warning",
      legacy: "off",
      strict: "warning"
    }
  },
  "ACU-STY-008": {
    ruleId: "exitTerminatorIssue",
    message: "'{keyword}' {issue}.",
    paramKeys: ["keyword", "issue"],
    surfaces: ["ide", "cicd"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "off",
      strict: "warning"
    }
  },

  // ============================================================================
  // ACU-TODO: TODO/FIXME/HACK tag tracking
  // ============================================================================
  "ACU-TODO-001": {
    ruleId: "todoItem",
    message: "TODO: {description}",
    paramKeys: ["description"],
    surfaces: ["ide"],
    category: "advisory",
    severity: {
      default: "information",
      legacy: "information",
      strict: "information"
    }
  },
  "ACU-TODO-002": {
    ruleId: "fixmeItem",
    message: "FIXME: {description}",
    paramKeys: ["description"],
    surfaces: ["ide"],
    category: "advisory",
    severity: {
      default: "information",
      legacy: "information",
      strict: "information"
    }
  },
  "ACU-TODO-003": {
    ruleId: "hackItem",
    message: "HACK: {description}",
    paramKeys: ["description"],
    surfaces: ["ide"],
    category: "advisory",
    severity: {
      default: "information",
      legacy: "information",
      strict: "information"
    }
  },

  // ============================================================================
  // ACU-ARG: Argument validation (constrained string literals and field handle identifiers)
  // ============================================================================
  "ACU-ARG-001": {
    ruleId: "invalidDateStringFormat",
    message: "Invalid date string '{value}'. Expected format DD/MM/YYYY (e.g. '31/12/2026').",
    paramKeys: ["value"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-ARG-002": {
    ruleId: "invalidDateStringOutOfRange",
    message: "Date string '{value}' is out of the valid range (00/00/0000 to 31/12/9998).",
    paramKeys: ["value"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-ARG-003": {
    ruleId: "invalidCursorDirection",
    message: "Invalid cursor direction '{value}'. Valid directions: >=, >, +, =, -, <, <=.",
    paramKeys: ["value"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-ARG-004": {
    ruleId: "invalidCursorDirection",
    message: "Invalid cursor direction '{value}'. Did you mean '{suggestion}'?",
    paramKeys: ["value", "suggestion"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "warning",
      strict: "error"
    }
  },
  "ACU-ARG-005": {
    ruleId: "invalidTableSynonym",
    message: "Invalid table synonym '{value}'. Expected a valid 2-letter table code (e.g. 'MD', 'PH').",
    paramKeys: ["value"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  },
  "ACU-VAR-008": {
    ruleId: "invalidFieldHandle",
    message: "'{name}' does not match any known database field handle.",
    paramKeys: ["name"],
    surfaces: ["ide", "cicd"],
    category: "advisory",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-VAR-009": {
    ruleId: "fieldHandleCapitalization",
    message: "Field handle '{name}' should be '{canonical}'.",
    paramKeys: ["name", "canonical"],
    surfaces: ["ide", "cicd"],
    category: "standards",
    severity: {
      default: "warning",
      legacy: "warning",
      strict: "warning"
    }
  },
  "ACU-ARG-008": {
    ruleId: "literalForVarParam",
    message: "'{paramLabel}' argument of {functionName}() requires a variable, not a literal.",
    paramKeys: ["paramLabel", "functionName"],
    surfaces: ["ide", "cicd"],
    category: "validity",
    severity: {
      default: "error",
      legacy: "error",
      strict: "error"
    }
  }
};

/**
 * Get a catalog entry by message ID.
 * @param {string} messageId - The message ID (e.g., 'ACU-INT-001')
 * @returns {Object|null} - The catalog entry or null if not found
 */
function getCatalogEntry(messageId) {
  return DIAGNOSTIC_CATALOG[messageId] || null;
}

/**
 * Check if a message ID exists in the catalog.
 * @param {string} messageId - The message ID to check
 * @returns {boolean} - True if ID exists
 */
function isValidMessageId(messageId) {
  return messageId in DIAGNOSTIC_CATALOG;
}

/**
 * Get all message IDs for a given rule ID.
 * @param {string} ruleId - The rule ID (e.g., 'keywordCapitalization')
 * @returns {string[]} - Array of message IDs that use this rule
 */
function getMessageIdsForRule(ruleId) {
  return Object.entries(DIAGNOSTIC_CATALOG)
    .filter(([_, entry]) => entry.ruleId === ruleId)
    .map(([id, _]) => id);
}

/** Profile names recognised by the catalog. */
const KNOWN_PROFILES = ["default", "legacy", "strict"];

/**
 * Get rule severities for a standards profile name.
 * Derived from per-entry severity blocks in DIAGNOSTIC_CATALOG.
 * @param {string} profileName - Profile name ("default", "legacy", "strict")
 * @returns {Object} - Rule -> severity map
 */
function getProfileRuleSeverities(profileName) {
  const result = {};
  for (const entry of Object.values(DIAGNOSTIC_CATALOG)) {
    const sev = entry.severity;
    if (!sev) continue;
    result[entry.ruleId] = sev[profileName] ?? sev.default ?? "warning";
  }
  return result;
}

/**
 * Returns true if a diagnostic's messageId is valid for the given surface.
 * If the catalog entry has no surfaces field, it is treated as visible on all surfaces.
 * @param {string} messageId - e.g. "ACU-VAR-001"
 * @param {string} surface - e.g. "cicd" or "ide"
 * @returns {boolean}
 */
function isOnSurface(messageId, surface) {
  const entry = getCatalogEntry(messageId);
  if (!entry || !entry.surfaces) return true;
  return entry.surfaces.includes(surface);
}

/**
 * Returns true if a diagnostic can be suppressed via `// @rule-ignore ACU-XXX-NNN`.
 *
 * All diagnostics are suppressable by default. Set `suppressable: false` on a catalog
 * entry to prevent suppression — reserved for errors so fundamental that silencing them
 * would mask broken code (e.g. block-structure mismatches).
 *
 * @param {string} messageId - e.g. "ACU-VAR-001"
 * @returns {boolean}
 */
function isSuppressable(messageId) {
  const entry = getCatalogEntry(messageId);
  if (!entry) return false;
  return entry.suppressable !== false;
}

export {
  DIAGNOSTIC_CATALOG,
  KNOWN_PROFILES,
  getCatalogEntry,
  isValidMessageId,
  getMessageIdsForRule,
  getProfileRuleSeverities,
  isSuppressable,
  isOnSurface
};
