module Lita
  # Builds the configuration object that is stored in each {Lita::Registry}.
  # @since 4.0.0
  # @api private
  class DefaultConfiguration
    # Valid levels for Lita's logger.
    LOG_LEVELS = %w(debug info warn error fatal)

    # A {Registry} to extract configuration for plugins from.
    # @return [Lita::Registry] The registry.
    attr_reader :registry

    # The top-level {Lita::ConfigurationBuilder} attribute.
    # @return [Lita::Configuration] The root attribute.
    attr_reader :root

    # @param registry [Lita::Registry] The registry to build a default configuration object from.
    def initialize(registry)
      @registry = registry
      @root = ConfigurationBuilder.new

      adapters_config
      handlers_config
      http_config
      redis_config
      robot_config
    end

    # Processes the {Lita::ConfigurationBuilder} object to return a {Lita::Configuration}.
    # @return [Lita::Configuration] The built configuration object.
    def build
      final_config = root.build
      add_adapter_attribute(final_config)
      add_struct_access_to_redis(final_config.redis)
      final_config
    end

    private

    # Builds config.adapters
    def adapters_config
      adapters = registry.adapters

      root.config :adapters do
        adapters.each do |key, adapter|
          combine(key, adapter.configuration_builder)
        end
      end
    end

    # Builds config.adapter
    def add_adapter_attribute(config)
      config.singleton_class.class_exec { attr_accessor :adapter }
      config.adapter = Class.new(Config) do
        def []=(key, value)
          deprecation_warning

          super
        end

        def [](key)
          deprecation_warning

          super
        end

        def method_missing(name, *args)
          deprecation_warning

          super
        end

        def deprecation_warning
          Lita.logger.warn(I18n.t("lita.config.adapter_deprecated"))
        end
        private :deprecation_warning
      end.new
    end

    # Allow config.redis to be accessed as a struct, for backwards compatibility.
    def add_struct_access_to_redis(redis)
      def redis.method_missing(name, *args)
        Lita.logger.warn(I18n.t("lita.config.redis_struct_access_deprecated"))
        name_string = name.to_s
        if name_string.chomp!("=")
          self[name_string.to_sym] = args.first
        else
          self[name_string.to_sym]
        end
      end
    end

    # Builds config.handlers
    def handlers_config
      handlers = registry.handlers

      root.config :handlers do
        handlers.each do |handler|
          if handler.configuration_builder.children?
            combine(handler.namespace, handler.configuration_builder)
          elsif handler.respond_to?(:default_config)
            old_config = Config.new
            handler.default_config(old_config)
            config(handler.namespace, default: old_config)
          end
        end
      end
    end

    # Builds config.http
    def http_config
      root.config :http do
        config :host, type: String, default: "0.0.0.0"
        config :port, type: [Integer, String], default: 8080
        config :min_threads, type: [Integer, String], default: 0
        config :max_threads, type: [Integer, String], default: 16
        config :middleware, type: MiddlewareRegistry, default: MiddlewareRegistry.new
      end
    end

    # Builds config.redis
    def redis_config
      root.config :redis, type: Hash, default: {}
    end

    # Builds config.robot
    def robot_config
      root.config :robot do
        config :name, type: String, default: "Lita"
        config :mention_name, type: String
        config :alias, type: String
        config :adapter, types: [String, Symbol], default: :shell
        config :locale, types: [String, Symbol], default: I18n.locale
        config :default_locale, types: [String, Symbol], default: I18n.default_locale
        config :log_level, types: [String, Symbol], default: :info do
          validate do |value|
            unless LOG_LEVELS.include?(value.to_s.downcase.strip)
              "must be one of: #{LOG_LEVELS.join(', ')}"
            end
          end
        end
        config :log_formatter, type: Proc, default: (lambda do |severity, datetime, _progname, msg|
          "[#{datetime.utc}] #{severity}: #{msg}\n"
        end)
        config :admins
        config :error_handler, default: -> (_error) {} do
          validate do |value|
            "must respond to #call" unless value.respond_to?(:call)
          end
        end
      end
    end
  end
end
