# typed: strict
class ColorLogger < Logger
  extend T::Sig
  include ActiveSupport::TaggedLogging

  @quiet =
    T.let(Concurrent::ThreadLocalVar.new { 0 }, Concurrent::ThreadLocalVar)

  @log_line_accumulator =
    T.let(Concurrent::ThreadLocalVar.new { nil }, Concurrent::ThreadLocalVar)

  sig { params(sink: T.any(IO, StringIO)).returns(ColorLogger) }
  def self.make(sink)
    logger = ColorLogger.new(sink, @log_line_accumulator)
    ActiveSupport::TaggedLogging.new(logger)
  end

  # @param log_line_accumulator a proc that is called with a String and returns void
  sig do
    params(
      log_line_accumulator: T.nilable(T.proc.params(line: String).void),
    ).void
  end
  def self.log_line_accumulator=(log_line_accumulator)
    @log_line_accumulator.value = log_line_accumulator
  end

  sig { params(blk: T.proc.void).void }
  def self.quiet(&blk)
    @quiet.value += 1
    blk.call
  ensure
    @quiet.value -= 1
  end

  sig { params(blk: T.proc.void).void }
  def self.unquiet(&blk)
    saved = @quiet.value
    @quiet.value = 0
    blk.call
  ensure
    @quiet.value = saved
  end

  sig { returns(T::Boolean) }
  def self.quiet?
    @quiet.value > 0
  end

  sig { returns(T.nilable(T.any(String, T.proc.returns(String)))) }
  attr_reader :prefix

  sig { params(prefix: T.any(String, T.proc.returns(String))).void }
  def prefix=(prefix)
    @prefix = prefix
  end

  private

  sig do
    params(
      sink: T.any(IO, StringIO),
      log_line_accumulator: Concurrent::ThreadLocalVar,
    ).void
  end
  def initialize(sink, log_line_accumulator)
    super(sink)
    @prefix = T.let(nil, T.nilable(T.any(String, T.proc.returns(String))))
    @log_lines = T.let([], T::Array[String])

    this = self
    this.define_singleton_method(:prefix=) do |prefix|
      this.instance_variable_set(:@prefix, prefix)
    end

    self.formatter =
      proc do |severity, datetime, progname, msg|
        prefix = this.instance_variable_get(:@prefix)
        prefix = prefix.call if prefix.is_a?(Proc)

        line = [prefix, msg].reject(&:blank?).join(" ")
        if (lla = log_line_accumulator.value)
          lla.call(line)
        end
        line += "\n"
        line = "" if ColorLogger.quiet?
        line
      end
  end
end
