class Tapioca::Gem::Listeners::Methods

def compile_directly_owned_methods(

def compile_directly_owned_methods(
  tree,
  module_name,
  mod,
  for_visibility = [:public, :protected, :private],
  attached_class: nil
)
  method_names_by_visibility(mod)
    .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
    .each do |visibility, method_list|
      method_list.sort!.map do |name|
        next if name == :initialize
        next if method_new_in_abstract_class?(attached_class, name)
        vis = case visibility
        when :protected
          RBI::Protected.new
        when :private
          RBI::Private.new
        else
          RBI::Public.new
        end
        compile_method(tree, module_name, mod, mod.instance_method(name), vis)
      end
    end
end

def compile_method(tree, symbol_name, constant, method, visibility = RBI::Public.new)

def compile_method(tree, symbol_name, constant, method, visibility = RBI::Public.new)
  return unless method
  return unless method_owned_by_constant?(method, constant)
  return if @pipeline.symbol_in_payload?(symbol_name) && !@pipeline.method_in_gem?(method)
  signature = lookup_signature_of(method)
  method = T.let(signature.method, UnboundMethod) if signature
  method_name = method.name.to_s
  return unless valid_method_name?(method_name)
  return if struct_method?(constant, method_name)
  return if method_name.start_with?("__t_props_generated_")
  parameters = T.let(method.parameters, T::Array[[Symbol, T.nilable(Symbol)]])
  sanitized_parameters = parameters.each_with_index.map do |(type, name), index|
    fallback_arg_name = "_arg#{index}"
    name = if name
      name.to_s
    else
      # For attr_writer methods, Sorbet signatures have the name
      # of the method (without the trailing = sign) as the name of
      # the only parameter. So, if the parameter does not have a name
      # then the replacement name should be the name of the method
      # (minus trailing =) if and only if there is a signature for the
      # method and the parameter is required and there is a single
      # parameter and the signature also defines a single parameter and
      # the name of the method ends with a = character.
      writer_method_with_sig =
        signature && type == :req &&
        parameters.size == 1 &&
        signature.arg_types.size == 1 &&
        method_name[-1] == "="
      if writer_method_with_sig
        method_name.delete_suffix("=")
      else
        fallback_arg_name
      end
    end
    # Sanitize param names
    name = fallback_arg_name unless valid_parameter_name?(name)
    [type, name]
  end
  rbi_method = RBI::Method.new(
    method_name,
    is_singleton: constant.singleton_class?,
    visibility: visibility,
  )
  sanitized_parameters.each do |type, name|
    case type
    when :req
      rbi_method << RBI::ReqParam.new(name)
    when :opt
      rbi_method << RBI::OptParam.new(name, "T.unsafe(nil)")
    when :rest
      rbi_method << RBI::RestParam.new(name)
    when :keyreq
      rbi_method << RBI::KwParam.new(name)
    when :key
      rbi_method << RBI::KwOptParam.new(name, "T.unsafe(nil)")
    when :keyrest
      rbi_method << RBI::KwRestParam.new(name)
    when :block
      rbi_method << RBI::BlockParam.new(name)
    end
  end
  @pipeline.push_method(symbol_name, constant, method, rbi_method, signature, sanitized_parameters)
  tree << rbi_method
end

def ignore?(event)

def ignore?(event)
  event.is_a?(Tapioca::Gem::ForeignScopeNodeAdded)
end

def initialize_method_for(constant)

def initialize_method_for(constant)
  constant.instance_method(:initialize)
rescue
  nil
end

def lookup_signature_of(method)

def lookup_signature_of(method)
  signature_of!(method)
rescue LoadError, StandardError => error
  @pipeline.error_handler.call(<<~MSG)
    Unable to compile signature for method: #{method.owner}##{method.name}
      Exception raised when loading signature: #{error.inspect}
  MSG
  nil
end

def method_names_by_visibility(mod)

def method_names_by_visibility(mod)
  {
    public: public_instance_methods_of(mod),
    protected: protected_instance_methods_of(mod),
    private: private_instance_methods_of(mod),
  }
end

def method_new_in_abstract_class?(attached_class, method_name)

def method_new_in_abstract_class?(attached_class, method_name)
  attached_class &&
    method_name == :new &&
    !!abstract_type_of(attached_class) &&
    Class === attached_class.singleton_class
end

def method_owned_by_constant?(method, constant)

def method_owned_by_constant?(method, constant)
  # Widen the type of `method` to be nilable
  method = T.let(method, T.nilable(UnboundMethod))
  while method
    return true if method.owner == constant
    method = method.super_method
  end
  false
end

def on_scope(event)

def on_scope(event)
  symbol = event.symbol
  constant = event.constant
  node = event.node
  compile_method(node, symbol, constant, initialize_method_for(constant))
  compile_directly_owned_methods(node, symbol, constant)
  compile_directly_owned_methods(node, symbol, singleton_class_of(constant), attached_class: constant)
end

def struct_method?(constant, method_name)

def struct_method?(constant, method_name)
  return false unless T::Props::ClassMethods === constant
  constant
    .props
    .keys
    .include?(method_name.gsub(/=$/, "").to_sym)
end