# frozen_string_literal: true

module TurboTests
  class Reporter
    def self.from_config(formatter_config, start_time, max_timings_count: nil)
      reporter = new(start_time:, max_timings_count:)

      formatter_config.each do |config|
        name, outputs = config.values_at(:name, :outputs)

        outputs.map! { |filename| filename == "-" ? STDOUT : File.open(filename, "w") }

        reporter.add(name, outputs)
      end

      reporter
    end

    attr_reader :pending_examples
    attr_reader :failed_examples
    attr_reader :formatters

    def initialize(start_time:, max_timings_count:)
      @formatters = []
      @pending_examples = []
      @failed_examples = []
      @all_examples = []
      @start_time = start_time
      @messages = []
      @errors_outside_of_examples_count = 0
      @timings = []
      @max_timings_count = max_timings_count
    end

    def add(name, outputs)
      outputs.each do |output|
        formatter_class =
          case name
          when "p", "progress"
            TurboTests::ProgressFormatter
          when "d", "documentation"
            TurboTests::DocumentationFormatter
          else
            Kernel.const_get(name)
          end

        add_formatter(formatter_class.new(output))
      end
    end

    def start
      delegate_to_formatters(:start, RSpec::Core::Notifications::StartNotification.new)
    end

    def example_passed(example)
      delegate_to_formatters(:example_passed, example.notification)

      @all_examples << example
      log_timing(example)
    end

    def example_pending(example)
      delegate_to_formatters(:example_pending, example.notification)

      @all_examples << example
      @pending_examples << example
      log_timing(example)
    end

    def example_failed(example)
      delegate_to_formatters(:example_failed, example.notification)

      @all_examples << example
      @failed_examples << example
      log_timing(example)
    end

    def message(message)
      delegate_to_formatters(:message, RSpec::Core::Notifications::MessageNotification.new(message))
      @messages << message
    end

    def error_outside_of_examples
      @errors_outside_of_examples_count += 1
    end

    def finish
      end_time = Time.now

      delegate_to_formatters(:stop, RSpec::Core::Notifications::ExamplesNotification.new(self))

      delegate_to_formatters(:start_dump, RSpec::Core::Notifications::NullNotification)

      delegate_to_formatters(
        :dump_pending,
        RSpec::Core::Notifications::ExamplesNotification.new(self),
      )

      delegate_to_formatters(
        :dump_failures,
        RSpec::Core::Notifications::ExamplesNotification.new(self),
      )

      delegate_to_formatters(
        :dump_summary,
        RSpec::Core::Notifications::SummaryNotification.new(
          end_time - @start_time,
          @all_examples,
          @failed_examples,
          @pending_examples,
          0,
          @errors_outside_of_examples_count,
        ),
        @timings,
      )

      delegate_to_formatters(:close, RSpec::Core::Notifications::NullNotification)
    end

    def add_formatter(formatter)
      @formatters << formatter
    end

    protected

    def delegate_to_formatters(method, *args)
      @formatters.each do |formatter|
        formatter.send(method, *args) if formatter.respond_to?(method)
      end
    end

    private

    def log_timing(example)
      if run_duration_ms = example.metadata[:run_duration_ms]
        @timings << [example.full_description, example.location, run_duration_ms]
        @timings.sort_by! { |timing| -timing.last }
        @timings.pop if @timings.size > @max_timings_count
      end
    end
  end
end