# frozen_string_literal: true

require 'task_list/filter'

# Generated HTML is transformed back to GFM by:
# - app/assets/javascripts/behaviors/markdown/nodes/ordered_task_list.js
# - app/assets/javascripts/behaviors/markdown/nodes/task_list.js
# - app/assets/javascripts/behaviors/markdown/nodes/task_list_item.js
module Banzai
  module Filter
    # TaskList filter replaces task list item markers (`[ ]`, `[x]`, and `[~]`)
    # with checkboxes, marked up with metadata and behavior.
    #
    # This should be run on the HTML generated by the Markdown filter, after the
    # SanitizationFilter.
    #
    # Syntax
    # ------
    #
    # Task list items must be in a list format:
    #
    # ```
    # - [ ] incomplete
    # - [x] complete
    # - [~] inapplicable
    # ```
    #
    # This class overrides TaskList::Filter in the `deckar01-task_list` gem
    # to add support for inapplicable task items
    class TaskListFilter < TaskList::Filter
      prepend Concerns::PipelineTimingCheck
      extend ::Gitlab::Utils::Override

      XPATH = 'descendant-or-self::li[input[@data-inapplicable]] | descendant-or-self::li[p[input[@data-inapplicable]]]'
      INAPPLICABLE = '[~]'
      INAPPLICABLEPATTERN = /\[~\]/

      # Pattern used to identify all task list items.
      # Useful when you need iterate over all items.
      NEWITEMPATTERN = /
        \A
        (?:\s*[-+*]|(?:\d+\.))? # optional list prefix
        \s*                     # optional whitespace prefix
        (                       # checkbox
          #{CompletePattern}|
          #{IncompletePattern}|
          #{INAPPLICABLEPATTERN}
        )
        (?=\s)                  # followed by whitespace
      /x

      # Force the gem's constant to use our new one
      superclass.send(:remove_const, :ItemPattern) # rubocop: disable GitlabSecurity/PublicSend
      superclass.const_set(:ItemPattern, NEWITEMPATTERN)

      def inapplicable?(item)
        !!(item.checkbox_text =~ INAPPLICABLEPATTERN)
      end

      override :render_item_checkbox
      def render_item_checkbox(item)
        stripped_source = item.source.sub(ItemPattern, '').strip
        text = stripped_source.partition(/\<(ol|ul)/)
        source = ActionView::Base.full_sanitizer.sanitize(text[0])
        truncated_source = source.truncate(100, separator: ' ', omission: '…')
        aria_label = format(_('Check option: %{option}'), option: truncated_source)

        %(<task-button></task-button><input type="checkbox"
          class="task-list-item-checkbox"
          aria-label="#{CGI.escapeHTML(aria_label)}"
          #{'checked="checked"' if item.complete?}
          #{'data-inapplicable' if inapplicable?(item)}
          disabled="disabled"/>)
      end

      override :render_task_list_item
      def render_task_list_item(item)
        source = item.source

        if inapplicable?(item)
          # Add a `<s>` tag around the list item text. However because of the
          # way tasks are built, the source can include an embedded sublist, like
          #   `[~] foobar\n<ol><li....`
          # The `<s>` should only be added to the main text.
          source = source.partition("#{INAPPLICABLE} ")
          text = source.last.partition(/\<(ol|ul)/)
          text[0] = "<s>#{text[0]}</s>"
          source[-1] = text.join
          source = source.join
        end

        Nokogiri::HTML.fragment \
          source.sub(ItemPattern, render_item_checkbox(item)), 'utf-8'
      end

      override :call
      def call
        super

        # add class to li for any inapplicable checkboxes
        doc.xpath(XPATH).each do |li|
          li.add_class('inapplicable')
        end

        doc
      end
    end
  end
end
