module Testable
  module Element
    module Locator
      private

      # This method is what actually calls the browser instance to find
      # an element. If there is an element definition like this:
      #
      #    text_field :username, id: 'username'
      #
      # This will become the following:
      #
      #    browser.text_field(id: 'username')
      #
      # Note that the `to_subtype` method is called, which allows for the
      # generic `element` to be expressed as the type of element, as opposed
      # to `text_field` or `select_list`. For example, an `element` may be
      # defined like this:
      #
      #    element :enable, id: 'enableForm'
      #
      # Which means it would look like this:
      #
      #    Watir::HTMLElement:0x1c8c9 selector={:id=>"enableForm"}
      #
      # Whereas getting the subtype would give you:
      #
      #    Watir::CheckBox:0x12f8b elector={element: (webdriver element)}
      #
      # Which is what you would get if the element definition were this:
      #
      #    checkbox :enable, id: 'enableForm'
      #
      # Using the subtype does get tricky for scripts that require the
      # built-in sychronization aspects and wait states of Watir.
      #
      # The approach being used in this method is necessary to allow actions
      # like `set`, which are not available on `element`, even though other
      # actions, like `click`, are. But if you use `element` for your element
      # definitions, and your script requires a series of actions where elements
      # may be delayed in appearing, you'll get an "unable to locate element"
      # message, along with a Watir::Exception::UnknownObjectException.
      #
      # A check is made if an UnknownObjectException occurs due to a ready
      # validation. That's necessary because a ready validation has to find
      # an element in order to determine the ready state, but that element
      # might not be present.

      # rubocop:disable Metrics/AbcSize
      # rubocop:disable Metrics/CyclomaticComplexity
      # rubocop:disable Metrics/PerceivedComplexity
      # rubocop:disable Metrics/MethodLength
      def access_element(element, locators, qualifiers)
        if qualifiers.empty?
          if element == "element".to_sym
            @browser.element(locators).to_subtype
          else
            region_element.__send__(element, locators)
          end
        else
          # If the qualifiers are not empty, then that means the framework
          # has to consider a given set of elements so that it can check
          # the qualifier against them.
          plural = Testable.plural?(element)
          element = Testable.pluralize(element) unless plural

          elements = region_element.__send__(element, locators)

          # Consider the following element definition:
          #
          # select_list :car_make, name: 'car', selected: 'Audi', enabled: true
          #
          # In this case, the qualifiers will be `selected` (with a value of)
          # "Audi") and `enabled` (with a value of true. The arity of the
          # `selected` method would be 1 while the arity of the `enabled`
          # method would be 0.
          qualifiers.each do |qualifier, value|
            elements.to_a.select! do |ele|
              if ele.public_method(:"#{qualifier}?").arity.zero?
                ele.__send__(:"#{qualifier}?") == value
              else
                ele.__send__(:"#{qualifier}?", value)
              end
            end
          end

          # If the locator passed in was plural, then any elements matching
          # the locator and qualifier have to be returned. Otherwise, it will
          # just be the first item of the elements found.
          plural ? elements : elements.first
        end
      rescue Watir::Exception::UnknownObjectException
        return false if caller_locations.any? do |str|
          str.to_s.match?("ready_validations_pass?")
        end

        raise
      end
      # rubocop:enable Metrics/AbcSize
      # rubocop:enable Metrics/CyclomaticComplexity
      # rubocop:enable Metrics/PerceivedComplexity
      # rubocop:enable Metrics/MethodLength
    end
  end
end