class BundleUpdateInteractive::CLI

def generate_report(options)

def generate_report(options)
  whisper "Resolving latest gem versions..."
  updater_class = options.latest? ? Latest::Updater : Updater
  updater = updater_class.new(groups: options.exclusively, only_explicit: options.only_explicit?)
  report = updater.generate_report
  populate_vulnerabilities_and_changelogs_concurrently(report) unless report.empty?
  [report, updater]
end

def handle_exception(error)

def handle_exception(error)
  case error
  when Errno::EPIPE
    # Ignore
  when BundleUpdateInteractive::Error, OptionParser::ParseError, Interrupt, Bundler::Dsl::DSLError
    $stderr.puts BundleUpdateInteractive.pastel.red(error.message)
    exit false
  else
    raise
  end
end

def legend

def legend
  pastel = BundleUpdateInteractive.pastel
  <<~LEGEND
    Color legend:
    #{pastel.white.on_red('<inverse>')} Known security vulnerability
    #{pastel.red('<red>')}     Major update; likely to have breaking changes, high risk
    #{pastel.yellow('<yellow>')}  Minor update; changes and additions, moderate risk
    #{pastel.green('<green>')}   Patch update; bug fixes, low risk
    #{pastel.cyan('<cyan>')}    Possibly unreleased git commits; unknown risk
  LEGEND
end

def populate_vulnerabilities_and_changelogs_concurrently(report)

def populate_vulnerabilities_and_changelogs_concurrently(report)
  pool = ThreadPool.new(max_threads: 25)
  whisper "Checking for security vulnerabilities..."
  scan_promise = pool.future(report, &:scan_for_vulnerabilities!)
  changelog_promises = report.all_gems.map do |_, outdated_gem|
    pool.future(outdated_gem, &:changelog_uri)
  end
  progress "Finding changelogs", changelog_promises, &:value!
  scan_promise.value!
end

def progress(message, items, &block)

def progress(message, items, &block)
  $stderr.print(message)
  items.each_slice([1, items.length / 12].max) do |slice|
    slice.each(&block)
    $stderr.print(".")
  end
  $stderr.print("\n")
end

def puts_gemfile_modified_notice

def puts_gemfile_modified_notice
  puts BundleUpdateInteractive.pastel.yellow("Your Gemfile was changed to accommodate the latest gem versions.")
end

def puts_legend_and_withheld_gems(report)

def puts_legend_and_withheld_gems(report)
  puts
  puts legend
  puts
  return if report.withheld_gems.empty?
  puts "The following gems are being held back and cannot be updated."
  puts Table.withheld(report.withheld_gems).render
  puts
end

def run(argv: ARGV) # rubocop:disable Metrics/AbcSize

rubocop:disable Metrics/AbcSize
def run(argv: ARGV) # rubocop:disable Metrics/AbcSize
  options = Options.parse(argv)
  report, updater = generate_report(options)
  puts_legend_and_withheld_gems(report) unless report.empty?
  puts("No gems to update.").then { return } if report.updatable_gems.empty?
  selected_gems = MultiSelect.prompt_for_gems_to_update(report.updatable_gems)
  puts("No gems to update.").then { return } if selected_gems.empty?
  puts "Updating the following gems."
  puts Table.updatable(selected_gems).render
  puts
  if options.commit?
    GitCommitter.new(updater).apply_updates_as_individual_commits(*selected_gems.keys)
  else
    updater.apply_updates(*selected_gems.keys)
  end
  puts_gemfile_modified_notice if updater.modified_gemfile?
rescue Exception => e # rubocop:disable Lint/RescueException
  handle_exception(e)
end

def whisper(message)

def whisper(message)
  $stderr.puts(message)
end