# typed: strict
# frozen_string_literal: true

module RubyLsp
  module Requests
    # ![Workspace symbol demo](../../workspace_symbol.gif)
    #
    # The [workspace symbol](https://microsoft.github.io/language-server-protocol/specification#workspace_symbol)
    # request allows fuzzy searching declarations in the entire project. On VS Code, use CTRL/CMD + T to search for
    # symbols.
    #
    # # Example
    #
    # ```ruby
    # # Searching for `Floo` will fuzzy match and return all declarations according to the query, including this `Foo`
    # class
    # class Foo
    # end
    # ```
    #
    class WorkspaceSymbol < Request
      extend T::Sig
      include Support::Common

      sig { params(global_state: GlobalState, query: T.nilable(String)).void }
      def initialize(global_state, query)
        super()
        @global_state = global_state
        @query = query
        @index = T.let(global_state.index, RubyIndexer::Index)
      end

      sig { override.returns(T::Array[Interface::WorkspaceSymbol]) }
      def perform
        @index.fuzzy_search(@query).filter_map do |entry|
          # If the project is using Sorbet, we let Sorbet handle symbols defined inside the project itself and RBIs, but
          # we still return entries defined in gems to allow developers to jump directly to the source
          file_path = entry.file_path
          next if @global_state.typechecker && not_in_dependencies?(file_path)

          # We should never show private symbols when searching the entire workspace
          next if entry.visibility == :private

          kind = kind_for_entry(entry)
          loc = entry.location

          # We use the namespace as the container name, but we also use the full name as the regular name. The reason we
          # do this is to allow people to search for fully qualified names (e.g.: `Foo::Bar`). If we only included the
          # short name `Bar`, then searching for `Foo::Bar` would not return any results
          *container, _short_name = entry.name.split("::")

          Interface::WorkspaceSymbol.new(
            name: entry.name,
            container_name: T.must(container).join("::"),
            kind: kind,
            location: Interface::Location.new(
              uri: URI::Generic.from_path(path: file_path).to_s,
              range:  Interface::Range.new(
                start: Interface::Position.new(line: loc.start_line - 1, character: loc.start_column),
                end: Interface::Position.new(line: loc.end_line - 1, character: loc.end_column),
              ),
            ),
          )
        end
      end

      private

      sig { params(entry: RubyIndexer::Entry).returns(T.nilable(Integer)) }
      def kind_for_entry(entry)
        case entry
        when RubyIndexer::Entry::Class
          Constant::SymbolKind::CLASS
        when RubyIndexer::Entry::Module
          Constant::SymbolKind::NAMESPACE
        when RubyIndexer::Entry::Constant
          Constant::SymbolKind::CONSTANT
        when RubyIndexer::Entry::Method
          entry.name == "initialize" ? Constant::SymbolKind::CONSTRUCTOR : Constant::SymbolKind::METHOD
        when RubyIndexer::Entry::Accessor
          Constant::SymbolKind::PROPERTY
        end
      end
    end
  end
end