module Watir
  module Locators
    class Element
      class SelectorBuilder
        class XPath
          def initialize(should_use_label_element)
            @should_use_label_element = should_use_label_element
          end

          def build(selectors)
            adjacent = selectors.delete :adjacent
            xpath = adjacent ? process_adjacent(adjacent) : './/*'

            tag_name = selectors.delete(:tag_name).to_s
            xpath << "[local-name()='#{tag_name}']" unless tag_name.empty?

            index = selectors.delete(:index)

            # the remaining entries should be attributes
            xpath << '[' << attribute_expression(nil, selectors) << ']' unless selectors.empty?

            xpath << "[#{index + 1}]" if adjacent && index

            p xpath: xpath, selectors: selectors if $DEBUG

            [:xpath, xpath]
          end

          # @todo Get rid of building
          def attribute_expression(building, selectors)
            f = selectors.map do |key, val|
              if val.is_a?(Array) && key == :class
                '(' + val.map { |v| build_class_match(v) }.join(' and ') + ')'
              elsif val.is_a?(Array)
                '(' + val.map { |v| equal_pair(building, key, v) }.join(' or ') + ')'
              elsif val.eql? true
                attribute_presence(key)
              elsif val.eql? false
                attribute_absence(key)
              else
                equal_pair(building, key, val)
              end
            end
            f.join(' and ')
          end

          # @todo Get rid of building
          def equal_pair(building, key, value)
            if key == :class
              if value.strip.include?(' ')
                dep = "Using the :class locator to locate multiple classes with a String value (i.e. \"#{value}\")"
                Watir.logger.deprecate dep,
                                       "Array (e.g. #{value.split})",
                                       ids: [:class_array]
              end
              build_class_match(value)
            elsif key == :label && @should_use_label_element
              # we assume :label means a corresponding label element, not the attribute
              text = "normalize-space()=#{XpathSupport.escape value}"
              "(@id=//label[#{text}]/@for or parent::label[#{text}])"
            else
              "#{lhs_for(building, key)}=#{XpathSupport.escape value}"
            end
          end

          # @todo Get rid of building
          def lhs_for(_building, key)
            case key
            when :text, 'text'
              'normalize-space()'
            when String
              "@#{key}"
            when :href
              # TODO: change this behaviour?
              'normalize-space(@href)'
            when :type
              # type attributes can be upper case - downcase them
              # https://github.com/watir/watir/issues/72
              XpathSupport.downcase('@type')
            when ::Symbol
              "@#{key.to_s.tr('_', '-')}"
            else
              raise Error::Exception, "Unable to build XPath using #{key}"
            end
          end

          private

          def process_adjacent(adjacent)
            xpath = './'
            xpath << case adjacent
                     when :ancestor
                       'ancestor::*'
                     when :preceding
                       'preceding-sibling::*'
                     when :following
                       'following-sibling::*'
                     when :child
                       'child::*'
                     end
            xpath
          end

          def build_class_match(value)
            if value =~ /^!/
              klass = XpathSupport.escape " #{value[1..-1]} "
              "not(contains(concat(' ', @class, ' '), #{klass}))"
            else
              klass = XpathSupport.escape " #{value} "
              "contains(concat(' ', @class, ' '), #{klass})"
            end
          end

          def attribute_presence(attribute)
            lhs_for(nil, attribute)
          end

          def attribute_absence(attribute)
            "not(#{lhs_for(nil, attribute)})"
          end
        end
      end
    end
  end
end