# typed: strict
# frozen_string_literal: true

module Tapioca
  module Gem
    module Listeners
      class SourceLocation < Base
        extend T::Sig

        private

        sig { override.params(event: ConstNodeAdded).void }
        def on_const(event)
          file, line = Object.const_source_location(event.symbol)
          add_source_location_comment(event.node, file, line)
        end

        sig { override.params(event: ScopeNodeAdded).void }
        def on_scope(event)
          # Instead of using `const_source_location`, which always reports the first place where a constant is defined,
          # we filter the locations tracked by ConstantDefinition. This allows us to provide the correct location for
          # constants that are defined by multiple gems.
          locations = Runtime::Trackers::ConstantDefinition.locations_for(event.constant)
          location = locations.find do |loc|
            Pathname.new(loc.path).realpath.to_s.include?(@pipeline.gem.full_gem_path)
          end

          # The location may still be nil in some situations, like constant aliases (e.g.: MyAlias = OtherConst). These
          # are quite difficult to attribute a correct location, given that the source location points to the original
          # constants and not the alias
          add_source_location_comment(event.node, location.path, location.lineno) unless location.nil?
        end

        sig { override.params(event: MethodNodeAdded).void }
        def on_method(event)
          file, line = event.method.source_location
          add_source_location_comment(event.node, file, line)
        end

        sig { params(node: RBI::NodeWithComments, file: T.nilable(String), line: T.nilable(Integer)).void }
        def add_source_location_comment(node, file, line)
          return unless file && line

          gem = @pipeline.gem
          path = Pathname.new(file)
          return unless File.exist?(path)

          path = if path.realpath.to_s.start_with?(gem.full_gem_path)
            "#{gem.name}-#{gem.version}/#{path.realpath.relative_path_from(gem.full_gem_path)}"
          else
            path.sub("#{Bundler.bundle_path}/gems/", "")
          end

          node.comments << RBI::Comment.new("") if node.comments.any?
          node.comments << RBI::Comment.new("source://#{path}:#{line}")
        end
      end
    end
  end
end