class Tapioca::Gem::Listeners::Methods

def compile_directly_owned_methods(

: (RBI::Tree tree, String module_name, Module mod, ?Array[Symbol] for_visibility, ?attached_class: Module?) -> void
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)

: (RBI::Tree tree, String symbol_name, Module constant, UnboundMethod? method, ?RBI::Visibility visibility) -> void
def compile_method(tree, symbol_name, constant, method, visibility = RBI::Public.new)
  return unless method
  return unless method_owned_by_constant?(method, constant)
  begin
    signature = signature_of!(method)
    method = signature.method if signature #: UnboundMethod
    case @pipeline.method_definition_in_gem(method.name, constant)
    when Pipeline::MethodUnknown
      # This means that this is a C-method. Thus, we want to
      # skip it only if the constant is an ignored one, since
      # that probably means that we've hit a C-method for a
      # core type.
      return if @pipeline.symbol_in_payload?(symbol_name)
    when Pipeline::MethodNotInGem
      # Do not process this method, if it is not defined by the current gem
      return
    end
  rescue SignatureBlockError => error
    @pipeline.error_handler.call(<<~MSG)
      Unable to compile signature for method: #{method.owner}##{method.name}
        Exception raised when loading signature: #{error.cause.inspect}
    MSG
    signature = nil
  end
  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 = method.parameters #: Array[[Symbol, 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)

: (NodeAdded event) -> bool
@override
def ignore?(event)
  event.is_a?(Tapioca::Gem::ForeignScopeNodeAdded)
end

def initialize_method_for(constant)

: (Module constant) -> UnboundMethod?
def initialize_method_for(constant)
  constant.instance_method(:initialize)
rescue
  nil
end

def method_names_by_visibility(mod)

: (Module mod) -> Hash[Symbol, Array[Symbol]]
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)

: (Module? attached_class, Symbol method_name) -> bool?
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)

: (UnboundMethod method, Module constant) -> bool
methods are owned by the constant, it means that the constant declares the method.
It walks up the ancestor tree via the `super_method` method; if any of the super
This method implements a better way of checking whether a constant defines a method.

will be owned by the prepended module, not the constant.
in the case that a method is also defined in a module prepended to the constant, it
In most cases, it works to check that the constant is the method owner. However,

Check whether the method is defined by the constant.
def method_owned_by_constant?(method, constant)
  # Widen the type of `method` to be nilable
  method = method #: UnboundMethod?
  while method
    return true if method.owner == constant
    method = method.super_method
  end
  false
end

def on_scope(event)

: (ScopeNodeAdded event) -> void
@override
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)

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