# 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
      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: T.any(RubyDocument, ERBDocument),
          global_state: GlobalState,
          params: T::Hash[Symbol, T.untyped],
          sorbet_level: RubyDocument::SorbetLevel,
          dispatcher: Prism::Dispatcher,
        ).void
      end
      def initialize(document, global_state, params, sorbet_level, 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
        delegate_request_if_needed!(global_state, document, char_position)

        node_context = RubyDocument.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,
          ],
          code_units_cache: document.code_units_cache,
        )
        @response_builder = T.let(
          ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem].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

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

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