class Kdeploy::CLI

Command-line interface for Kdeploy

def self.exit_on_failure?

def self.exit_on_failure?
  true
end

def determine_tasks(task_name)

def determine_tasks(task_name)
  task_name ? [task_name.to_sym] : self.class.kdeploy_tasks.keys
end

def execute(task_file, task_name = nil)

def execute(task_file, task_name = nil)
  load_config_file
  show_banner_once
  @task_file_dir = File.dirname(File.expand_path(task_file))
  load_task_file(task_file)
  tasks_to_run = determine_tasks(task_name)
  execute_tasks(tasks_to_run)
rescue StandardError => e
  puts Kdeploy::Banner.show_error(e.message)
  exit 1
end

def execute_single_task(task)

def execute_single_task(task)
  task_hosts = self.class.get_task_hosts(task)
  hosts = filter_hosts(options[:limit], task_hosts)
  if hosts.empty?
    puts Kdeploy::Banner.show_error("No hosts found for task: #{task}")
    return nil
  end
  if options[:dry_run]
    print_dry_run(hosts, task)
    return nil
  end
  run_task(hosts, task)
end

def execute_tasks(tasks_to_run)

def execute_tasks(tasks_to_run)
  all_results = {}
  tasks_to_run.each do |task|
    task_results = execute_single_task(task)
    # Collect results for final summary
    all_results[task] = task_results if task_results
  end
  # Show combined summary at the end for all tasks
  print_all_tasks_summary(all_results) unless all_results.empty?
end

def extract_error_message(result)

def extract_error_message(result)
  return result[:error] if result[:error]
  if result[:output].is_a?(Array)
    result[:output].map do |o|
      o[:output][:stderr] if o[:output].is_a?(Hash)
    end.compact.join("\n")
  else
    result[:output].to_s
  end
end

def filter_hosts(limit, task_hosts)

def filter_hosts(limit, task_hosts)
  hosts = self.class.kdeploy_hosts.slice(*task_hosts)
  return hosts unless limit
  host_names = limit.split(',').map(&:strip)
  hosts.slice(*host_names)
end

def format_steps_by_type(type, steps, shown, formatter)

def format_steps_by_type(type, steps, shown, formatter)
  case type
  when :upload
    formatter.format_upload_steps(steps, shown)
  when :upload_template
    formatter.format_template_steps(steps, shown)
  when :run
    formatter.format_run_steps(steps, shown)
  else
    []
  end
end

def group_output_by_type(output)

def group_output_by_type(output)
  output.group_by { |step| step[:type] || :run }
end

def help(command = nil)

def help(command = nil)
  if command
    super
  else
    show_general_help
  end
end

def init(dir = '.')

def init(dir = '.')
  initializer = Initializer.new(dir)
  initializer.run
rescue StandardError => e
  puts Kdeploy::Banner.show_error(e.message)
  exit 1
end

def load_config_file

def load_config_file
  Configuration.load_from_file
end

def load_task_file(file)

def load_task_file(file)
  validate_task_file(file)
  # 用 instance_eval 并传递顶层 binding,兼容 heredoc
  self.class.module_eval(File.read(file), file)
rescue StandardError => e
  raise FileNotFoundError, file if e.message.include?('not found')
  raise
end

def print_all_tasks_summary(all_results)

def print_all_tasks_summary(all_results)
  debug_mode = options[:debug] || false
  formatter = OutputFormatter.new(debug: debug_mode)
  puts formatter.format_summary_header
  # Collect all hosts from all tasks
  all_hosts = {}
  all_results.each do |task_name, task_results|
    task_results.each do |host, result|
      all_hosts[host] ||= { ok: 0, changed: 0, failed: 0, tasks: [] }
      counts = formatter.calculate_summary_counts(result)
      all_hosts[host][:ok] += counts[:ok]
      all_hosts[host][:changed] += counts[:changed]
      all_hosts[host][:failed] += counts[:failed]
      all_hosts[host][:tasks] << task_name
    end
  end
  max_host_len = all_hosts.keys.map(&:length).max || 16
  all_hosts.keys.sort.each do |host|
    counts = all_hosts[host]
    line = formatter.build_summary_line(host, counts, max_host_len)
    puts formatter.colorize_summary_line(line, counts)
  end
end

def print_dry_run(hosts, task_name)

def print_dry_run(hosts, task_name)
  formatter = OutputFormatter.new
  # Banner already shown by show_banner_once, don't show again
  puts formatter.format_dry_run_header
  puts
  hosts.each do |name, config|
    print_dry_run_for_host(name, config, task_name, formatter)
  end
end

def print_dry_run_for_host(name, config, task_name, formatter)

def print_dry_run_for_host(name, config, task_name, formatter)
  commands = self.class.kdeploy_tasks[task_name][:block].call
  output = commands.map { |cmd| formatter.format_command_for_dry_run(cmd) }.join("\n")
  title = "#{name} (#{config[:ip]})"
  puts formatter.format_dry_run_box(title, output)
  puts
end

def print_failure_result(result, formatter)

def print_failure_result(result, formatter)
  error_message = extract_error_message(result)
  puts formatter.format_error(error_message)
end

def print_host_result(_host, result, formatter)

def print_host_result(_host, result, formatter)
  if %i[success changed].include?(result[:status])
    print_success_result(result, formatter)
  else
    print_failure_result(result, formatter)
  end
end

def print_results(results, task_name, show_summary: false, debug: false)

def print_results(results, task_name, show_summary: false, debug: false)
  formatter = OutputFormatter.new(debug: debug)
  puts formatter.format_task_header(task_name)
  if results.empty?
    puts Kdeploy::Banner.show_error("No hosts executed for task: #{task_name}")
    puts 'This usually means no hosts matched the task configuration.'
    return
  end
  results.each do |host, result|
    puts formatter.format_host_status(host, result[:status])
    print_host_result(host, result, formatter)
  end
  # Only show summary if explicitly requested (for single task execution)
  print_summary(results, formatter) if show_summary
end

def print_success_result(result, formatter)

def print_success_result(result, formatter)
  shown = {}
  grouped = group_output_by_type(result[:output])
  grouped.each do |type, steps|
    output_lines = format_steps_by_type(type, steps, shown, formatter)
    output_lines.each { |line| puts line }
  end
end

def print_summary(results, formatter)

def print_summary(results, formatter)
  puts formatter.format_summary_header
  max_host_len = results.keys.map(&:length).max || 16
  results.keys.sort.each do |host|
    result = results[host]
    puts formatter.format_summary_line(host, result, max_host_len)
  end
end

def run_task(hosts, task)

def run_task(hosts, task)
  output = ConsoleOutput.new
  parallel_count = options[:parallel] || Configuration.default_parallel
  debug_mode = options[:debug] || false
  base_dir = @task_file_dir
  runner = Runner.new(
    hosts, self.class.kdeploy_tasks,
    parallel: parallel_count,
    output: output,
    debug: debug_mode,
    base_dir: base_dir
  )
  results = runner.run(task)
  # Don't show summary here - it will be shown at the end for all tasks
  print_results(results, task, show_summary: false, debug: debug_mode)
  results
end

def show_banner_once

def show_banner_once
  @banner_printed ||= false
  return if @banner_printed
  puts Kdeploy::Banner.show
  @banner_printed = true
end

def show_general_help

def show_general_help
  formatter = HelpFormatter.new
  puts Kdeploy::Banner.show
  puts formatter.format_help
end

def validate_task_file(file)

def validate_task_file(file)
  return if File.exist?(file)
  puts Kdeploy::Banner.show_error("Task file not found: #{file}")
  exit 1
end

def version

def version
  puts Kdeploy::Banner.show_version
end