class Tryouts::CLI::VerboseFormatter

Detailed formatter with comprehensive output and clear visual hierarchy

def batch_summary(failure_collector)

Summary operations - show detailed failure summary
def batch_summary(failure_collector)
  return unless failure_collector.any_failures?
  puts
  write '=' * 50
  puts
  puts Console.color(:red, 'Failed Tests:')
  failure_collector.failures_by_file.each do |file_path, failures|
    failures.each_with_index do |failure, index|
      pretty_path = Console.pretty_path(file_path)
      # Include line number with file path for easy copying/clicking
      if failure.line_number > 0
        location = "#{pretty_path}:#{failure.line_number + 1}"
      else
        location = pretty_path
      end
      puts
      puts Console.color(:yellow, location)
      puts "  #{index + 1}) #{failure.description}"
      puts "     #{Console.color(:red, 'Failure:')} #{failure.failure_reason}"
      # Show source context in verbose mode
      if failure.source_context.any?
        puts "     #{Console.color(:cyan, 'Source:')}"
        failure.source_context.each do |line|
          puts "       #{line.strip}"
        end
      end
      puts
    end
  end
end

def debug_info(message, level: 0)

Debug and diagnostic output
def debug_info(message, level: 0)
  return unless @show_debug
  prefix = Console.color(:cyan, 'INFO ')
  puts
  puts indent_text("#{prefix} #{message}", level + 1)
end

def error_message(message, backtrace: nil)

def error_message(message, backtrace: nil)
  error_msg = Console.color(:red, "ERROR: #{message}")
  puts indent_text(error_msg, 1)
  return unless backtrace && @show_debug
  puts indent_text('Details:', 2)
  # Show first 10 lines of backtrace to avoid overwhelming output
  backtrace.first(10).each do |line|
    puts indent_text(line, 3)
  end
  puts indent_text("... (#{backtrace.length - 10} more lines)", 3) if backtrace.length > 10
end

def file_execution_start(_file_path, test_count:, context_mode:)

def file_execution_start(_file_path, test_count:, context_mode:)
  message = "Running #{test_count} tests with #{context_mode} context"
  puts(indent_text(message, 1))
end

def file_header_visual(file_path)

def file_header_visual(file_path)
  pretty_path = Console.pretty_path(file_path)
  header_content = ">>>>>  #{pretty_path}  "
  padding_length = [@line_width - header_content.length, 0].max
  padding = '<' * padding_length
  [
    indent_text('-' * @line_width, 1),
    indent_text(header_content + padding, 1),
    indent_text('-' * @line_width, 1),
  ].join("\n")
end

def file_parsed(_file_path, test_count:, setup_present: false, teardown_present: false)

def file_parsed(_file_path, test_count:, setup_present: false, teardown_present: false)
  message = ''
  extras = []
  extras << 'setup' if setup_present
  extras << 'teardown' if teardown_present
  message += " (#{extras.join(', ')})" unless extras.empty?
  puts(indent_text(message, 2))
end

def file_result(_file_path, total_tests:, failed_count:, error_count:, elapsed_time: nil)

def file_result(_file_path, total_tests:, failed_count:, error_count:, elapsed_time: nil)
  issues_count = failed_count + error_count
  passed_count = total_tests - issues_count
  details = ["#{passed_count} passed"]
  puts
  if issues_count > 0
    details << "#{failed_count} failed" if failed_count > 0
    details << "#{error_count} errors" if error_count > 0
    details_str = details.join(', ')
    color = :red
    time_str = elapsed_time ? " (#{elapsed_time.round(2)}s)" : ''
    message = "✗ Out of #{total_tests} tests: #{details_str}#{time_str}"
    puts indent_text(Console.color(color, message), 2)
  else
    message = "#{total_tests} tests passed"
    color = :green
    puts indent_text(Console.color(color, "✓ #{message}"), 2)
  end
  return unless elapsed_time
  time_msg = "Completed in #{format_timing(elapsed_time).strip.tr('()', '')}"
  puts indent_text(Console.color(:dim, time_msg), 2)
end

def file_start(file_path, context_info: {})

File-level operations
def file_start(file_path, context_info: {})
  puts(file_header_visual(file_path))
end

def grand_total(total_tests:, failed_count:, error_count:, successful_files:, total_files:, elapsed_time:)

def grand_total(total_tests:, failed_count:, error_count:, successful_files:, total_files:, elapsed_time:)
  puts
  puts '=' * @line_width
  puts 'Grand Total:'
  issues_count = failed_count + error_count
  time_str = if elapsed_time < 2.0
    " (#{(elapsed_time * 1000).round}ms)"
  else
    " (#{elapsed_time.round(2)}s)"
  end
  if issues_count > 0
    passed = [total_tests - issues_count, 0].max  # Ensure passed never goes negative
    details = []
    details << "#{failed_count} failed" if failed_count > 0
    details << "#{error_count} errors" if error_count > 0
    puts "#{details.join(', ')}, #{passed} passed#{time_str}"
  else
    puts "#{total_tests} tests passed#{time_str}"
  end
  puts "Files: #{successful_files} of #{total_files} successful"
  puts '=' * @line_width
end

def has_exception_expectations?(test_case)

def has_exception_expectations?(test_case)
  test_case.expectations.any? { |exp| exp.type == :exception }
end

def initialize(options = {})

def initialize(options = {})
  super
  @line_width     = options.fetch(:line_width, 70)
  @show_passed    = options.fetch(:show_passed, true)
  @show_debug     = options.fetch(:debug, false)
  @show_trace     = options.fetch(:trace, false)
end

def live_status_capabilities

def live_status_capabilities
  {
    supports_coordination: true,     # Verbose can work with coordinated output
    output_frequency: :high,         # Outputs frequently for each test
    requires_tty: false,             # Works without TTY
  }
end

def phase_header(message, file_count: nil)

Phase-level output
def phase_header(message, file_count: nil)
  return if message.include?('EXECUTING') # Skip execution phase headers
  header_line = message.center(@line_width)
  separator_line = '=' * @line_width
  puts(separator_line)
  puts(header_line)
  puts(separator_line)
end

def setup_output(output_text)

def setup_output(output_text)
  return if output_text.strip.empty?
  output_text.lines.each do |line|
    puts indent_text(line.chomp, 0)
  end
end

def setup_start(line_range:)

Setup/teardown operations
def setup_start(line_range:)
  message = "Executing global setup (lines #{line_range.first}..#{line_range.last})"
  puts indent_text(Console.color(:cyan, message), 2)
end

def show_exception_details(test_case, actual_results, expected_results = [])

def show_exception_details(test_case, actual_results, expected_results = [])
  return if actual_results.empty?
  puts indent_text('Exception Details:', 4)
  actual_results.each_with_index do |actual, idx|
    expected = expected_results[idx] if expected_results && idx < expected_results.length
    expectation = test_case.expectations[idx] if test_case.expectations
    if expectation&.type == :exception
      puts indent_text("Caught: #{Console.color(:blue, actual.inspect)}", 5)
      puts indent_text("Expectation: #{Console.color(:green, expectation.content)}", 5)
      puts indent_text("Result: #{Console.color(:green, expected.inspect)}", 5) if expected
    end
  end
  puts
end

def show_failure_details(test_case, actual_results, expected_results = [])

def show_failure_details(test_case, actual_results, expected_results = [])
  return if actual_results.empty?
  actual_results.each_with_index do |actual, idx|
    expected = expected_results[idx] if expected_results && idx < expected_results.length
    expected_line = test_case.expectations[idx] if test_case.expectations
    if !expected.nil?
      # Use the evaluated expected value from the evaluator
      puts indent_text("Expected: #{Console.color(:green, expected.inspect)}", 4)
      puts indent_text("Actual:   #{Console.color(:red, actual.inspect)}", 4)
    elsif expected_line && !expected_results.empty?
      # Only show raw expectation content if we have expected_results (non-error case)
      puts indent_text("Expected: #{Console.color(:green, expected_line.content)}", 4)
      puts indent_text("Actual:   #{Console.color(:red, actual.inspect)}", 4)
    else
      # For error cases (empty expected_results), just show the error
      puts indent_text("Error:   #{Console.color(:red, actual.inspect)}", 4)
    end
    # Show difference if both are strings
    if !expected.nil? && actual.is_a?(String) && expected.is_a?(String)
      show_string_diff(expected, actual)
    end
    puts
  end
end

def show_string_diff(expected, actual)

def show_string_diff(expected, actual)
  return if expected == actual
  puts indent_text('Difference:', 4)
  puts indent_text("- #{Console.color(:red, actual)}", 5)
  puts indent_text("+ #{Console.color(:green, expected)}", 5)
end

def show_test_source_code(test_case)

def show_test_source_code(test_case)
  # Use pre-captured source lines from parsing
  start_line = test_case.line_range.first
  test_case.source_lines.each_with_index do |line_content, index|
    line_num = start_line + index
    line_display = format('%3d: %s', line_num + 1, line_content)
    # Highlight expectation lines by checking if this line contains any expectation syntax
    if line_content.match?(%r{^\s*#\s*=(!|<|=|/=|\||:|~|%|\d+)?>\s*})
      line_display = Console.color(:yellow, line_display)
    end
    puts indent_text(line_display, 4)
  end
  puts
end

def teardown_output(output_text)

def teardown_output(output_text)
  return if output_text.strip.empty?
  output_text.lines.each do |line|
    puts indent_text(line.chomp, 0)
  end
end

def teardown_start(line_range:)

def teardown_start(line_range:)
  message = "Executing teardown (lines #{line_range.first}..#{line_range.last})"
  puts indent_text(Console.color(:cyan, message), 2)
  puts
end

def test_output(test_case:, output_text:, result_packet:)

def test_output(test_case:, output_text:, result_packet:)
  return if output_text.nil? || output_text.strip.empty?
  puts indent_text('Test Output:', 3)
  puts indent_text(Console.color(:dim, '--- BEGIN OUTPUT ---'), 3)
  output_text.lines.each do |line|
    puts indent_text(line.chomp, 4)
  end
  puts indent_text(Console.color(:dim, '--- END OUTPUT ---'), 3)
  puts
end

def test_result(result_packet)

def test_result(result_packet)
  should_show = @show_passed || !result_packet.passed?
  return unless should_show
  status_line = case result_packet.status
  when :passed
    Console.color(:green, 'PASSED')
  when :failed
    Console.color(:red, 'FAILED')
  when :error
    Console.color(:red, 'ERROR')
  when :skipped
    Console.color(:yellow, 'SKIPPED')
  else
    'UNKNOWN'
  end
  test_case = result_packet.test_case
  location = "#{Console.pretty_path(test_case.path)}:#{test_case.first_expectation_line + 1}"
  puts
  puts indent_text("#{status_line} @ #{location}", 2)
  # Show source code for verbose mode
  show_test_source_code(test_case)
  # Show failure details for failed tests
  if result_packet.failed? || result_packet.error?
    show_failure_details(test_case, result_packet.actual_results, result_packet.expected_results)
  # Show exception details for passed exception expectations
  elsif result_packet.passed? && has_exception_expectations?(test_case)
    show_exception_details(test_case, result_packet.actual_results, result_packet.expected_results)
  end
end

def test_start(test_case:, index:, total:)

Test-level operations
def test_start(test_case:, index:, total:)
  desc = test_case.description.to_s
  desc = 'Unnamed test' if desc.empty?
  message = "Test #{index}/#{total}: #{desc}"
  puts indent_text(Console.color(:dim, message), 2)
end

def trace_info(message, level: 0)

def trace_info(message, level: 0)
  return unless @show_trace
  prefix = Console.color(:dim, 'TRACE')
  puts indent_text("#{prefix} #{message}", level + 1)
end