# typed: strict
# frozen_string_literal: true

require "ruby_lsp/listeners/completion"

module RubyLsp
  module Requests
    # The [completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
    # suggests possible completions according to what the developer is typing.
    class Completion < Request
      class << self
        #: -> Interface::CompletionOptions
        def provider
          Interface::CompletionOptions.new(
            resolve_provider: true,
            trigger_characters: ["/", "\"", "'", ":", "@", ".", "=", "<", "$"],
            completion_item: {
              labelDetailsSupport: true,
            },
          )
        end
      end

      #: ((RubyDocument | ERBDocument) document, GlobalState global_state, Hash[Symbol, untyped] params, SorbetLevel sorbet_level, Prism::Dispatcher dispatcher) -> void
      def initialize(document, global_state, params, sorbet_level, dispatcher)
        super()
        @target = nil #: Prism::Node?
        @dispatcher = dispatcher
        # Completion always receives the position immediately after the character that was just typed. Here we adjust it
        # back by 1, so that we find the right node
        char_position, _ = document.find_index_by_position(params[:position])
        char_position -= 1
        delegate_request_if_needed!(global_state, document, char_position)

        node_context = RubyDocument.locate(
          document.ast,
          char_position,
          node_types: [
            Prism::CallNode,
            Prism::ConstantReadNode,
            Prism::ConstantPathNode,
            Prism::GlobalVariableAndWriteNode,
            Prism::GlobalVariableOperatorWriteNode,
            Prism::GlobalVariableOrWriteNode,
            Prism::GlobalVariableReadNode,
            Prism::GlobalVariableTargetNode,
            Prism::GlobalVariableWriteNode,
            Prism::InstanceVariableReadNode,
            Prism::InstanceVariableAndWriteNode,
            Prism::InstanceVariableOperatorWriteNode,
            Prism::InstanceVariableOrWriteNode,
            Prism::InstanceVariableTargetNode,
            Prism::InstanceVariableWriteNode,
            Prism::ClassVariableAndWriteNode,
            Prism::ClassVariableOperatorWriteNode,
            Prism::ClassVariableOrWriteNode,
            Prism::ClassVariableReadNode,
            Prism::ClassVariableTargetNode,
            Prism::ClassVariableWriteNode,
          ],
          code_units_cache: document.code_units_cache,
        )
        @response_builder = ResponseBuilders::CollectionResponseBuilder
          .new #: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem]

        Listeners::Completion.new(
          @response_builder,
          global_state,
          node_context,
          sorbet_level,
          dispatcher,
          document.uri,
          params.dig(:context, :triggerCharacter),
        )

        Addon.addons.each do |addon|
          addon.create_completion_listener(@response_builder, node_context, dispatcher, document.uri)
        end

        matched = node_context.node
        parent = node_context.parent
        return unless matched && parent

        @target = if parent.is_a?(Prism::ConstantPathNode) && matched.is_a?(Prism::ConstantReadNode)
          parent
        else
          matched
        end
      end

      # @override
      #: -> Array[Interface::CompletionItem]
      def perform
        return [] unless @target

        @dispatcher.dispatch_once(@target)
        @response_builder.response
      end
    end
  end
end
