# -*- coding: utf-8; frozen_string_literal: true -*-
#
#--
# Copyright (C) 2009-2019 Thomas Leitner <t_leitner@gmx.at>
#
# This file is part of kramdown which is licensed under the MIT.
#++
#

require 'kramdown/parser/html'

module Kramdown
  module Parser
    class Kramdown

      include Kramdown::Parser::Html::Parser

      # Mapping of markdown attribute value to content model. I.e. :raw when "0", :default when "1"
      # (use default content model for the HTML element), :span when "span", :block when block and
      # for everything else +nil+ is returned.
      HTML_MARKDOWN_ATTR_MAP = {"0" => :raw, "1" => :default, "span" => :span, "block" => :block}

      TRAILING_WHITESPACE = /[ \t]*\n/

      def handle_kramdown_html_tag(el, closed, handle_body)
        if @block_ial
          el.options[:ial] = @block_ial
          @block_ial = nil
        end

        content_model = if @tree.type != :html_element || @tree.options[:content_model] != :raw
                          (@options[:parse_block_html] ? HTML_CONTENT_MODEL[el.value] : :raw)
                        else
                          :raw
                        end
        if (val = HTML_MARKDOWN_ATTR_MAP[el.attr.delete('markdown')])
          content_model = (val == :default ? HTML_CONTENT_MODEL[el.value] : val)
        end

        @src.scan(TRAILING_WHITESPACE) if content_model == :block
        el.options[:content_model] = content_model
        el.options[:is_closed] = closed

        if !closed && handle_body
          if content_model == :block
            unless parse_blocks(el)
              warning("Found no end tag for '#{el.value}' (line #{el.options[:location]}) - auto-closing it")
            end
          elsif content_model == :span
            curpos = @src.pos
            if @src.scan_until(/(?=<\/#{el.value}\s*>)/mi)
              add_text(extract_string(curpos...@src.pos, @src), el)
              @src.scan(HTML_TAG_CLOSE_RE)
            else
              add_text(@src.rest, el)
              @src.terminate
              warning("Found no end tag for '#{el.value}' (line #{el.options[:location]}) - auto-closing it")
            end
          else
            parse_raw_html(el, &method(:handle_kramdown_html_tag))
          end
          unless @tree.type == :html_element && @tree.options[:content_model] == :raw
            @src.scan(TRAILING_WHITESPACE)
          end
        end
      end

      HTML_BLOCK_START = /^#{OPT_SPACE}<(#{REXML::Parsers::BaseParser::UNAME_STR}|!--|\/)/

      # Parse the HTML at the current position as block-level HTML.
      def parse_block_html
        line = @src.current_line_number
        if (result = @src.scan(HTML_COMMENT_RE))
          @tree.children << Element.new(:xml_comment, result, nil, category: :block, location: line)
          @src.scan(TRAILING_WHITESPACE)
          true
        else
          if @src.check(/^#{OPT_SPACE}#{HTML_TAG_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
            @src.pos += @src.matched_size
            handle_html_start_tag(line, &method(:handle_kramdown_html_tag))
            Kramdown::Parser::Html::ElementConverter.convert(@root, @tree.children.last) if @options[:html_to_native]
            true
          elsif @src.check(/^#{OPT_SPACE}#{HTML_TAG_CLOSE_RE}/o) && !HTML_SPAN_ELEMENTS.include?(@src[1].downcase)
            name = @src[1].downcase

            if @tree.type == :html_element && @tree.value == name
              @src.pos += @src.matched_size
              throw :stop_block_parsing, :found
            else
              false
            end
          else
            false
          end
        end
      end
      define_parser(:block_html, HTML_BLOCK_START)

      HTML_SPAN_START = /<(#{REXML::Parsers::BaseParser::UNAME_STR}|!--|\/)/

      # Parse the HTML at the current position as span-level HTML.
      def parse_span_html
        line = @src.current_line_number
        if (result = @src.scan(HTML_COMMENT_RE))
          @tree.children << Element.new(:xml_comment, result, nil, category: :span, location: line)
        elsif (result = @src.scan(HTML_TAG_CLOSE_RE))
          warning("Found invalidly used HTML closing tag for '#{@src[1]}' on line #{line}")
          add_text(result)
        elsif (result = @src.scan(HTML_TAG_RE))
          tag_name = @src[1]
          tag_name.downcase! if HTML_ELEMENT[tag_name.downcase]
          if HTML_BLOCK_ELEMENTS.include?(tag_name)
            warning("Found block HTML tag '#{tag_name}' in span-level text on line #{line}")
            add_text(result)
            return
          end

          attrs = parse_html_attributes(@src[2], line, HTML_ELEMENT[tag_name])
          attrs.each_value {|value| value.gsub!(/\n+/, ' ') unless value.empty? }

          do_parsing = if HTML_CONTENT_MODEL[tag_name] == :raw || @tree.options[:content_model] == :raw
                         false
                       else
                         @options[:parse_span_html]
                       end
          if (val = HTML_MARKDOWN_ATTR_MAP[attrs.delete('markdown')])
            if val == :block
              warning("Cannot use block-level parsing in span-level HTML tag (line #{line}) " \
                      "- using default mode")
            elsif val == :span
              do_parsing = true
            elsif val == :default
              do_parsing = HTML_CONTENT_MODEL[tag_name] != :raw
            elsif val == :raw
              do_parsing = false
            end
          end

          el = Element.new(:html_element, tag_name, attrs, category: :span, location: line,
                           content_model: (do_parsing ? :span : :raw), is_closed: !!@src[4])
          @tree.children << el
          stop_re = /<\/#{Regexp.escape(tag_name)}\s*>/
          stop_re = Regexp.new(stop_re.source, Regexp::IGNORECASE) if HTML_ELEMENT[tag_name]
          if !@src[4] && !HTML_ELEMENTS_WITHOUT_BODY.include?(el.value)
            if parse_spans(el, stop_re, (do_parsing ? nil : [:span_html]))
              @src.scan(stop_re)
            else
              warning("Found no end tag for '#{el.value}' (line #{line}) - auto-closing it")
              add_text(@src.rest, el)
              @src.terminate
            end
          end
          Kramdown::Parser::Html::ElementConverter.convert(@root, el) if @options[:html_to_native]
        else
          add_text(@src.getch)
        end
      end
      define_parser(:span_html, HTML_SPAN_START, '<')

    end
  end
end
