/**
 * YARA rule language support for CodeMirror 6
 */

import { StreamLanguage, LanguageSupport } from '@codemirror/language'
import { type Completion } from '@codemirror/autocomplete'

const KEYWORDS = [
  'rule',
  'meta',
  'strings',
  'condition',
  'import',
  'include',
  'private',
  'global',
  'true',
  'false',
]

const MODIFIERS = ['nocase', 'wide', 'ascii', 'fullword', 'xor', 'base64', 'base64wide']

const OPERATORS = [
  'and',
  'or',
  'not',
  'of',
  'them',
  'any',
  'all',
  'at',
  'in',
  'for',
  'contains',
  'icontains',
  'startswith',
  'istartswith',
  'endswith',
  'iendswith',
  'matches',
  'defined',
]

const BUILTIN_MODULES = [
  'pe',
  'elf',
  'math',
  'hash',
  'cuckoo',
  'magic',
  'dotnet',
  'time',
  'console',
  'string',
]

const yaraLanguage = StreamLanguage.define({
  token(stream): string | null {
    if (stream.eatSpace()) {
      return null
    }

    // Line comment
    if (stream.match('//') !== null) {
      stream.skipToEnd()
      return 'comment'
    }

    // Block comment
    if (stream.match(/\/\*.*?\*\//) !== null) {
      return 'comment'
    }

    // Hex string literal { AB CD ?? }
    if (stream.match(/\{[\s0-9A-Fa-f?|()[\]-]*\}/) !== null) {
      return 'string'
    }

    // Regular expression /pattern/
    if (stream.match(/\/(?:[^/\\]|\\.)*\//) !== null) {
      return 'regexp'
    }

    // Double-quoted string
    if (stream.match(/"(?:[^"\\]|\\.)*"/) !== null) {
      return 'string'
    }

    // Numbers (hex and decimal)
    if (stream.match(/0x[0-9A-Fa-f]+/) !== null) {
      return 'number'
    }
    if (stream.match(/\d+(\.\d+)?/) !== null) {
      return 'number'
    }

    // Variable references ($identifier, #identifier, @identifier)
    if (stream.match(/[$#@][A-Za-z_]\w*/) !== null) {
      return 'variableName.special'
    }

    // Identifiers
    if (stream.match(/[A-Za-z_]\w*/) !== null) {
      const word = stream.current().toLowerCase()
      if (KEYWORDS.includes(word)) {
        return 'keyword'
      }
      if (MODIFIERS.includes(word)) {
        return 'modifier'
      }
      if (OPERATORS.includes(word)) {
        return 'operator'
      }
      if (BUILTIN_MODULES.includes(word)) {
        return 'namespace'
      }
      return 'variableName'
    }

    // Comparison operators
    if (stream.match(/==|!=|<=|>=|<<|>>/) !== null) {
      return 'operator'
    }
    if (stream.match(/[<>|=]/) !== null) {
      return 'operator'
    }

    stream.next()
    return null
  },
})

/** YARA rule language support for CodeMirror */
export function yara(): LanguageSupport {
  return new LanguageSupport(yaraLanguage)
}

/** Static completions for YARA keywords, modifiers, operators */
export const yaraCompletions: Completion[] = [
  ...KEYWORDS.map(label => ({ label, type: 'keyword' as const })),
  ...MODIFIERS.map(label => ({
    label,
    type: 'keyword' as const,
    info: 'string modifier',
  })),
  ...OPERATORS.map(label => ({ label, type: 'keyword' as const })),
  ...BUILTIN_MODULES.map(label => ({
    label,
    type: 'namespace' as const,
    info: 'built-in module',
  })),
]

export { yaraLanguage }
