# frozen_string_literal: true

require_relative 'branch_coverage'
require_relative 'color_printing'
require_relative 'line_printer'

class SimpleCov::Formatter::Terminal::ResultPrinter
  extend Forwardable
  prepend MemoWise
  include SimpleCov::Formatter::Terminal::BranchCoverage
  include SimpleCov::Formatter::Terminal::ColorPrinting

  def_delegators(:@file_determiner, :executed_spec_file, :targeted_application_file)

  def initialize(file_determiner)
    @file_determiner = file_determiner
  end

  def print_coverage_info(result)
    sourcefile = result.files.find { _1.filename.end_with?(targeted_application_file) }
    force_coverage = ENV.fetch('SIMPLECOV_FORCE_DETAILS', nil) == '1'

    if sourcefile.nil?
      print_no_coverage_info_found
    elsif failure_occurred? && !force_coverage
      print_coverage_summary(sourcefile, 'Not showing detailed coverage because an example failed.')
    elsif sourcefile.covered_percent < 100 || uncovered_branches(sourcefile).any? || force_coverage
      print_coverage_details(sourcefile)
    else
      print_coverage_summary(sourcefile)
    end
  end

  def print_coverage_summary(sourcefile, log_addendum = nil)
    summary = "-- Coverage for #{targeted_application_file} --\n"
    summary << "Line coverage: #{colorized_coverage(sourcefile.covered_percent)}"
    if SimpleCov.branch_coverage?
      summary << ' '
      summary << <<~LOG
        | Uncovered branches: #{colorized_uncovered_branches(uncovered_branches(sourcefile).size)}
      LOG
    else
      summary << "\n"
    end
    summary << log_addendum if log_addendum
    puts(summary)
  end

  def print_coverage_details(sourcefile)
    @sourcefile = sourcefile

    puts("---- Coverage for #{targeted_application_file} ".ljust(80, '-').rstrip)

    skipped_lines = []
    sourcefile.lines.each do |line|
      if print_line?(line.line_number)
        if skipped_lines.any?
          print_skipped_lines(skipped_lines)
        end

        puts(line_printer.colored_line(line, sourcefile))
        skipped_lines = []
      else
        skipped_lines << line.line_number
      end
    end

    if skipped_lines.any?
      print_skipped_lines(skipped_lines)
    end

    puts(<<~LOG.squish)
      ----
      Line coverage: #{colorized_coverage(sourcefile.covered_percent)}
      |
      Uncovered branches: #{colorized_uncovered_branches(uncovered_branches(sourcefile).size)}
      ----
    LOG
  end

  def print_skipped_lines(skipped_lines)
    divider = ' -' * 40

    puts(line_printer.numbered_line_output(nil, :white, divider))
    puts(
      line_printer.numbered_line_output(
        nil,
        :white,
        "#{skipped_lines.size} covered line(s) omitted".center(80, ' '),
      ),
    )
    puts(line_printer.numbered_line_output(nil, :white, divider))
  end

  def print_no_coverage_info_found
    puts(<<~LOG.squish)
      No code coverage info was found for "#{targeted_application_file}". Try stopping and
      disabling `spring`, if you are using it, and then rerun the spec.
    LOG
  end

  def print_info_for_no_executed_specs
    puts('Not showing test coverage details because no specs were executed successfully.')
  end

  def print_info_for_undeterminable_application_target
    puts(<<~LOG.squish)
      Not showing test coverage details because "#{executed_spec_file}" cannot
      be mapped to a single application file.
    LOG
    puts(<<~LOG.squish)
      Tip: you can specify a file manually via a SIMPLECOV_TARGET_FILE environment variable.
    LOG
  end

  def print_info_for_undetermined_application_target
    puts(<<~LOG.squish)
      Not showing test coverage details because we could not map "#{executed_spec_file}"
      to an application file.
    LOG
    puts(<<~LOG.squish)
      Tip: You can provide a mapping via
      `SimpleCov::Formatter::Terminal.config.spec_to_app_file_map`.
    LOG
  end

  def print_info_for_nonexistent_application_target
    puts(<<~LOG.squish)
      Cannot show code coverage. Looked for application file "#{targeted_application_file}",
      but it does not exist.
    LOG
  end

  private

  def failure_occurred?
    SimpleCov::Formatter::Terminal::RSpecIntegration.failure_occurred?
  end

  def colorized_coverage(covered_percent)
    case
    when covered_percent >= 100 then color("#{covered_percent.round(2)}%", :green)
    when covered_percent >= 80 then color("#{covered_percent.round(2)}%", :yellow)
    else color("#{covered_percent.round(2)}%", :red)
    end
  end

  def colorized_uncovered_branches(num_uncovered_branches)
    case num_uncovered_branches
    when 0 then color(num_uncovered_branches.to_s, :green)
    when (1..3) then color(num_uncovered_branches.to_s, :yellow)
    else color(num_uncovered_branches.to_s, :red)
    end
  end

  def print_line?(line_number)
    line_numbers_to_print.include?(line_number)
  end

  def sourcefile
    @sourcefile ||= @result.files.find { _1.filename.end_with?(targeted_application_file) }
  end

  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
  memo_wise \
  def line_numbers_to_print
    max_line_number = sourcefile.lines.map(&:line_number).max

    begin
      case SimpleCov::Formatter::Terminal.config.lines_to_print.to_sym
      in SimpleCov::Formatter::Terminal::Config::LinesToPrint::ALL
        (1..max_line_number).to_a
      in SimpleCov::Formatter::Terminal::Config::LinesToPrint::UNCOVERED
        line_numbers_to_print = []

        sourcefile.lines.each do |line|
          if (
            line.coverage.nil? || (
              (line.coverage > 0) &&
                !line_numbers_with_missing_branches(sourcefile).include?(line.line_number)
            )
          )
            next
          end

          line_number = line.line_number
          contextualized_line_numbers =
            ((line_number - 2)..(line_number + 2)).
              to_a.
              select do |context_line_number|
                context_line_number.positive? && context_line_number <= max_line_number
              end
          line_numbers_to_print += contextualized_line_numbers
        end

        line_numbers_to_print
      end
    end.to_set
  end
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

  memo_wise \
  def line_printer
    SimpleCov::Formatter::Terminal::LinePrinter.new(targeted_application_file)
  end
end