# frozen_string_literal: true

module Bridgetown
  class LogAdapter
    attr_reader :writer, :messages, :level

    LOG_LEVELS = {
      debug: ::Logger::DEBUG,
      info: ::Logger::INFO,
      warn: ::Logger::WARN,
      error: ::Logger::ERROR,
    }.freeze

    # Create a new instance of a log writer
    #
    # @param writer [Logger] compatible instance
    # @param log_level [Symbol] the log level (`debug` | `info` | `warn` | `error`)
    def initialize(writer, level = :info)
      @messages = []
      @writer = writer
      self.log_level = level
    end

    # Set the log level on the writer
    #
    # @param log_level [Symbol] the log level (`debug` | `info` | `warn` | `error`)
    def log_level=(level)
      writer.level = level if level.is_a?(Integer) && level.between?(0, 3)
      writer.level = LOG_LEVELS[level] ||
        raise(ArgumentError, "unknown log level")
      @level = level
    end

    def adjust_verbosity(**options)
      # Quiet always wins.
      if options[:quiet]
        self.log_level = :error
      elsif options[:verbose]
        self.log_level = :debug
      end
      debug "Logging at level:", LOG_LEVELS.key(writer.level).to_s
    end

    # Print a debug message
    #
    # @param topic [String] e.g. "Configuration file", "Deprecation", etc.
    # @param message [String] the message detail
    def debug(topic, message = nil, &)
      write(:debug, topic, message, &)
    end

    # Print an informational message
    #
    # @param topic [String] e.g. "Configuration file", "Deprecation", etc.
    # @param message [String] the message detail
    def info(topic, message = nil, &)
      write(:info, topic, message, &)
    end

    # Print a warning message
    #
    # @param topic [String] e.g. "Configuration file", "Deprecation", etc.
    # @param message [String] the message detail
    def warn(topic, message = nil, &)
      write(:warn, topic, message, &)
    end

    # Print an error message
    #
    # @param topic [String] e.g. "Configuration file", "Deprecation", etc.
    # @param message [String] the message detail
    def error(topic, message = nil, &)
      write(:error, topic, message, &)
    end

    # Print an error message and immediately abort the process
    #
    # @param topic [String] e.g. "Configuration file", "Deprecation", etc.
    # @param message [String] the message detail
    def abort_with(topic, message = nil, &)
      error(topic, message, &)
      abort
    end

    # Build a topic method
    #
    # @param topic [String] e.g. "Configuration file", "Deprecation", etc.
    # @param message [String] the message detail
    # @return [String] the formatted message
    def message(topic, message = nil)
      raise ArgumentError, "block or message, not both" if block_given? && message

      message = yield if block_given?
      message = message.to_s.gsub(%r!\s+!, " ")
      topic = formatted_topic(topic, block_given?)
      out = topic + message
      messages << out
      out
    end

    # Format the topic
    #
    # @param topic [String] e.g. "Configuration file", "Deprecation", etc.
    # @param colon [Boolean]
    # @return [String] formatted topic statement
    def formatted_topic(topic, colon = false) # rubocop:disable Style/OptionalBooleanParameter
      "#{topic}#{colon ? ": " : " "}".rjust(20)
    end

    # Check if the message should be written given the log level
    #
    # @param level_of_message [Symbol] the message level (`debug` | `info` | `warn` | `error`)
    # @return [Boolean] whether the message should be written to the log
    def write_message?(level_of_message)
      LOG_LEVELS.fetch(level) <= LOG_LEVELS.fetch(level_of_message)
    end

    # Log a message. If a block is provided containing the message, use that instead.
    #
    # @param level_of_message [Symbol] the message level (`debug` | `info` | `warn` | `error`)
    # @param topic [String] e.g. "Configuration file", "Deprecation", etc.
    # @param message [String] the message detail
    # @return [BasicObject] false if the message was not written, otherwise returns the value of
    #   calling the appropriate writer method, e.g. writer.info.
    def write(level_of_message, topic, message = nil, &)
      return false unless write_message?(level_of_message)

      writer.public_send(level_of_message, message(topic, message, &))
    end
  end
end
