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

module Rouge
  module Lexers
    class Mason < TemplateLexer
      title 'Mason'
      desc 'The HTML::Mason framework (https://metacpan.org/pod/HTML::Mason)'
      tag 'mason'
      filenames '*.mi', '*.mc', '*.mas', '*.m', '*.mhtml', '*.mcomp', 'autohandler', 'dhandler'
      mimetypes 'text/x-mason', 'application/x-mason'

      def initialize(*)
        super
        @perl = Perl.new
      end

      # Note: If you add a tag in the lines below, you also need to modify "disambiguate '*.m'" in file disambiguation.rb
      TEXT_BLOCKS = %w(text doc)
      PERL_BLOCKS = %w(args flags attr init once shared perl cleanup filter)
      COMPONENTS = %w(def method)

      state :root do
        mixin :mason_tags
      end

      state :mason_tags do
        rule %r/\s+/, Text::Whitespace

        rule %r/<%(#{TEXT_BLOCKS.join('|')})>/oi, Comment::Preproc, :text_block

        rule %r/<%(#{PERL_BLOCKS.join('|')})>/oi, Comment::Preproc, :perl_block

        rule %r/(<%(#{COMPONENTS.join('|')}))([^>]*)(>)/oi do |m|
          token Comment::Preproc, m[1]
          token Name, m[3]
          token Comment::Preproc, m[4]
          push :component_block
        end

        # perl line
        rule %r/^(%)(.*)$/ do |m|
          token Comment::Preproc, m[1]
          delegate @perl, m[2]
        end

        # start of component call
        rule %r/<%/, Comment::Preproc, :component_call

         # start of component with content
        rule %r/<&\|/ do
          token Comment::Preproc
          push :component_with_content
          push :component_sub
        end

        # start of component substitution
        rule %r/<&/, Comment::Preproc, :component_sub

        # fallback to HTML until a mason tag is encountered
        rule(/(.+?)(?=(<\/?&|<\/?%|^%|^#))/m) { delegate parent }

        # if we get here, there's no more mason tags, so we parse the rest of the doc as HTML
        rule(/.+/m) { delegate parent }
      end

      state :perl_block do
        rule %r/<\/%(#{PERL_BLOCKS.join('|')})>/oi, Comment::Preproc, :pop!
        rule %r/\s+/, Text::Whitespace
        rule %r/^(#.*)$/, Comment
        rule(/(.*?[^"])(?=<\/%)/m) { delegate @perl }
      end

      state :text_block do
        rule %r/<\/%(#{TEXT_BLOCKS.join('|')})>/oi, Comment::Preproc, :pop!
        rule %r/\s+/, Text::Whitespace
        rule %r/^(#.*)$/, Comment
        rule %r/(.*?[^"])(?=<\/%)/m, Comment
      end

      state :component_block do
        rule %r/<\/%(#{COMPONENTS.join('|')})>/oi, Comment::Preproc, :pop!
        rule %r/\s+/, Text::Whitespace
        rule %r/^(#.*)$/, Comment
        mixin :mason_tags
      end

      state :component_with_content do
        rule %r/<\/&>/ do
          token Comment::Preproc
          pop!
        end

        mixin :mason_tags
      end

      state :component_sub do
        rule %r/&>/, Comment::Preproc, :pop!

        rule(/(.*?)(?=&>)/m) { delegate @perl }
      end

      state :component_call do
        rule %r/%>/, Comment::Preproc, :pop!

        rule(/(.*?)(?=%>)/m) { delegate @perl }
      end
    end
  end
end
