# typed: strict
# frozen_string_literal: true

# Parlour type signature generator plugin for Homebrew DSL attributes.
class Attr < Parlour::Plugin
  sig { override.params(root: Parlour::RbiGenerator::Namespace).void }
  def generate(root)
    tree = T.let([], T::Array[T.untyped])
    Homebrew::Parlour.ast_list.each do |node|
      tree += find_custom_attr(node)
    end
    process_custom_attr(tree, root)
  end

  sig { override.returns(T.nilable(String)) }
  def strictness
    "strict"
  end

  private

  sig { params(node: Parser::AST::Node, list: T::Array[String]).returns(T::Array[String]) }
  def traverse_module_name(node, list = [])
    parent, name = node.children
    list = traverse_module_name(parent, list) if parent
    list << name.to_s
    list
  end

  sig { params(node: T.nilable(Parser::AST::Node)).returns(T.nilable(String)) }
  def extract_module_name(node)
    return if node.nil?

    traverse_module_name(node).join("::")
  end

  sig { params(node: Parser::AST::Node).returns(T::Array[T.untyped]) }
  def find_custom_attr(node)
    tree = T.let([], T::Array[T.untyped])
    children = node.children.dup

    if node.type == :begin
      children.each do |child|
        subtree = find_custom_attr(child)
        tree += subtree unless subtree.empty?
      end
    elsif node.type == :sclass
      subtree = find_custom_attr(node.children[1])
      return tree if subtree.empty?

      tree << [:sclass, subtree]
    elsif node.type == :class || node.type == :module
      element = []
      case node.type
      when :class
        element << :class
        element << extract_module_name(children.shift)
        element << extract_module_name(children.shift)
      when :module
        element << :module
        element << extract_module_name(children.shift)
      end

      body = children.shift
      return tree if body.nil?

      subtree = find_custom_attr(body)
      return tree if subtree.empty?

      element << subtree
      tree << element
    elsif node.type == :send && children.shift.nil?
      method_name = children.shift

      case method_name
      when :attr_rw, :attr_predicate
        children.each do |name_node|
          tree << [method_name, name_node.children.first.to_s]
        end
      when :delegate
        children.each do |name_node|
          name_node.children.each do |pair|
            delegated_method = pair.children.first
            delegated_methods = if delegated_method.type == :array
              delegated_method.children
            else
              [delegated_method]
            end

            delegated_methods.each do |delegated_method_sym|
              tree << [method_name, delegated_method_sym.children.first.to_s]
            end
          end
        end
      end
    end

    tree
  end

  ARRAY_METHODS = T.let(["to_a", "to_ary"].freeze, T::Array[String])
  HASH_METHODS = T.let(["to_h", "to_hash"].freeze, T::Array[String])
  STRING_METHODS = T.let(["to_s", "to_str", "to_json"].freeze, T::Array[String])

  sig { params(tree: T::Array[T.untyped], namespace: Parlour::RbiGenerator::Namespace, sclass: T::Boolean).void }
  def process_custom_attr(tree, namespace, sclass: false)
    tree.each do |node|
      type = node.shift
      case type
      when :sclass
        process_custom_attr(node.shift, namespace, sclass: true)
      when :class
        class_namespace = namespace.create_class(node.shift, superclass: node.shift)
        process_custom_attr(node.shift, class_namespace)
      when :module
        module_namespace = namespace.create_module(node.shift)
        process_custom_attr(node.shift, module_namespace)
      when :attr_rw
        name = node.shift
        name = "self.#{name}" if sclass
        namespace.create_method(name,
                                parameters:  [
                                  Parlour::RbiGenerator::Parameter.new("arg", type:    "T.untyped",
                                                                              default: "T.unsafe(nil)"),
                                ],
                                return_type: "T.untyped")
      when :attr_predicate
        name = node.shift
        name = "self.#{name}" if sclass
        namespace.create_method(name, return_type: "T::Boolean")
      when :delegate
        name = node.shift

        return_type = if name.end_with?("?")
          "T::Boolean"
        elsif ARRAY_METHODS.include?(name)
          "Array"
        elsif HASH_METHODS.include?(name)
          "Hash"
        elsif STRING_METHODS.include?(name)
          "String"
        else
          "T.untyped"
        end

        name = "self.#{name}" if sclass

        namespace.create_method(
          name,
          parameters:  [
            Parlour::RbiGenerator::Parameter.new("*args"),
            Parlour::RbiGenerator::Parameter.new("**options"),
            Parlour::RbiGenerator::Parameter.new("&block"),
          ],
          return_type: return_type,
        )
      else
        raise "Malformed tree."
      end
    end
  end
end
