/*
Language: XQuery
Author: Dirk Kirsten <dk@basex.org>
Contributor: Duncan Paterson
Description: Supports XQuery 3.1 including XQuery Update 3, so also XPath (as it is a superset)
Refactored to process xml constructor syntax and function-bodies. Added missing data-types, xpath operands, inbuilt functions, and query prologs
Website: https://www.w3.org/XML/Query/
Category: functional
Audit: 2020
*/

/** @type LanguageFn */
function xquery(_hljs) {
  // see https://www.w3.org/TR/xquery/#id-terminal-delimitation
  const KEYWORDS = [
    "module",
    "schema",
    "namespace",
    "boundary-space",
    "preserve",
    "no-preserve",
    "strip",
    "default",
    "collation",
    "base-uri",
    "ordering",
    "context",
    "decimal-format",
    "decimal-separator",
    "copy-namespaces",
    "empty-sequence",
    "except",
    "exponent-separator",
    "external",
    "grouping-separator",
    "inherit",
    "no-inherit",
    "lax",
    "minus-sign",
    "per-mille",
    "percent",
    "schema-attribute",
    "schema-element",
    "strict",
    "unordered",
    "zero-digit",
    "declare",
    "import",
    "option",
    "function",
    "validate",
    "variable",
    "for",
    "at",
    "in",
    "let",
    "where",
    "order",
    "group",
    "by",
    "return",
    "if",
    "then",
    "else",
    "tumbling",
    "sliding",
    "window",
    "start",
    "when",
    "only",
    "end",
    "previous",
    "next",
    "stable",
    "ascending",
    "descending",
    "allowing",
    "empty",
    "greatest",
    "least",
    "some",
    "every",
    "satisfies",
    "switch",
    "case",
    "typeswitch",
    "try",
    "catch",
    "and",
    "or",
    "to",
    "union",
    "intersect",
    "instance",
    "of",
    "treat",
    "as",
    "castable",
    "cast",
    "map",
    "array",
    "delete",
    "insert",
    "into",
    "replace",
    "value",
    "rename",
    "copy",
    "modify",
    "update"
  ];

  // Node Types (sorted by inheritance)
  // atomic types (sorted by inheritance)
  const TYPES = [
    "item",
    "document-node",
    "node",
    "attribute",
    "document",
    "element",
    "comment",
    "namespace",
    "namespace-node",
    "processing-instruction",
    "text",
    "construction",
    "xs:anyAtomicType",
    "xs:untypedAtomic",
    "xs:duration",
    "xs:time",
    "xs:decimal",
    "xs:float",
    "xs:double",
    "xs:gYearMonth",
    "xs:gYear",
    "xs:gMonthDay",
    "xs:gMonth",
    "xs:gDay",
    "xs:boolean",
    "xs:base64Binary",
    "xs:hexBinary",
    "xs:anyURI",
    "xs:QName",
    "xs:NOTATION",
    "xs:dateTime",
    "xs:dateTimeStamp",
    "xs:date",
    "xs:string",
    "xs:normalizedString",
    "xs:token",
    "xs:language",
    "xs:NMTOKEN",
    "xs:Name",
    "xs:NCName",
    "xs:ID",
    "xs:IDREF",
    "xs:ENTITY",
    "xs:integer",
    "xs:nonPositiveInteger",
    "xs:negativeInteger",
    "xs:long",
    "xs:int",
    "xs:short",
    "xs:byte",
    "xs:nonNegativeInteger",
    "xs:unisignedLong",
    "xs:unsignedInt",
    "xs:unsignedShort",
    "xs:unsignedByte",
    "xs:positiveInteger",
    "xs:yearMonthDuration",
    "xs:dayTimeDuration"
  ];

  const LITERALS = [
    "eq",
    "ne",
    "lt",
    "le",
    "gt",
    "ge",
    "is",
    "self::",
    "child::",
    "descendant::",
    "descendant-or-self::",
    "attribute::",
    "following::",
    "following-sibling::",
    "parent::",
    "ancestor::",
    "ancestor-or-self::",
    "preceding::",
    "preceding-sibling::",
    "NaN"
  ];

  // functions (TODO: find regex for op: without breaking build)
  const BUILT_IN = {
    className: 'built_in',
    variants: [
      {
        begin: /\barray:/,
        end: /(?:append|filter|flatten|fold-(?:left|right)|for-each(?:-pair)?|get|head|insert-before|join|put|remove|reverse|size|sort|subarray|tail)\b/
      },
      {
        begin: /\bmap:/,
        end: /(?:contains|entry|find|for-each|get|keys|merge|put|remove|size)\b/
      },
      {
        begin: /\bmath:/,
        end: /(?:a(?:cos|sin|tan[2]?)|cos|exp(?:10)?|log(?:10)?|pi|pow|sin|sqrt|tan)\b/
      },
      {
        begin: /\bop:/,
        end: /\(/,
        excludeEnd: true
      },
      {
        begin: /\bfn:/,
        end: /\(/,
        excludeEnd: true
      },
      // do not highlight inbuilt strings as variable or xml element names
      {
        begin: /[^</$:'"-]\b(?:abs|accumulator-(?:after|before)|adjust-(?:date(?:Time)?|time)-to-timezone|analyze-string|apply|available-(?:environment-variables|system-properties)|avg|base-uri|boolean|ceiling|codepoints?-(?:equal|to-string)|collation-key|collection|compare|concat|contains(?:-token)?|copy-of|count|current(?:-)?(?:date(?:Time)?|time|group(?:ing-key)?|output-uri|merge-(?:group|key))?data|dateTime|days?-from-(?:date(?:Time)?|duration)|deep-equal|default-(?:collation|language)|distinct-values|document(?:-uri)?|doc(?:-available)?|element-(?:available|with-id)|empty|encode-for-uri|ends-with|environment-variable|error|escape-html-uri|exactly-one|exists|false|filter|floor|fold-(?:left|right)|for-each(?:-pair)?|format-(?:date(?:Time)?|time|integer|number)|function-(?:arity|available|lookup|name)|generate-id|has-children|head|hours-from-(?:dateTime|duration|time)|id(?:ref)?|implicit-timezone|in-scope-prefixes|index-of|innermost|insert-before|iri-to-uri|json-(?:doc|to-xml)|key|lang|last|load-xquery-module|local-name(?:-from-QName)?|(?:lower|upper)-case|matches|max|minutes-from-(?:dateTime|duration|time)|min|months?-from-(?:date(?:Time)?|duration)|name(?:space-uri-?(?:for-prefix|from-QName)?)?|nilled|node-name|normalize-(?:space|unicode)|not|number|one-or-more|outermost|parse-(?:ietf-date|json)|path|position|(?:prefix-from-)?QName|random-number-generator|regex-group|remove|replace|resolve-(?:QName|uri)|reverse|root|round(?:-half-to-even)?|seconds-from-(?:dateTime|duration|time)|snapshot|sort|starts-with|static-base-uri|stream-available|string-?(?:join|length|to-codepoints)?|subsequence|substring-?(?:after|before)?|sum|system-property|tail|timezone-from-(?:date(?:Time)?|time)|tokenize|trace|trans(?:form|late)|true|type-available|unordered|unparsed-(?:entity|text)?-?(?:public-id|uri|available|lines)?|uri-collection|xml-to-json|years?-from-(?:date(?:Time)?|duration)|zero-or-one)\b/
      },
      {
        begin: /\blocal:/,
        end: /\(/,
        excludeEnd: true
      },
      {
        begin: /\bzip:/,
        end: /(?:zip-file|(?:xml|html|text|binary)-entry| (?:update-)?entries)\b/
      },
      {
        begin: /\b(?:util|db|functx|app|xdmp|xmldb):/,
        end: /\(/,
        excludeEnd: true
      }
    ]
  };

  const TITLE = {
    className: 'title',
    begin: /\bxquery version "[13]\.[01]"\s?(?:encoding ".+")?/,
    end: /;/
  };

  const VAR = {
    className: 'variable',
    begin: /[$][\w\-:]+/
  };

  const NUMBER = {
    className: 'number',
    begin: /(\b0[0-7_]+)|(\b0x[0-9a-fA-F_]+)|(\b[1-9][0-9_]*(\.[0-9_]+)?)|[0_]\b/,
    relevance: 0
  };

  const STRING = {
    className: 'string',
    variants: [
      {
        begin: /"/,
        end: /"/,
        contains: [
          {
            begin: /""/,
            relevance: 0
          }
        ]
      },
      {
        begin: /'/,
        end: /'/,
        contains: [
          {
            begin: /''/,
            relevance: 0
          }
        ]
      }
    ]
  };

  const ANNOTATION = {
    className: 'meta',
    begin: /%[\w\-:]+/
  };

  const COMMENT = {
    className: 'comment',
    begin: /\(:/,
    end: /:\)/,
    relevance: 10,
    contains: [
      {
        className: 'doctag',
        begin: /@\w+/
      }
    ]
  };

  // see https://www.w3.org/TR/xquery/#id-computedConstructors
  // mocha: computed_inbuilt
  // see https://www.regexpal.com/?fam=99749
  const COMPUTED = {
    beginKeywords: 'element attribute comment document processing-instruction',
    end: /\{/,
    excludeEnd: true
  };

  // mocha: direct_method
  const DIRECT = {
    begin: /<([\w._:-]+)(\s+\S*=('|").*('|"))?>/,
    end: /(\/[\w._:-]+>)/,
    subLanguage: 'xml',
    contains: [
      {
        begin: /\{/,
        end: /\}/,
        subLanguage: 'xquery'
      },
      'self'
    ]
  };

  const CONTAINS = [
    VAR,
    BUILT_IN,
    STRING,
    NUMBER,
    COMMENT,
    ANNOTATION,
    TITLE,
    COMPUTED,
    DIRECT
  ];

  return {
    name: 'XQuery',
    aliases: [
      'xpath',
      'xq'
    ],
    case_insensitive: false,
    illegal: /(proc)|(abstract)|(extends)|(until)|(#)/,
    keywords: {
      $pattern: /[a-zA-Z$][a-zA-Z0-9_:-]*/,
      keyword: KEYWORDS,
      type: TYPES,
      literal: LITERALS
    },
    contains: CONTAINS
  };
}

module.exports = xquery;
