# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
  module Lexers
    load_lexer 'xpath.rb'
    class XQuery < XPath
      title 'XQuery'
      desc 'XQuery 3.1: An XML Query Language'
      tag 'xquery'
      filenames '*.xquery', '*.xq'
      mimetypes 'application/xquery'

      def self.keywords
        @keywords ||= Regexp.union super, Regexp.union(%w(
          xquery encoding version declare module
          namespace copy-namespaces boundary-space construction
          default collation base-uri preserve strip
          ordering ordered unordered order empty greatest least
          preserve no-preserve inherit no-inherit
          decimal-format decimal-separator grouping-separator
          infinity minus-sign NaN percent per-mille
          zero-digit digit pattern-separator exponent-separator
          import schema at element option
          function external context item
          typeswitch switch case
          try catch
          validate lax strict type
          document element attribute text comment processing-instruction
          for let where order group by return
          allowing tumbling stable sliding window
          start end only when previous next count collation
          ascending descending
        ))
      end

      # Mixin states:

      state :tags do
        rule %r/<#{XPath.qName}/, Name::Tag, :start_tag
        rule %r/<!--/, Comment, :xml_comment
        rule %r/<\?.*?\?>/, Comment::Preproc
        rule %r/<!\[CDATA\[.*?\]\]>/, Comment::Preproc
        rule %r/&\S*?;/, Name::Entity
      end

      # Lexical states:

      prepend :root do
        mixin :tags

        rule %r/\{/, Punctuation
        rule %r/\}`?/ do
          token Punctuation
          if stack.length > 1
            pop!
          end
        end

        rule %r/(namespace)(\s+)(#{XPath.ncName})/ do
          groups Keyword, Text::Whitespace, Name::Namespace
        end

        rule %r/(#{XQuery.keywords})\b/, Keyword
        rule %r/;/, Punctuation
        rule %r/%/, Keyword::Declaration, :annotation

        rule %r/(\(#)(\s*)(#{XPath.eqName})/ do
          groups Comment::Preproc, Text::Whitespace, Name::Tag
          push :pragma
        end

        rule %r/``\[/, Str, :str_constructor
      end

      state :annotation do
        mixin :commentsAndWhitespace
        rule XPath.eqName, Keyword::Declaration, :pop!
      end

      state :pragma do
        mixin :commentsAndWhitespace
        rule %r/#\)/, Comment::Preproc, :pop!
        rule %r/./, Comment::Preproc
      end

      # https://www.w3.org/TR/xquery-31/#id-string-constructors
      state :str_constructor do
        rule %r/`\{/, Punctuation, :root
        rule %r/\]``/, Str, :pop!
        rule %r/[^`\]]+/m, Str
        rule %r/[`\]]/, Str
      end

      state :xml_comment do
        rule %r/[^-]+/m, Comment
        rule %r/-->/, Comment, :pop!
        rule %r/-/, Comment
      end

      state :start_tag do
        rule %r/\s+/m, Text::Whitespace
        rule %r/([\w.:-]+\s*=)(")/m do
          groups Name::Attribute, Str
          push :quot_attr
        end
        rule %r/([\w.:-]+\s*=)(')/m do
          groups Name::Attribute, Str
          push :apos_attr
        end
        rule %r/>/, Name::Tag, :tag_content
        rule %r(/>), Name::Tag, :pop!
      end

      state :quot_attr do
        rule %r/"/, Str, :pop!
        rule %r/\{\{/, Str
        rule %r/\{/, Punctuation, :root
        rule %r/[^"{>]+/m, Str
      end

      state :apos_attr do
        rule %r/'/, Str, :pop!
        rule %r/\{\{/, Str
        rule %r/\{/, Punctuation, :root
        rule %r/[^'{>]+/m, Str
      end

      state :tag_content do
        rule %r/\s+/m, Text::Whitespace
        mixin :tags

        rule %r/(\{\{|\}\})/, Text
        rule %r/\{/, Punctuation, :root

        rule %r/[^{}<&]/, Text

        rule %r(</#{XPath.qName}(\s*)>) do
          token Name::Tag
          pop! 2 # pop self and tag_start
        end
      end
    end
  end
end
