# frozen_string_literal: true

module Solargraph
  module Rspec
    module Correctors
      # A corrector of RSpec parsed pins by Solargraph
      # @abstract
      class Base
        # @return [Array<Solargraph::Pin::Namespace>]
        attr_reader :namespace_pins

        # @return [Solargraph::Rspec::SpecWalker]
        attr_reader :rspec_walker

        # @return [Array<Solargraph::Pin::Base>]
        attr_reader :added_pins

        # @param namespace_pins [Array<Solargraph::Pin::Base>]
        # @param rspec_walker [Solargraph::Rspec::SpecWalker]
        # @param added_pins [Array<Solargraph::Pin::Base>]
        def initialize(namespace_pins:, rspec_walker:, added_pins: [])
          @namespace_pins = namespace_pins
          @rspec_walker = rspec_walker
          @added_pins = added_pins
        end

        # @param _source_map [Solargraph::SourceMap]
        # @return [void]
        def correct(_source_map)
          raise NotImplementedError
        end

        private

        # @param pin [Solargraph::Pin::Base, nil]
        # @return [void]
        def add_pin(pin)
          return unless pin

          added_pins.push(pin)
        end

        # @param pins [Array<Solargraph::Pin::Base>]
        # @return [void]
        def add_pins(*pins)
          added_pins.concat(pins.flatten.compact)
        end

        # @return [Solargraph::Pin::Namespace]
        def root_example_group_namespace_pin
          Solargraph::Pin::Namespace.new(
            name: ROOT_NAMESPACE,
            location: PinFactory.dummy_location('lib/rspec/core/example_group.rb')
          )
        end

        # @param namespace_pins [Array<Pin::Namespace>]
        # @param line [Integer]
        # @return [Pin::Namespace, nil]
        def closest_namespace_pin(namespace_pins, line)
          namespace_pins.min_by do |namespace_pin|
            distance = line - namespace_pin.location.range.start.line
            distance >= 0 ? distance : Float::INFINITY
          end
        end

        # Overrides a pin's closure, and resets all the needed instance vars
        # @param pin [Solargraph::Pin::Base]
        # @param new_closure [Solargraph::Pin::Closure]
        def override_closure(pin, new_closure)
          # HACK: We should only rely on public API, and avoid instance_variable_get/set.
          pin.instance_variable_set('@closure', new_closure)
          pin.reset_generated!

          pin.remove_instance_variable(:@path) if pin.instance_variables.include? :@path
        end

        # Given the following code, Solargraph::Parser.node_range returns the following range for block ast:
        #
        # ```ruby
        #   some_method_with_block do
        #   ^ - block start
        #   end
        #   ^ - block end
        # ```
        #
        # Instead we want the range to be:
        #
        # ```ruby
        #   some_method_with_block do
        #                          ^ - block start
        #   end
        #   ^ - block end
        # ```
        #
        # @param closure [Solargraph::Pin::Closure]
        # @param source_map [Solargraph::SourceMap]
        # @return [void]
        def override_block_location(closure, source_map)
          range = closure.location.range
          block_ast = source_map.source.node_at(range.ending.line, range.ending.column)
          new_location = PinFactory.build_location(
            PinFactory.build_location_range(block_ast),
            closure.location.filename
          )

          # HACK: We should only rely on public API, and avoid instance_variable_get/set.
          closure.instance_variable_set('@location', new_location)
        end
      end
    end
  end
end
