# typed: strict
# frozen_string_literal: true

module Tapioca
  module Gem
    module Listeners
      class SorbetTypeVariables < Base
        extend T::Sig

        include Runtime::Reflection

        private

        sig { override.params(event: ScopeNodeAdded).void }
        def on_scope(event)
          constant = event.constant
          node = event.node

          compile_type_variable_declarations(node, constant)

          sclass = RBI::SingletonClass.new
          compile_type_variable_declarations(sclass, singleton_class_of(constant))
          node << sclass if sclass.nodes.length > 1
        end

        sig { params(tree: RBI::Tree, constant: Module).void }
        def compile_type_variable_declarations(tree, constant)
          # Try to find the type variables defined on this constant, bail if we can't
          type_variables = Runtime::GenericTypeRegistry.lookup_type_variables(constant)
          return unless type_variables

          # Map each type variable to its string representation.
          #
          # Each entry of `type_variables` maps a Module to a String, or
          # is a `has_attached_class!` declaration, and the order they are inserted
          # into the hash is the order they should be defined in the source code.
          type_variable_declarations = type_variables.filter_map do |type_variable|
            node = node_from_type_variable(type_variable)
            next unless node

            tree << node
          end

          return if type_variable_declarations.empty?

          tree << RBI::Extend.new("T::Generic")
        end

        sig { params(type_variable: Tapioca::TypeVariableModule).returns(T.nilable(RBI::Node)) }
        def node_from_type_variable(type_variable)
          case type_variable.type
          when Tapioca::TypeVariableModule::Type::HasAttachedClass
            RBI::Send.new(type_variable.serialize)
          else
            type_variable_name = type_variable.name
            return unless type_variable_name

            RBI::TypeMember.new(type_variable_name, type_variable.serialize)
          end
        end

        sig { override.params(event: NodeAdded).returns(T::Boolean) }
        def ignore?(event)
          event.is_a?(Tapioca::Gem::ForeignScopeNodeAdded)
        end
      end
    end
  end
end