# frozen_string_literal: true

require "minitest/silence/fail_on_output_reporter"
require "minitest/silence/boxed_output_reporter"
require "minitest/silence/version"
require "io/console"

module Minitest
  module Silence
    Error = Class.new(StandardError)
    UnexpectedOutput = Class.new(Error)

    module ResultOutputPatch
      attr_accessor :output
    end

    module RunOneMethodPatch
      attr_reader :original_stdin, :original_stdout, :original_stderr

      def __run(*)
        @original_stdin = $stdin.dup
        @original_stdout = $stdout.dup
        @original_stderr = $stderr.dup
        super
      end

      def run_one_method(klass, method_name)
        output_reader, output_writer = IO.pipe
        output_thread = Thread.new { output_reader.read }

        result = begin
          $stdout.reopen(output_writer)
          $stderr.reopen(output_writer)
          $stdin.reopen(File::NULL)

          super
        ensure
          $stdout.reopen(original_stdout)
          $stderr.reopen(original_stderr)
          $stdin.reopen(original_stdin)
          output_writer.close
        end

        result.output = output_thread.value
        result
      end
    end

    class << self
      def boxed(title, content)
        box = +"── #{title} ──\n"
        box << "#{content}\n"
        box << "───#{'─' * title.length}───\n"
      end
    end
  end

  class << self
    def plugin_silence_options(opts, options)
      opts.on('--disable-silence', "Do not rebind standard IO") do
        options[:disable_silence] = true
      end
      opts.on('--fail-on-output', "Fail a test when it writes to STDOUT or STDERR") do
        options[:fail_on_output] = true
      end
    end

    def plugin_silence_init(options)
      unless options[:disable_silence]
        Minitest::Result.prepend(Minitest::Silence::ResultOutputPatch)
        Minitest.singleton_class.prepend(Minitest::Silence::RunOneMethodPatch)

        if options[:fail_on_output]
          # We have to make sure this reporter runs as the first reporter, so it can still adjust
          # the result and other reporters will take the change into account.
          reporter.reporters.unshift(Minitest::Silence::FailOnOutputReporter.new(options[:io], options))
        elsif options[:verbose]
          reporter << Minitest::Silence::BoxedOutputReporter.new(options[:io], options)
        end
      end
    end
  end
end