# typed: strict
# frozen_string_literal: true

module RubyLsp
  module Rails
    class IndexingEnhancement < RubyIndexer::Enhancement
      extend T::Sig

      sig do
        override.params(
          call_node: Prism::CallNode,
        ).void
      end
      def on_call_node_enter(call_node)
        owner = @listener.current_owner
        return unless owner

        case call_node.name
        when :extend
          handle_concern_extend(owner, call_node)
        when :has_one, :has_many, :belongs_to, :has_and_belongs_to_many
          handle_association(owner, call_node)
        # for `class_methods do` blocks within concerns
        when :class_methods
          handle_class_methods(owner, call_node)
        end
      end

      sig do
        override.params(
          call_node: Prism::CallNode,
        ).void
      end
      def on_call_node_leave(call_node)
        if call_node.name == :class_methods && call_node.block
          @listener.pop_namespace_stack
        end
      end

      private

      sig do
        params(
          owner: RubyIndexer::Entry::Namespace,
          call_node: Prism::CallNode,
        ).void
      end
      def handle_association(owner, call_node)
        arguments = call_node.arguments&.arguments
        return unless arguments

        name_arg = arguments.first

        name = case name_arg
        when Prism::StringNode
          name_arg.content
        when Prism::SymbolNode
          name_arg.value
        end

        return unless name

        loc = name_arg.location

        # Reader
        reader_signatures = [RubyIndexer::Entry::Signature.new([])]
        @listener.add_method(name, loc, reader_signatures)

        # Writer
        writer_signatures = [
          RubyIndexer::Entry::Signature.new([RubyIndexer::Entry::RequiredParameter.new(name: name.to_sym)]),
        ]
        @listener.add_method("#{name}=", loc, writer_signatures)
      end

      sig { params(owner: RubyIndexer::Entry::Namespace, call_node: Prism::CallNode).void }
      def handle_concern_extend(owner, call_node)
        arguments = call_node.arguments&.arguments
        return unless arguments

        arguments.each do |node|
          next unless node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)

          module_name = node.full_name
          next unless module_name == "ActiveSupport::Concern"

          @listener.register_included_hook do |index, base|
            class_methods_name = "#{owner.name}::ClassMethods"

            if index.indexed?(class_methods_name)
              singleton = index.existing_or_new_singleton_class(base.name)
              singleton.mixin_operations << RubyIndexer::Entry::Include.new(class_methods_name)
            end
          end
        rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError,
               Prism::ConstantPathNode::MissingNodesInConstantPathError
          # Do nothing
        end
      end

      sig { params(owner: RubyIndexer::Entry::Namespace, call_node: Prism::CallNode).void }
      def handle_class_methods(owner, call_node)
        return unless call_node.block

        @listener.add_module("ClassMethods", call_node.location, call_node.location)
      end
    end
  end
end