# frozen_string_literal: true

module RailsBestPractices
  module Reviews
    # Find out unused methods in models.
    #
    # Implemenation:
    #
    # Review process:
    #   remember all method calls,
    #   at end, check if all defined methods are called,
    #   if not, non called methods are unused.
    class RemoveUnusedMethodsInModelsReview < Review
      include Classable
      include Callable
      include Exceptable

      interesting_nodes :command, :command_call, :method_add_arg
      interesting_files ALL_FILES

      def initialize(options = {})
        super
        @model_methods = Prepares.model_methods
      end

      # skip scope and validate nodes for start_command callbacks.
      def skip_command_callback_nodes
        %w[named_scope scope validate validate_on_create validate_on_update]
      end

      # mark validate methods as used.
      # mark key method and value method for collection_select and grouped_collection_select.
      add_callback :start_command do |node|
        arguments = node.arguments.all
        case node.message.to_s
        when 'validate', 'validate_on_create', 'validate_on_update'
          arguments.each { |argument| mark_used(argument) }
        when 'collection_select'
          mark_used(arguments[3])
          mark_used(arguments[4])
        when 'grouped_collection_select'
          mark_used(arguments[3])
          mark_used(arguments[4])
          mark_used(arguments[5])
          mark_used(arguments[6])
        end
      end

      # mark key method and value method for collection_select and grouped_collection_select.
      add_callback :start_command_call do |node|
        arguments = node.arguments.all
        case node.message.to_s
        when 'collection_select'
          mark_used(arguments[2])
          mark_used(arguments[3])
        when 'grouped_collection_select'
          mark_used(arguments[2])
          mark_used(arguments[3])
          mark_used(arguments[4])
          mark_used(arguments[5])
        end
      end

      # mark key method and value method for options_from_collection_for_select and
      # option_groups_from_collection_for_select.
      add_callback :start_method_add_arg do |node|
        arguments = node.arguments.all
        case node.message.to_s
        when 'options_from_collection_for_select'
          mark_used(arguments[1])
          mark_used(arguments[2])
        when 'option_groups_from_collection_for_select'
          mark_used(arguments[1])
          mark_used(arguments[2])
          mark_used(arguments[3])
          mark_used(arguments[4])
        end
      end

      # get all unused methods at the end of review process.
      add_callback :after_check do
        @model_methods.get_all_unused_methods.each do |method|
          next unless !excepted?(method) && method.method_name !~ /=$/

          add_error "remove unused methods (#{method.class_name}##{method.method_name})",
                    method.file,
                    method.line_number
        end
      end

      protected

      def methods
        @model_methods
      end

      def internal_except_methods
        %w[
          initialize
          validate
          validate_each
          validate_on_create
          validate_on_update
          human_attribute_name
          assign_attributes
          attributes
          attribute
          to_xml
          to_json
          as_json
          to_param
          before_save
          before_create
          before_update
          before_destroy
          after_save
          after_create
          after_update
          after_destroy
          after_find
          after_initialize
          method_missing
          table_name
          module_prefix
        ].map { |method_name| "*\##{method_name}" }
      end
    end
  end
end
