# frozen_string_literal: true

module Mutation
  class Env
    # Bootstrap environment
    class Bootstrap
      include Adamantium::Flat, Concord::Public.new(:config), Procto.call(:env)

      SEMANTICS_MESSAGE_FORMAT =
        "%<message>s. Fix your lib to follow normal ruby semantics!\n" \
        '{Module,Class}#name should return resolvable constant name as String or nil'

      CLASS_NAME_RAISED_EXCEPTION =
        '%<scope_class>s#name from: %<scope>s raised an error: %<exception>s'

      CLASS_NAME_TYPE_MISMATCH_FORMAT =
        '%<scope_class>s#name from: %<scope>s returned %<name>s'

      private_constant(*constants(false))

      # Scopes that are eligible for matching
      #
      # @return [Enumerable<Matcher::Scope>]
      attr_reader :matchable_scopes

      # Parser for this environment
      #
      # @return [Parser]
      attr_reader :parser

      # Initialize object
      #
      # @return [Object]
      def initialize(*)
        super
        @parser = Parser.new
        infect
        initialize_matchable_scopes
        @integration = config.integration.new(config).setup
      end

      # Print warning message
      #
      # @param [String]
      #
      # @return [self]
      def warn(message)
        config.reporter.warn(message)
        self
      end

      # Environment after bootstraping
      #
      # @return [Env]
      # rubocop:disable MethodLength
      #
      def env
        subjects = matched_subjects
        Env.new(
          config:           config,
          integration:      integration,
          matchable_scopes: matchable_scopes,
          mutations:        subjects.flat_map(&:mutations),
          parser:           parser,
          selector:         Selector::Expression.new(integration),
          subjects:         subjects
        )
      end

    private

      # Configured mutation integration
      #
      # @return [Mutation::Integration]
      attr_reader :integration

      # Scope name from scoping object
      #
      # @param [Class, Module] scope
      #
      # @return [String]
      #   if scope has a name and does not raise exceptions obtaining it
      #
      # @return [nil]
      #   otherwise
      def scope_name(scope)
        scope.name
      rescue => exception
        semantics_warning(
          CLASS_NAME_RAISED_EXCEPTION,
          exception:   exception.inspect,
          scope:       scope,
          scope_class: scope.class
        )
        nil
      end

      # Infect environment
      #
      # @return [undefined]
      def infect
        config.includes.each(&config.load_path.method(:<<))
        config.requires.each(&config.kernel.method(:require))
      end

      # Matched subjects
      #
      # @return [Enumerable<Subject>]
      def matched_subjects
        Matcher::Compiler.call(config.matcher).call(self)
      end

      # Initialize matchable scopes
      #
      # @return [undefined]
      def initialize_matchable_scopes
        scopes = ObjectSpace.each_object(Module).each_with_object([]) do |scope, aggregate|
          expression = expression(scope) || next
          aggregate << Scope.new(scope, expression)
        end

        @matchable_scopes = scopes.sort_by { |scope| scope.expression.syntax }
      end

      # Try to turn scope into expression
      #
      # @param [Class, Module] scope
      #
      # @return [Expression]
      #   if scope can be represented in an expression
      #
      # @return [nil]
      #   otherwise
      def expression(scope)
        name = scope_name(scope) or return

        unless name.instance_of?(String)
          semantics_warning(
            CLASS_NAME_TYPE_MISMATCH_FORMAT,
            name:        name,
            scope_class: scope.class,
            scope:       scope
          )
          return
        end

        config.expression_parser.try_parse(name)
      end

      # Write a semantics warning
      #
      # @return [undefined]
      def semantics_warning(format, options)
        message = format % options
        warn(SEMANTICS_MESSAGE_FORMAT % { message: message })
      end
    end # Bootstrap
  end # Env
end # Mutation
