# typed: strict
# frozen_string_literal: true

module RBI
  module Rewriters
    class SortNodes < Visitor
      extend T::Sig

      sig { override.params(node: T.nilable(Node)).void }
      def visit(node)
        sort_node_names!(node) if node

        return unless node.is_a?(Tree)
        visit_all(node.nodes)
        original_order = node.nodes.map.with_index.to_h
        node.nodes.sort! do |a, b|
          # First we try to compare the nodes by their node rank (based on the node type)
          res = node_rank(a) <=> node_rank(b)
          next res if res != 0 # we can sort the nodes by their rank, let's stop here

          # Then, if the nodes ranks are the same (res == 0), we try to compare the nodes by their name
          res = node_name(a) <=> node_name(b)
          next res if res && res != 0 # we can sort the nodes by their name, let's stop here

          # Finally, if the two nodes have the same rank and the same name or at least one node is anonymous then,
          T.must(original_order[a]) <=> T.must(original_order[b]) # we keep the original order
        end
      end

      private

      sig { params(node: Node).returns(Integer) }
      def node_rank(node)
        case node
        when Group                then group_rank(node.kind)
        when Include, Extend      then 10
        when Helper               then 20
        when TypeMember           then 30
        when MixesInClassMethods  then 40
        when Send                 then 50
        when TStructField         then 60
        when TEnumBlock           then 70
        when Attr                 then 75
        when Method
          if node.name == "initialize"
            81
          elsif !node.is_singleton
            82
          else
            83
          end
        when SingletonClass       then 90
        when Scope, Const         then 100
        else
          110
        end
      end

      sig { params(kind: Group::Kind).returns(Integer) }
      def group_rank(kind)
        case kind
        when Group::Kind::Mixins              then 0
        when Group::Kind::Helpers             then 1
        when Group::Kind::TypeMembers         then 2
        when Group::Kind::MixesInClassMethods then 3
        when Group::Kind::Sends               then 5
        when Group::Kind::TStructFields       then 6
        when Group::Kind::TEnums              then 7
        when Group::Kind::Attrs               then 8
        when Group::Kind::Inits               then 9
        when Group::Kind::Methods             then 10
        when Group::Kind::SingletonClasses    then 11
        when Group::Kind::Consts              then 12
        else
          T.absurd(kind)
        end
      end

      sig { params(node: Node).returns(T.nilable(String)) }
      def node_name(node)
        case node
        when Module, Class, Struct, Const, Method, Helper, TStructField
          node.name
        when Attr
          node.names.first.to_s
        end
      end

      sig { params(node: Node).void }
      def sort_node_names!(node)
        case node
        when Attr
          node.names.sort!
        end
      end
    end
  end

  class Tree
    extend T::Sig

    sig { void }
    def sort_nodes!
      visitor = Rewriters::SortNodes.new
      visitor.visit(self)
    end
  end
end
