module CiteProc
  module Ruby

    class Renderer

      # @param item [CiteProc::CitationItem]
      # @param node [CSL::Style::Choose]
      # @return [String]
      def render_choose(item, node)
        return '' unless node.has_children?

        node.each_child do |child|
          return render_block(item, child) if evaluates?(item, child)
        end

        '' # no block was rendered
      end

      # @param item [CiteProc::CitationItem]
      # @param node [CSL::Style::Choose::Block]
      # @return [String]
      def render_block(item, node)
        return '' unless node.has_children?

        join node.each_child.map { |child|
          render item, child
        }, closest_delimiter_for(node)
      end

      def closest_delimiter_for(node)
        node.closest(/^group|layout$/)&.delimiter || ''
      end

      # Evaluates the conditions of the passed-in Choose::Block
      # against the passed-in CitationItem using the Block's matcher.
      #
      # @param item [CiteProc::CitationItem]
      # @param node [CSL::Style::Choose::Block]
      #
      # @return [Boolean] whether or not the node's conditions
      #   are true for the passed-in item
      def evaluates?(item, node)

        # subtle: else-nodes have no conditions. since the default
        # matcher :all? returns true for an empty list we do not
        # need to check for an else node specifically.

        # return true if node.nodename == 'else'

        node.conditions.send(node.matcher) do |type, matcher, values|
          case type
          when :disambiguate
            false # TODO not implemented yet

          when :'is-numeric'
            evaluates_condition? matcher, values do |value|
              v = item.data.unobservable_read_attribute(value)
              v.respond_to?(:numeric?) && v.numeric?
            end

          when :'is-uncertain-date'
            evaluates_condition? matcher, values do |value|
              v = item.data.unobservable_read_attribute(value)
              v.respond_to?(:uncertain?) && v.uncertain?
            end


          when :locator
            locator = item.locator.to_s.tr(' ', '-')

            evaluates_condition? matcher, values do |value|
              value.to_s == locator
            end

          when :position
            false # TODO not implemented yet

          when :type
            type = item.data.unobservable_read_attribute(:type).to_s

            evaluates_condition? matcher, values do |value|
              value.to_s == type
            end

          when :variable
            evaluates_condition? matcher, values do |value|
              item.data.attribute?(value) || (value == 'locator' && item.locator?)
            end

          else
            fail "unknown condition type: #{type}"
          end
        end
      end

      # Evaluates the passed-in block for each value in values,
      # negating the result if the value is prefixed with 'not:'
      def evaluates_condition?(matcher, values, &condition)
        values.send(matcher) do |value|
          value, negate = value.split(/^not:/, 2).reverse
          result = condition.call(value)

          negate ? !result : result
        end
      end
    end
  end
end
