# frozen_string_literal: true

module Unparser

  # Holds the comments that remain to be emitted
  #
  # ignore :reek:RepeatedConditional
  class Comments

    # Proxy to singleton
    #
    # NOTICE:
    #   Delegating to stateless helpers is a pattern I saw many times in our code.
    #   Maybe we should make another helper module? include SingletonDelegator.new(:source_range) ?
    #
    # @return [undefined]
    #
    # @api private
    #
    def source_range(*arguments)
      self.class.source_range(*arguments)
    end

    # Initialize object
    #
    # @param [Array] comments
    #
    # @return [undefined]
    #
    # @api private
    #
    def initialize(comments)
      @comments = comments.dup
      @last_range_consumed = nil
    end

    # Consume part or all of the node
    #
    # @param [Parser::AST::Node] node
    # @param [Symbol] source_part
    #
    # @return [undefined]
    #
    # @api private
    #
    def consume(node, source_part = :expression)
      range = source_range(node, source_part)
      @last_range_consumed = range if range
    end

    # Take end-of-line comments
    #
    # @return [Array]
    #
    # @api private
    #
    def take_eol_comments
      return EMPTY_ARRAY unless @last_range_consumed

      comments = take_up_to_line(@last_range_consumed.end.line)
      unshift_documents(comments)
    end

    # Take all remaining comments
    #
    # @return [Array]
    #
    # @api private
    #
    def take_all
      take_while { true }
    end

    # Take comments appear in the source before the specified part of the node
    #
    # @param [Parser::AST::Node] node
    # @param [Symbol] source_part
    #
    # @return [Array]
    #
    # @api private
    #
    def take_before(node, source_part)
      range = source_range(node, source_part)
      if range
        take_while { |comment| comment.location.expression.end_pos <= range.begin_pos }
      else
        EMPTY_ARRAY
      end
    end

    # Return source location part
    #
    # FIXME: This method should not be needed. It does to much inline signalling.
    #
    # @param [Parser::AST::Node] node
    # @param [Symbol] part
    #
    # @return [Parser::Source::Range]
    #   if present
    #
    # @return [nil]
    #   otherwise
    #
    # @api private
    #
    # :reek:ManualDispatch
    #
    def self.source_range(node, part)
      location = node.location
      location.public_send(part) if location.respond_to?(part)
    end

  private

    # Take comments while the provided block returns true
    #
    # @yield [Parser::Source::Comment]
    #
    # @return [Array]
    #
    # @api private
    #
    def take_while
      number_to_take = @comments.index { |comment| !yield(comment) } || @comments.size
      @comments.shift(number_to_take)
    end

    # Take comments up to the line number
    #
    # @param [Fixnum] line
    #
    # @return [Array]
    #
    # @api private
    #
    def take_up_to_line(line)
      take_while { |comment| comment.location.expression.line <= line }
    end

    # Unshift document comments and return the rest
    #
    # @param [Array] comments
    #
    # @return [Array]
    #
    # @api private
    #
    def unshift_documents(comments)
      doc_comments, other_comments = comments.partition(&:document?)
      doc_comments.reverse_each { |comment| @comments.unshift(comment) }
      other_comments
    end

  end # Comments
end # Unparser
