<commit_before># frozen_string_literal: true
module ActiveMocker
  class MockCreator
    def initialize(file:,
                   file_out:,
                   schema_scrapper:,
                   template_creator: nil,
                   class_introspector: nil,
                   enabled_partials: nil,
                   klasses_to_be_mocked:,
                   mock_append_name:,
                   active_record_model:,
                   active_record_base_klass: ActiveRecord::Base)
      @file                     = file
      @file_out                 = file_out
      @schema_scrapper          = schema_scrapper
      @template_creator         = template_creator || template_creator_default(file_out)
      @class_introspector       = class_introspector || class_introspector_default
      @enabled_partials         = enabled_partials || self.class.enabled_partials_default
      @klasses_to_be_mocked     = klasses_to_be_mocked
      @active_record_base_klass = active_record_base_klass
      @mock_append_name         = mock_append_name
      @active_record_model      = active_record_model
      @errors                   = []
      @completed                = false
    end

    def create
      verify_class
      if errors.empty?
        begin
          template_creator.render
        rescue => e
          raise e unless error_already_collected?(e)
        end
        file_out.close
        @completed = true
      end
      self
    end

    def completed?
      @completed
    end

    attr_reader :errors

    private

    attr_reader :file,
                :file_out,
                :schema_scrapper,
                :template_creator,
                :class_introspector,
                :enabled_partials,
                :klasses_to_be_mocked,
                :active_record_base_klass,
                :mock_append_name

    def error_already_collected?(e)
      errors.any? { |eo| eo.original_error == e }
    end

    # -- Defaults -- #
    def template_creator_default(file_out)
      TemplateCreator.new(file_out:     file_out,
                          erb_template: File.new(File.join(File.dirname(__FILE__), "mock_template.erb"), "r"),
                          binding:      binding,
                          post_process: lambda do |str|
                            ruby_code = DissociatedIntrospection::RubyCode.build_from_source(str,
                                                                                             parse_with_comments: true)
                            DissociatedIntrospection::WrapInModules.new(ruby_code: ruby_code)
                              .call(modules: nested_modules)
                              .source_from_ast.gsub(/end\n/, "end\n\n")
                          end)
    end

    def class_introspector_default
      DissociatedIntrospection::Inspection.new(file: file)
    end

    class << self
      def enabled_partials_default
        [
          :modules_constants,
          :class_methods,
          :attributes,
          :scopes,
          :recreate_class_method_calls,
          :defined_methods,
          :associations,
        ]
      end

      public :enabled_partials_default
    end

    # -- END defaults -- #

    def verify_class
      v = ParentClass.new(parsed_source:        class_introspector.parsed_source,
                          klasses_to_be_mocked: klasses_to_be_mocked,
                          mock_append_name:     mock_append_name).call
      errors << v.error if v.error
      @parent_class = v.parent_mock_name
    end

    public

    def partials
      OpenStruct.new(enabled_partials.each_with_object({}) do |p, hash|
        begin
          file = File.new(File.join(File.dirname(__FILE__), "mock_template/_#{p}.erb"))
          extend("ActiveMocker::MockCreator::#{p.to_s.camelize}".constantize)
          hash[p] = ERB.new(file.read, nil, "-", "_sub#{p}").result(binding)
        rescue => e
          errors << ErrorObject.new(class_name:     class_name,
                                    original_error: e, type: :generation,
                                    level:          :error,
                                    message:        e.message)
          errors << ErrorObject.new(class_name:     class_name,
                                    original_error: e,
                                    type:           :erb,
                                    level:          :debug,
                                    message:        "Erb template: #{p} failed.\n#{file.path}")
          raise e
        end
      end)
    end

    def class_name
      @class_name ||= class_introspector.parsed_source.class_name.split("::").last
    end

    def nested_modules
      @nested_modules ||= begin
        class_introspector.parsed_source.module_nesting.join("::")
      end
    end

    attr_reader :parent_class, :active_record_model

    def primary_key
      @primary_key ||= ActiveRecordSchemaScrapper::Attribute.new(name: "id", type: :integer)
    end

    module ModulesConstants
      def constants
        class_introspector.get_class.constants.each_with_object({}) do |v, const|
          c        = class_introspector.get_class.const_get(v)
          const[v] = c unless c.class == Module || c.class == Class
        end
      end

      def modules
        @modules ||= begin
          {
            included: get_module_by_reference(:included_modules),
            extended: get_module_by_reference(:extended_modules),
          }
        end
      end

      private

      def get_module_by_reference(type)
        isolated_module_names = reject_local_const(class_introspector.public_send(type)).map(&:referenced_name)
        real_module_names = get_real_module(type).map(&:name).compact
        isolated_module_names.map do |isolated_name|
          real_name = real_module_names.find do |rmn|
            real_parts = rmn.split("::")
            total_parts_count = active_record_model.name.split("::").count + isolated_name.split("::").count
            real_parts.include?(active_record_model.name) && real_parts.include?(isolated_name) && total_parts_count == real_parts.count
          end
          real_name ? real_name : isolated_name
        end
      end

      def get_real_module(type)
        if type == :extended_modules
          active_record_model.singleton_class.included_modules
        else
          active_record_model.included_modules
        end
      end

      def reject_local_const(source)
        source.reject do |n|
          class_introspector.locally_defined_constants.values.include?(n)
        end
      end

      def find_fully_specified_version(source)
        active_record_model
        source

        active_record_model.singleton_class.included_modules do |_module|
          _module.name.split("::").last
        end
      end
    end

    module Attributes
      def attributes<commit_msg>Style fixes<commit_after># frozen_string_literal: true
module ActiveMocker
  class MockCreator
    def initialize(file:,
                   file_out:,
                   schema_scrapper:,
                   template_creator: nil,
                   class_introspector: nil,
                   enabled_partials: nil,
                   klasses_to_be_mocked:,
                   mock_append_name:,
                   active_record_model:,
                   active_record_base_klass: ActiveRecord::Base)
      @file                     = file
      @file_out                 = file_out
      @schema_scrapper          = schema_scrapper
      @template_creator         = template_creator || template_creator_default(file_out)
      @class_introspector       = class_introspector || class_introspector_default
      @enabled_partials         = enabled_partials || self.class.enabled_partials_default
      @klasses_to_be_mocked     = klasses_to_be_mocked
      @active_record_base_klass = active_record_base_klass
      @mock_append_name         = mock_append_name
      @active_record_model      = active_record_model
      @errors                   = []
      @completed                = false
    end

    def create
      verify_class
      if errors.empty?
        begin
          template_creator.render
        rescue => e
          raise e unless error_already_collected?(e)
        end
        file_out.close
        @completed = true
      end
      self
    end

    def completed?
      @completed
    end

    attr_reader :errors

    private

    attr_reader :file,
                :file_out,
                :schema_scrapper,
                :template_creator,
                :class_introspector,
                :enabled_partials,
                :klasses_to_be_mocked,
                :active_record_base_klass,
                :mock_append_name

    def error_already_collected?(e)
      errors.any? { |eo| eo.original_error == e }
    end

    # -- Defaults -- #
    def template_creator_default(file_out)
      TemplateCreator.new(file_out:     file_out,
                          erb_template: File.new(File.join(File.dirname(__FILE__), "mock_template.erb"), "r"),
                          binding:      binding,
                          post_process: lambda do |str|
                            ruby_code = DissociatedIntrospection::RubyCode.build_from_source(str,
                                                                                             parse_with_comments: true)
                            DissociatedIntrospection::WrapInModules.new(ruby_code: ruby_code)
                              .call(modules: nested_modules)
                              .source_from_ast.gsub(/end\n/, "end\n\n")
                          end)
    end

    def class_introspector_default
      DissociatedIntrospection::Inspection.new(file: file)
    end

    class << self
      def enabled_partials_default
        [
          :modules_constants,
          :class_methods,
          :attributes,
          :scopes,
          :recreate_class_method_calls,
          :defined_methods,
          :associations,
        ]
      end

      public :enabled_partials_default
    end

    # -- END defaults -- #

    def verify_class
      v = ParentClass.new(parsed_source:        class_introspector.parsed_source,
                          klasses_to_be_mocked: klasses_to_be_mocked,
                          mock_append_name:     mock_append_name).call
      errors << v.error if v.error
      @parent_class = v.parent_mock_name
    end

    public

    def partials
      OpenStruct.new(enabled_partials.each_with_object({}) do |p, hash|
        begin
          file = File.new(File.join(File.dirname(__FILE__), "mock_template/_#{p}.erb"))
          extend("ActiveMocker::MockCreator::#{p.to_s.camelize}".constantize)
          hash[p] = ERB.new(file.read, nil, "-", "_sub#{p}").result(binding)
        rescue => e
          errors << ErrorObject.new(class_name:     class_name,
                                    original_error: e, type: :generation,
                                    level:          :error,
                                    message:        e.message)
          errors << ErrorObject.new(class_name:     class_name,
                                    original_error: e,
                                    type:           :erb,
                                    level:          :debug,
                                    message:        "Erb template: #{p} failed.\n#{file.path}")
          raise e
        end
      end)
    end

    def class_name
      @class_name ||= class_introspector.parsed_source.class_name.split("::").last
    end

    def nested_modules
      @nested_modules ||= begin
        class_introspector.parsed_source.module_nesting.join("::")
      end
    end

    attr_reader :parent_class, :active_record_model

    def primary_key
      @primary_key ||= ActiveRecordSchemaScrapper::Attribute.new(name: "id", type: :integer)
    end

    module ModulesConstants
      def constants
        class_introspector.get_class.constants.each_with_object({}) do |v, const|
          c        = class_introspector.get_class.const_get(v)
          const[v] = c unless c.class == Module || c.class == Class
        end
      end

      def modules
        @modules ||= begin
          {
            included: get_module_by_reference(:included_modules),
            extended: get_module_by_reference(:extended_modules),
          }
        end
      end

      private

      def get_module_by_reference(type)
        isolated_module_names = reject_local_const(class_introspector.public_send(type)).map(&:referenced_name)
        real_module_names = get_real_module(type).map(&:name).compact
        isolated_module_names.map do |isolated_name|
          real_name = real_module_names.detect do |rmn|
            real_parts = rmn.split("::")
            total_parts_count = active_record_model.name.split("::").count + isolated_name.split("::").count
            [
              real_parts.include?(active_record_model.name),
              real_parts.include?(isolated_name),
              (total_parts_count == real_parts.count)
            ].all?
          end
          real_name ? real_name : isolated_name
        end
      end

      def get_real_module(type)
        if type == :extended_modules
          active_record_model.singleton_class.included_modules
        else
          active_record_model.included_modules
        end
      end

      def reject_local_const(source)
        source.reject do |n|
          class_introspector.locally_defined_constants.values.include?(n)
        end
      end
    end

    module Attributes
      def attributes
{'fraction_non_alphanumeric': 0.07679870654810024, 'fraction_numerical': 0.0, 'mean_word_length': 1.6454121306376361, 'pattern_counts': {'":': 11, '<': 19, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 22, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4871399', 'n_tokens_mistral': 3857, 'n_tokens_neox': 3740, 'n_words': 631}