# typed: strict
# frozen_string_literal: true

require "ruby_lsp/listeners/completion"

module RubyLsp
  module Requests
    # ![Completion demo](../../completion.gif)
    #
    # The [completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion)
    # suggests possible completions according to what the developer is typing.
    #
    # Currently supported targets:
    #
    # - Classes
    # - Modules
    # - Constants
    # - Require paths
    # - Methods invoked on self only
    # - Instance variables
    #
    # # Example
    #
    # ```ruby
    # require "ruby_lsp/requests" # --> completion: suggests `base_request`, `code_actions`, ...
    #
    # RubyLsp::Requests:: # --> completion: suggests `Completion`, `Hover`, ...
    # ```
    class Completion < Request
      extend T::Sig

      class << self
        extend T::Sig

        sig { returns(Interface::CompletionOptions) }
        def provider
          Interface::CompletionOptions.new(
            resolve_provider: true,
            trigger_characters: ["/", "\"", "'", ":", "@", "."],
            completion_item: {
              labelDetailsSupport: true,
            },
          )
        end
      end

      sig do
        params(
          document: Document,
          global_state: GlobalState,
          params: T::Hash[Symbol, T.untyped],
          typechecker_enabled: T::Boolean,
          dispatcher: Prism::Dispatcher,
        ).void
      end
      def initialize(document, global_state, params, typechecker_enabled, dispatcher)
        super()
        @target = T.let(nil, T.nilable(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.create_scanner.find_char_position(params[:position]) - 1
        node_context = document.locate(
          document.parse_result.value,
          char_position,
          node_types: [
            Prism::CallNode,
            Prism::ConstantReadNode,
            Prism::ConstantPathNode,
            Prism::InstanceVariableReadNode,
            Prism::InstanceVariableAndWriteNode,
            Prism::InstanceVariableOperatorWriteNode,
            Prism::InstanceVariableOrWriteNode,
            Prism::InstanceVariableTargetNode,
            Prism::InstanceVariableWriteNode,
          ],
        )
        @response_builder = T.let(
          ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem].new,
          ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
        )

        Listeners::Completion.new(
          @response_builder,
          global_state,
          node_context,
          typechecker_enabled,
          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

      sig { override.returns(T::Array[Interface::CompletionItem]) }
      def perform
        return [] unless @target

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